mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
It was possible that a malicious introducer sends new request with the same session ID that was used previously and thus causing introducees to have multiple states for the same session ID. This commits prevents that from happening and adds an integration test for that scenario. Also if an introducee removes an introducer, all past session states will be deleted from the database. For this, a test was added as well. Closes #371 Closes #372
1057 lines
36 KiB
Java
1057 lines
36 KiB
Java
package org.briarproject;
|
|
|
|
import net.jodah.concurrentunit.Waiter;
|
|
|
|
import org.briarproject.api.clients.SessionId;
|
|
import org.briarproject.api.contact.Contact;
|
|
import org.briarproject.api.contact.ContactId;
|
|
import org.briarproject.api.contact.ContactManager;
|
|
import org.briarproject.api.crypto.SecretKey;
|
|
import org.briarproject.api.data.BdfDictionary;
|
|
import org.briarproject.api.data.BdfEntry;
|
|
import org.briarproject.api.db.DatabaseComponent;
|
|
import org.briarproject.api.db.DbException;
|
|
import org.briarproject.api.db.Metadata;
|
|
import org.briarproject.api.db.Transaction;
|
|
import org.briarproject.api.event.Event;
|
|
import org.briarproject.api.event.EventListener;
|
|
import org.briarproject.api.event.IntroductionAbortedEvent;
|
|
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
|
|
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
|
|
import org.briarproject.api.event.IntroductionSucceededEvent;
|
|
import org.briarproject.api.event.MessageValidatedEvent;
|
|
import org.briarproject.api.identity.AuthorFactory;
|
|
import org.briarproject.api.identity.IdentityManager;
|
|
import org.briarproject.api.identity.LocalAuthor;
|
|
import org.briarproject.api.introduction.IntroductionManager;
|
|
import org.briarproject.api.introduction.IntroductionMessage;
|
|
import org.briarproject.api.introduction.IntroductionRequest;
|
|
import org.briarproject.api.lifecycle.LifecycleManager;
|
|
import org.briarproject.api.properties.TransportProperties;
|
|
import org.briarproject.api.properties.TransportPropertyManager;
|
|
import org.briarproject.api.sync.Group;
|
|
import org.briarproject.api.sync.MessageId;
|
|
import org.briarproject.api.sync.SyncSession;
|
|
import org.briarproject.api.sync.SyncSessionFactory;
|
|
import org.briarproject.api.system.Clock;
|
|
import org.briarproject.contact.ContactModule;
|
|
import org.briarproject.crypto.CryptoModule;
|
|
import org.briarproject.introduction.IntroductionGroupFactory;
|
|
import org.briarproject.introduction.IntroductionModule;
|
|
import org.briarproject.introduction.MessageSender;
|
|
import org.briarproject.lifecycle.LifecycleModule;
|
|
import org.briarproject.properties.PropertiesModule;
|
|
import org.briarproject.sync.SyncModule;
|
|
import org.briarproject.transport.TransportModule;
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.TimeoutException;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import static org.briarproject.TestPluginsModule.MAX_LATENCY;
|
|
import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
|
|
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
|
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
|
|
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
|
|
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
|
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
|
|
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
|
|
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
public class IntroductionIntegrationTest extends BriarTestCase {
|
|
|
|
LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
|
|
SyncSessionFactory sync0, sync1, sync2;
|
|
ContactManager contactManager0, contactManager1, contactManager2;
|
|
ContactId contactId0, contactId1, contactId2;
|
|
IdentityManager identityManager0, identityManager1, identityManager2;
|
|
LocalAuthor author0, author1, author2;
|
|
|
|
@Inject
|
|
Clock clock;
|
|
@Inject
|
|
AuthorFactory authorFactory;
|
|
|
|
// objects accessed from background threads need to be volatile
|
|
private volatile IntroductionManager introductionManager0;
|
|
private volatile IntroductionManager introductionManager1;
|
|
private volatile IntroductionManager introductionManager2;
|
|
private volatile Waiter eventWaiter;
|
|
private volatile Waiter msgWaiter;
|
|
|
|
private final File testDir = TestUtils.getTestDirectory();
|
|
private final SecretKey master = TestUtils.getSecretKey();
|
|
private final int TIMEOUT = 15000;
|
|
private final String INTRODUCER = "Introducer";
|
|
private final String INTRODUCEE1 = "Introducee1";
|
|
private final String INTRODUCEE2 = "Introducee2";
|
|
|
|
private static final Logger LOG =
|
|
Logger.getLogger(IntroductionIntegrationTest.class.getName());
|
|
|
|
private IntroductionIntegrationTestComponent t0, t1, t2;
|
|
|
|
@Before
|
|
public void setUp() {
|
|
IntroductionIntegrationTestComponent component =
|
|
DaggerIntroductionIntegrationTestComponent.builder().build();
|
|
component.inject(this);
|
|
injectEagerSingletons(component);
|
|
|
|
assertTrue(testDir.mkdirs());
|
|
File t0Dir = new File(testDir, INTRODUCER);
|
|
t0 = DaggerIntroductionIntegrationTestComponent.builder()
|
|
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
|
injectEagerSingletons(t0);
|
|
File t1Dir = new File(testDir, INTRODUCEE1);
|
|
t1 = DaggerIntroductionIntegrationTestComponent.builder()
|
|
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
|
injectEagerSingletons(t1);
|
|
File t2Dir = new File(testDir, INTRODUCEE2);
|
|
t2 = DaggerIntroductionIntegrationTestComponent.builder()
|
|
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
|
injectEagerSingletons(t2);
|
|
|
|
identityManager0 = t0.getIdentityManager();
|
|
identityManager1 = t1.getIdentityManager();
|
|
identityManager2 = t2.getIdentityManager();
|
|
contactManager0 = t0.getContactManager();
|
|
contactManager1 = t1.getContactManager();
|
|
contactManager2 = t2.getContactManager();
|
|
introductionManager0 = t0.getIntroductionManager();
|
|
introductionManager1 = t1.getIntroductionManager();
|
|
introductionManager2 = t2.getIntroductionManager();
|
|
sync0 = t0.getSyncSessionFactory();
|
|
sync1 = t1.getSyncSessionFactory();
|
|
sync2 = t2.getSyncSessionFactory();
|
|
|
|
// initialize waiters fresh for each test
|
|
eventWaiter = new Waiter();
|
|
msgWaiter = new Waiter();
|
|
}
|
|
|
|
@Test
|
|
public void testIntroductionSession() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees as contacts
|
|
contactId1 = contactManager0.addContact(author1,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0,
|
|
author1.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
ContactId contactId02 = contactManager2.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
assertTrue(contactId0.equals(contactId02));
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
|
t1.getEventBus().addListener(listener1);
|
|
IntroduceeListener listener2 = new IntroduceeListener(2, true);
|
|
t2.getEventBus().addListener(listener2);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
|
|
|
// sync first request message
|
|
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener1.requestReceived);
|
|
|
|
// sync second request message
|
|
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener2.requestReceived);
|
|
|
|
// sync first response
|
|
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response1Received);
|
|
|
|
// sync second response
|
|
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response2Received);
|
|
|
|
// sync forwarded responses to introducees
|
|
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
|
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
|
|
|
|
// sync first ACK and its forward
|
|
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
|
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
|
|
|
|
// sync second ACK and its forward
|
|
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
|
|
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 2");
|
|
|
|
// wait for introduction to succeed
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener1.succeeded);
|
|
assertTrue(listener2.succeeded);
|
|
|
|
assertTrue(contactManager1
|
|
.contactExists(author2.getId(), author1.getId()));
|
|
assertTrue(contactManager2
|
|
.contactExists(author1.getId(), author2.getId()));
|
|
|
|
assertDefaultUiMessages();
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIntroductionSessionFirstDecline() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees as contacts
|
|
contactId1 = contactManager0.addContact(author1, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0, author1.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
ContactId contactId02 = contactManager2.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
assertTrue(contactId0.equals(contactId02));
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, false);
|
|
t1.getEventBus().addListener(listener1);
|
|
IntroduceeListener listener2 = new IntroduceeListener(2, true);
|
|
t2.getEventBus().addListener(listener2);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, null, time);
|
|
|
|
// sync request messages
|
|
deliverMessage(sync0, contactId0, sync1, contactId1);
|
|
deliverMessage(sync0, contactId0, sync2, contactId2);
|
|
|
|
// wait for requests to arrive
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener1.requestReceived);
|
|
assertTrue(listener2.requestReceived);
|
|
|
|
// sync first response
|
|
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response1Received);
|
|
|
|
// sync second response
|
|
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response2Received);
|
|
|
|
// sync first forwarded response
|
|
deliverMessage(sync0, contactId0, sync2, contactId2);
|
|
|
|
// note how the introducer does not forward the second response,
|
|
// because after the first decline the protocol finished
|
|
|
|
assertFalse(listener1.succeeded);
|
|
assertFalse(listener2.succeeded);
|
|
|
|
assertFalse(contactManager1
|
|
.contactExists(author2.getId(), author1.getId()));
|
|
assertFalse(contactManager2
|
|
.contactExists(author1.getId(), author2.getId()));
|
|
|
|
assertEquals(2,
|
|
introductionManager0.getIntroductionMessages(contactId1)
|
|
.size());
|
|
assertEquals(2,
|
|
introductionManager0.getIntroductionMessages(contactId2)
|
|
.size());
|
|
assertEquals(2,
|
|
introductionManager1.getIntroductionMessages(contactId0)
|
|
.size());
|
|
// introducee2 should also have the decline response of introducee1
|
|
assertEquals(3,
|
|
introductionManager2.getIntroductionMessages(contactId0)
|
|
.size());
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIntroductionSessionSecondDecline() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees as contacts
|
|
contactId1 = contactManager0.addContact(author1, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0, author1.getId(),
|
|
master, clock.currentTimeMillis(), false, true
|
|
);
|
|
ContactId contactId02 = contactManager2.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), false,
|
|
true
|
|
);
|
|
assertTrue(contactId0.equals(contactId02));
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
|
t1.getEventBus().addListener(listener1);
|
|
IntroduceeListener listener2 = new IntroduceeListener(2, false);
|
|
t2.getEventBus().addListener(listener2);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, null, time);
|
|
|
|
// sync request messages
|
|
deliverMessage(sync0, contactId0, sync1, contactId1);
|
|
deliverMessage(sync0, contactId0, sync2, contactId2);
|
|
|
|
// wait for requests to arrive
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener1.requestReceived);
|
|
assertTrue(listener2.requestReceived);
|
|
|
|
// sync first response
|
|
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response1Received);
|
|
|
|
// sync second response
|
|
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response2Received);
|
|
|
|
// sync both forwarded response
|
|
deliverMessage(sync0, contactId0, sync2, contactId2);
|
|
deliverMessage(sync0, contactId0, sync1, contactId1);
|
|
|
|
assertFalse(contactManager1
|
|
.contactExists(author2.getId(), author1.getId()));
|
|
assertFalse(contactManager2
|
|
.contactExists(author1.getId(), author2.getId()));
|
|
|
|
assertEquals(2,
|
|
introductionManager0.getIntroductionMessages(contactId1)
|
|
.size());
|
|
assertEquals(2,
|
|
introductionManager0.getIntroductionMessages(contactId2)
|
|
.size());
|
|
// introducee1 also sees the decline response from introducee2
|
|
assertEquals(3,
|
|
introductionManager1.getIntroductionMessages(contactId0)
|
|
.size());
|
|
assertEquals(2,
|
|
introductionManager2.getIntroductionMessages(contactId0)
|
|
.size());
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIntroductionSessionDelayedFirstDecline() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees as contacts
|
|
contactId1 = contactManager0.addContact(author1, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0, author1.getId(),
|
|
master, clock.currentTimeMillis(), false, true
|
|
);
|
|
ContactId contactId02 = contactManager2.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), false,
|
|
true
|
|
);
|
|
assertTrue(contactId0.equals(contactId02));
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, false);
|
|
t1.getEventBus().addListener(listener1);
|
|
IntroduceeListener listener2 = new IntroduceeListener(2, false);
|
|
t2.getEventBus().addListener(listener2);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, null, time);
|
|
|
|
// sync request messages
|
|
deliverMessage(sync0, contactId0, sync1, contactId1);
|
|
deliverMessage(sync0, contactId0, sync2, contactId2);
|
|
|
|
// wait for requests to arrive
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener1.requestReceived);
|
|
assertTrue(listener2.requestReceived);
|
|
|
|
// sync first response
|
|
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response1Received);
|
|
|
|
// sync second response
|
|
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener0.response2Received);
|
|
|
|
// sync first forwarded response
|
|
deliverMessage(sync0, contactId0, sync2, contactId2);
|
|
|
|
// note how the second response will not be forwarded anymore
|
|
|
|
assertFalse(contactManager1
|
|
.contactExists(author2.getId(), author1.getId()));
|
|
assertFalse(contactManager2
|
|
.contactExists(author1.getId(), author2.getId()));
|
|
|
|
// since introducee2 was already in FINISHED state when
|
|
// introducee1's response arrived, she ignores and deletes it
|
|
assertDefaultUiMessages();
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIntroductionToSameContact() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducee as contact
|
|
contactId1 = contactManager0.addContact(author1, author0.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0, author1.getId(),
|
|
master, clock.currentTimeMillis(), true, true
|
|
);
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
|
t1.getEventBus().addListener(listener1);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee1, null, time);
|
|
|
|
// sync request messages
|
|
deliverMessage(sync0, contactId0, sync1, contactId1);
|
|
|
|
// we should not get any event, because the request will be discarded
|
|
assertFalse(listener1.requestReceived);
|
|
|
|
// make really sure we don't have that request
|
|
assertTrue(introductionManager1.getIntroductionMessages(contactId0)
|
|
.isEmpty());
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIntroductionToIdentitiesOfSameContact() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
author0 = authorFactory.createLocalAuthor(INTRODUCER,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
|
TestUtils.getRandomBytes(123));
|
|
identityManager0.addLocalAuthor(author0);
|
|
author1 = authorFactory.createLocalAuthor(INTRODUCEE1,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
|
TestUtils.getRandomBytes(123));
|
|
identityManager1.addLocalAuthor(author1);
|
|
author2 = authorFactory.createLocalAuthor(INTRODUCEE2,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
|
TestUtils.getRandomBytes(123));
|
|
identityManager1.addLocalAuthor(author2);
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees' authors as contacts
|
|
contactId1 = contactManager0.addContact(author1,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = null;
|
|
ContactId contactId01 = contactManager1.addContact(author0,
|
|
author1.getId(), master, clock.currentTimeMillis(), false,
|
|
true
|
|
);
|
|
ContactId contactId02 = contactManager1.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), false,
|
|
true
|
|
);
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
|
t1.getEventBus().addListener(listener1);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
|
|
|
// sync request messages
|
|
deliverMessage(sync0, contactId01, sync1, contactId1);
|
|
deliverMessage(sync0, contactId02, sync1, contactId2);
|
|
|
|
// wait for request to arrive
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener1.requestReceived);
|
|
|
|
// sync responses
|
|
deliverMessage(sync1, contactId1, sync0, contactId01);
|
|
deliverMessage(sync1, contactId2, sync0, contactId02);
|
|
|
|
// wait for two responses to arrive
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener0.response1Received);
|
|
assertTrue(listener0.response2Received);
|
|
|
|
// sync forwarded responses to introducees
|
|
deliverMessage(sync0, contactId01, sync1, contactId1);
|
|
deliverMessage(sync0, contactId02, sync1, contactId2);
|
|
|
|
// wait for "both" introducees to abort session
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener1.aborted);
|
|
|
|
// sync abort message
|
|
deliverMessage(sync1, contactId1, sync0, contactId01);
|
|
deliverMessage(sync1, contactId2, sync0, contactId02);
|
|
|
|
// wait for introducer to abort session (gets event twice)
|
|
eventWaiter.await(TIMEOUT, 2);
|
|
assertTrue(listener0.aborted);
|
|
|
|
assertFalse(contactManager1
|
|
.contactExists(author1.getId(), author2.getId()));
|
|
assertFalse(contactManager1
|
|
.contactExists(author2.getId(), author1.getId()));
|
|
|
|
assertEquals(2, introductionManager0.getIntroductionMessages(
|
|
contactId1).size());
|
|
assertEquals(2, introductionManager0.getIntroductionMessages(
|
|
contactId2).size());
|
|
assertEquals(2, introductionManager1.getIntroductionMessages(
|
|
contactId01).size());
|
|
assertEquals(2, introductionManager1.getIntroductionMessages(
|
|
contactId02).size());
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testSessionIdReuse() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees as contacts
|
|
contactId1 = contactManager0.addContact(author1,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0,
|
|
author1.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
ContactId contactId02 = contactManager2.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
assertTrue(contactId0.equals(contactId02));
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
|
t1.getEventBus().addListener(listener1);
|
|
IntroduceeListener listener2 = new IntroduceeListener(2, true);
|
|
t2.getEventBus().addListener(listener2);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
|
|
|
// sync first request message
|
|
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener1.requestReceived);
|
|
|
|
// get SessionId
|
|
List<IntroductionMessage> list = new ArrayList<>(
|
|
introductionManager1.getIntroductionMessages(contactId0));
|
|
assertEquals(2, list.size());
|
|
assertTrue(list.get(0) instanceof IntroductionRequest);
|
|
IntroductionRequest msg = (IntroductionRequest) list.get(0);
|
|
SessionId sessionId = msg.getSessionId();
|
|
|
|
// get contact group
|
|
IntroductionGroupFactory groupFactory =
|
|
t0.getIntroductionGroupFactory();
|
|
Group group = groupFactory.createIntroductionGroup(introducee1);
|
|
|
|
// create new message with same SessionId
|
|
BdfDictionary d = BdfDictionary.of(
|
|
new BdfEntry(TYPE, TYPE_REQUEST),
|
|
new BdfEntry(SESSION_ID, sessionId),
|
|
new BdfEntry(GROUP_ID, group.getId()),
|
|
new BdfEntry(NAME, TestUtils.getRandomString(42)),
|
|
new BdfEntry(PUBLIC_KEY,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
|
|
);
|
|
|
|
// reset request received state
|
|
listener1.requestReceived = false;
|
|
|
|
// add the message to the queue
|
|
DatabaseComponent db0 = t0.getDatabaseComponent();
|
|
MessageSender sender0 = t0.getMessageSender();
|
|
Transaction txn = db0.startTransaction(false);
|
|
try {
|
|
sender0.sendMessage(txn, d);
|
|
txn.setComplete();
|
|
} finally {
|
|
db0.endTransaction(txn);
|
|
}
|
|
|
|
// actually send message
|
|
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
|
|
|
// make sure it does not arrive
|
|
assertFalse(listener1.requestReceived);
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIntroducerRemovedCleanup() throws Exception {
|
|
startLifecycles();
|
|
try {
|
|
// Add Identities
|
|
addDefaultIdentities();
|
|
|
|
// Add Transport Properties
|
|
addTransportProperties();
|
|
|
|
// Add introducees as contacts
|
|
contactId1 = contactManager0.addContact(author1,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
contactId2 = contactManager0.addContact(author2,
|
|
author0.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
// Add introducer back
|
|
contactId0 = contactManager1.addContact(author0,
|
|
author1.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
ContactId contactId02 = contactManager2.addContact(author0,
|
|
author2.getId(), master, clock.currentTimeMillis(), true,
|
|
true
|
|
);
|
|
assertTrue(contactId0.equals(contactId02));
|
|
|
|
// listen to events
|
|
IntroducerListener listener0 = new IntroducerListener();
|
|
t0.getEventBus().addListener(listener0);
|
|
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
|
t1.getEventBus().addListener(listener1);
|
|
IntroduceeListener listener2 = new IntroduceeListener(2, true);
|
|
t2.getEventBus().addListener(listener2);
|
|
|
|
// make introduction
|
|
long time = clock.currentTimeMillis();
|
|
Contact introducee1 = contactManager0.getContact(contactId1);
|
|
Contact introducee2 = contactManager0.getContact(contactId2);
|
|
introductionManager0
|
|
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
|
|
|
// sync first request message
|
|
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
|
eventWaiter.await(TIMEOUT, 1);
|
|
assertTrue(listener1.requestReceived);
|
|
|
|
// get database and local group for introducee
|
|
DatabaseComponent db1 = t1.getDatabaseComponent();
|
|
IntroductionGroupFactory groupFactory1 =
|
|
t1.getIntroductionGroupFactory();
|
|
Group group1 = groupFactory1.createLocalGroup();
|
|
|
|
// get local session state messages
|
|
Map<MessageId, Metadata> map;
|
|
Transaction txn = db1.startTransaction(false);
|
|
try {
|
|
map = db1.getMessageMetadata(txn, group1.getId());
|
|
txn.setComplete();
|
|
} finally {
|
|
db1.endTransaction(txn);
|
|
}
|
|
// check that we have one session state
|
|
assertEquals(1, map.size());
|
|
|
|
// introducee1 removes introducer
|
|
contactManager1.removeContact(contactId0);
|
|
|
|
// get local session state messages again
|
|
txn = db1.startTransaction(false);
|
|
try {
|
|
map = db1.getMessageMetadata(txn, group1.getId());
|
|
txn.setComplete();
|
|
} finally {
|
|
db1.endTransaction(txn);
|
|
}
|
|
// make sure local state got deleted
|
|
assertEquals(0, map.size());
|
|
} finally {
|
|
stopLifecycles();
|
|
}
|
|
}
|
|
|
|
// TODO add a test for faking responses when #256 is implemented
|
|
|
|
@After
|
|
public void tearDown() throws InterruptedException {
|
|
TestUtils.deleteTestDirectory(testDir);
|
|
}
|
|
|
|
private void startLifecycles() throws InterruptedException {
|
|
// Start the lifecycle manager and wait for it to finish
|
|
lifecycleManager0 = t0.getLifecycleManager();
|
|
lifecycleManager1 = t1.getLifecycleManager();
|
|
lifecycleManager2 = t2.getLifecycleManager();
|
|
lifecycleManager0.startServices();
|
|
lifecycleManager1.startServices();
|
|
lifecycleManager2.startServices();
|
|
lifecycleManager0.waitForStartup();
|
|
lifecycleManager1.waitForStartup();
|
|
lifecycleManager2.waitForStartup();
|
|
}
|
|
|
|
private void stopLifecycles() throws InterruptedException {
|
|
// Clean up
|
|
lifecycleManager0.stopServices();
|
|
lifecycleManager1.stopServices();
|
|
lifecycleManager2.stopServices();
|
|
lifecycleManager0.waitForShutdown();
|
|
lifecycleManager1.waitForShutdown();
|
|
lifecycleManager2.waitForShutdown();
|
|
}
|
|
|
|
private void addTransportProperties() throws DbException {
|
|
TransportPropertyManager tpm0 = t0.getTransportPropertyManager();
|
|
TransportPropertyManager tpm1 = t1.getTransportPropertyManager();
|
|
TransportPropertyManager tpm2 = t2.getTransportPropertyManager();
|
|
|
|
TransportProperties tp = new TransportProperties(
|
|
Collections.singletonMap("key", "value"));
|
|
tpm0.mergeLocalProperties(TRANSPORT_ID, tp);
|
|
tpm1.mergeLocalProperties(TRANSPORT_ID, tp);
|
|
tpm2.mergeLocalProperties(TRANSPORT_ID, tp);
|
|
}
|
|
|
|
private void addDefaultIdentities() throws DbException {
|
|
author0 = authorFactory.createLocalAuthor(INTRODUCER,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
|
TestUtils.getRandomBytes(123));
|
|
identityManager0.addLocalAuthor(author0);
|
|
author1 = authorFactory.createLocalAuthor(INTRODUCEE1,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
|
TestUtils.getRandomBytes(123));
|
|
identityManager1.addLocalAuthor(author1);
|
|
author2 = authorFactory.createLocalAuthor(INTRODUCEE2,
|
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
|
TestUtils.getRandomBytes(123));
|
|
identityManager2.addLocalAuthor(author2);
|
|
}
|
|
|
|
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
|
|
SyncSessionFactory toSync, ContactId toId)
|
|
throws IOException, TimeoutException {
|
|
deliverMessage(fromSync, fromId, toSync, toId, null);
|
|
}
|
|
|
|
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
|
|
SyncSessionFactory toSync, ContactId toId, String debug)
|
|
throws IOException, TimeoutException {
|
|
|
|
if (debug != null) LOG.info("TEST: Sending message from " + debug);
|
|
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
// Create an outgoing sync session
|
|
SyncSession sessionFrom =
|
|
fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
|
|
// Write whatever needs to be written
|
|
sessionFrom.run();
|
|
out.close();
|
|
|
|
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
|
// Create an incoming sync session
|
|
SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
|
|
// Read whatever needs to be read
|
|
sessionTo.run();
|
|
in.close();
|
|
|
|
// wait for message to actually arrive
|
|
msgWaiter.await(TIMEOUT, 1);
|
|
}
|
|
|
|
private void assertDefaultUiMessages() throws DbException {
|
|
assertEquals(2, introductionManager0.getIntroductionMessages(
|
|
contactId1).size());
|
|
assertEquals(2, introductionManager0.getIntroductionMessages(
|
|
contactId2).size());
|
|
assertEquals(2, introductionManager1.getIntroductionMessages(
|
|
contactId0).size());
|
|
assertEquals(2, introductionManager2.getIntroductionMessages(
|
|
contactId0).size());
|
|
}
|
|
|
|
private class IntroduceeListener implements EventListener {
|
|
|
|
public volatile boolean requestReceived = false;
|
|
public volatile boolean succeeded = false;
|
|
public volatile boolean aborted = false;
|
|
|
|
private final int introducee;
|
|
private final boolean accept;
|
|
|
|
IntroduceeListener(int introducee, boolean accept) {
|
|
this.introducee = introducee;
|
|
this.accept = accept;
|
|
}
|
|
|
|
public void eventOccurred(Event e) {
|
|
if (e instanceof MessageValidatedEvent) {
|
|
MessageValidatedEvent event = (MessageValidatedEvent) e;
|
|
if (event.getClientId()
|
|
.equals(introductionManager0.getClientId()) &&
|
|
!event.isLocal()) {
|
|
LOG.info("TEST: Introducee" + introducee +
|
|
" received message in group " +
|
|
((MessageValidatedEvent) e).getMessage()
|
|
.getGroupId().hashCode());
|
|
msgWaiter.resume();
|
|
}
|
|
} else if (e instanceof IntroductionRequestReceivedEvent) {
|
|
IntroductionRequestReceivedEvent introEvent =
|
|
((IntroductionRequestReceivedEvent) e);
|
|
requestReceived = true;
|
|
IntroductionRequest ir = introEvent.getIntroductionRequest();
|
|
ContactId contactId = introEvent.getContactId();
|
|
SessionId sessionId = ir.getSessionId();
|
|
long time = clock.currentTimeMillis();
|
|
try {
|
|
if (introducee == 1) {
|
|
if (accept) {
|
|
introductionManager1
|
|
.acceptIntroduction(contactId, sessionId,
|
|
time);
|
|
} else {
|
|
introductionManager1
|
|
.declineIntroduction(contactId, sessionId,
|
|
time);
|
|
}
|
|
} else if (introducee == 2) {
|
|
if (accept) {
|
|
introductionManager2
|
|
.acceptIntroduction(contactId, sessionId,
|
|
time);
|
|
} else {
|
|
introductionManager2
|
|
.declineIntroduction(contactId, sessionId,
|
|
time);
|
|
}
|
|
}
|
|
} catch (DbException | IOException exception) {
|
|
eventWaiter.rethrow(exception);
|
|
} finally {
|
|
eventWaiter.resume();
|
|
}
|
|
} else if (e instanceof IntroductionSucceededEvent) {
|
|
succeeded = true;
|
|
Contact contact = ((IntroductionSucceededEvent) e).getContact();
|
|
eventWaiter.assertFalse(contact.getId().equals(contactId0));
|
|
eventWaiter.assertTrue(contact.isActive());
|
|
eventWaiter.resume();
|
|
} else if (e instanceof IntroductionAbortedEvent) {
|
|
aborted = true;
|
|
eventWaiter.resume();
|
|
}
|
|
}
|
|
}
|
|
|
|
private class IntroducerListener implements EventListener {
|
|
|
|
public volatile boolean response1Received = false;
|
|
public volatile boolean response2Received = false;
|
|
public volatile boolean aborted = false;
|
|
|
|
public void eventOccurred(Event e) {
|
|
if (e instanceof MessageValidatedEvent) {
|
|
MessageValidatedEvent event = (MessageValidatedEvent) e;
|
|
if (event.getClientId()
|
|
.equals(introductionManager0.getClientId()) &&
|
|
!event.isLocal()) {
|
|
LOG.info("TEST: Introducer received message in group " +
|
|
((MessageValidatedEvent) e).getMessage()
|
|
.getGroupId().hashCode());
|
|
msgWaiter.resume();
|
|
}
|
|
} else if (e instanceof IntroductionResponseReceivedEvent) {
|
|
ContactId c =
|
|
((IntroductionResponseReceivedEvent) e).getContactId();
|
|
try {
|
|
if (c.equals(contactId1)) {
|
|
response1Received = true;
|
|
} else if (c.equals(contactId2)) {
|
|
response2Received = true;
|
|
}
|
|
} finally {
|
|
eventWaiter.resume();
|
|
}
|
|
} else if (e instanceof IntroductionAbortedEvent) {
|
|
aborted = true;
|
|
eventWaiter.resume();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void injectEagerSingletons(
|
|
IntroductionIntegrationTestComponent component) {
|
|
|
|
component.inject(new LifecycleModule.EagerSingletons());
|
|
component.inject(new LifecycleModule.EagerSingletons());
|
|
component.inject(new IntroductionModule.EagerSingletons());
|
|
component.inject(new CryptoModule.EagerSingletons());
|
|
component.inject(new ContactModule.EagerSingletons());
|
|
component.inject(new TransportModule.EagerSingletons());
|
|
component.inject(new SyncModule.EagerSingletons());
|
|
component.inject(new PropertiesModule.EagerSingletons());
|
|
}
|
|
|
|
}
|