Files
briar/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java
Torsten Grote 685e1422a5 Do not allow session ID reuse and clean up sessions for introducee
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
2016-05-04 20:39:22 -03:00

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());
}
}