diff --git a/briar-android-tests/build.gradle b/briar-android-tests/build.gradle index 8c329bb6e3aabecbcacd215980d3075e440033e7..ba1dd6ec21eb2e86e35dcbd9b08a6af61eae5473 100644 --- a/briar-android-tests/build.gradle +++ b/briar-android-tests/build.gradle @@ -31,6 +31,7 @@ dependencies { compile project(':briar-api') compile project(':briar-core') testCompile 'junit:junit:4.12' + testCompile 'net.jodah:concurrentunit:0.4.2' compile 'com.android.support:appcompat-v7:23.2.0' testApt 'com.google.dagger:dagger-compiler:2.0.2' provided 'javax.annotation:jsr250-api:1.0' diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7f2f02be254eda522d1850d2cc47f1c5c6248f09 --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java @@ -0,0 +1,750 @@ +package org.briarproject; + +import net.jodah.concurrentunit.Waiter; + +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.db.DbException; +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.IntroductionRequest; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.properties.TransportProperties; +import org.briarproject.api.properties.TransportPropertyManager; +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.IntroductionModule; +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.Collections; +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.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())); + + assertDefaultUiMessages(); + } 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())); + + 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())); + + assertTrue(introductionManager0.getIntroductionMessages(contactId1) + .size() == 2); + assertTrue(introductionManager0.getIntroductionMessages(contactId2) + .size() == 2); + assertTrue(introductionManager1.getIntroductionMessages(contactId01) + .size() == 2); + assertTrue(introductionManager1.getIntroductionMessages(contactId02) + .size() == 2); + } 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 { + assertTrue(introductionManager0.getIntroductionMessages(contactId1) + .size() == 2); + assertTrue(introductionManager0.getIntroductionMessages(contactId2) + .size() == 2); + assertTrue(introductionManager1.getIntroductionMessages(contactId0) + .size() == 2); + assertTrue(introductionManager2.getIntroductionMessages(contactId0) + .size() == 2); + } + + 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()); + } + +} diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..b027327565cce75a422788738a3442ab6b21b5fa --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java @@ -0,0 +1,77 @@ +package org.briarproject; + +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.introduction.IntroductionManager; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.properties.TransportPropertyManager; +import org.briarproject.api.sync.SyncSessionFactory; +import org.briarproject.clients.ClientsModule; +import org.briarproject.contact.ContactModule; +import org.briarproject.crypto.CryptoModule; +import org.briarproject.data.DataModule; +import org.briarproject.db.DatabaseModule; +import org.briarproject.event.EventModule; +import org.briarproject.identity.IdentityModule; +import org.briarproject.introduction.IntroductionModule; +import org.briarproject.lifecycle.LifecycleModule; +import org.briarproject.properties.PropertiesModule; +import org.briarproject.sync.SyncModule; +import org.briarproject.transport.TransportModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestSystemModule.class, + TestDatabaseModule.class, + TestPluginsModule.class, + LifecycleModule.class, + IntroductionModule.class, + DatabaseModule.class, + CryptoModule.class, + EventModule.class, + ContactModule.class, + IdentityModule.class, + TransportModule.class, + ClientsModule.class, + SyncModule.class, + DataModule.class, + PropertiesModule.class +}) +public interface IntroductionIntegrationTestComponent { + + void inject(IntroductionIntegrationTest testCase); + + void inject(ContactModule.EagerSingletons init); + + void inject(CryptoModule.EagerSingletons init); + + void inject(IntroductionModule.EagerSingletons init); + + void inject(LifecycleModule.EagerSingletons init); + + void inject(PropertiesModule.EagerSingletons init); + + void inject(SyncModule.EagerSingletons init); + + void inject(TransportModule.EagerSingletons init); + + LifecycleManager getLifecycleManager(); + + EventBus getEventBus(); + + IdentityManager getIdentityManager(); + + ContactManager getContactManager(); + + IntroductionManager getIntroductionManager(); + + TransportPropertyManager getTransportPropertyManager(); + + SyncSessionFactory getSyncSessionFactory(); + +} diff --git a/briar-api/src/org/briarproject/api/event/IntroductionAbortedEvent.java b/briar-api/src/org/briarproject/api/event/IntroductionAbortedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..258d588e284a05c980aa7c193ff73f8993fe7305 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/IntroductionAbortedEvent.java @@ -0,0 +1,23 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.introduction.SessionId; + +public class IntroductionAbortedEvent extends Event { + + private final ContactId contactId; + private final SessionId sessionId; + + public IntroductionAbortedEvent(ContactId contactId, SessionId sessionId) { + this.contactId = contactId; + this.sessionId = sessionId; + } + + public ContactId getContactId() { + return contactId; + } + + public SessionId getSessionId() { + return sessionId; + } +} diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java index e713fb8bd8e3ea2a81a46e6ff1d632da5db53e6d..ad6e005f6e69426056e8cdac760f4bc1746ecd25 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java @@ -5,6 +5,7 @@ import org.briarproject.api.ProtocolEngine; import org.briarproject.api.contact.ContactId; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.event.Event; +import org.briarproject.api.event.IntroductionAbortedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.introduction.IntroduceeAction; @@ -25,6 +26,8 @@ import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ABORT; import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ACCEPT; import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_DECLINE; import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_ABORT; +import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_ACCEPT; +import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_DECLINE; import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_ACK; import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REMOTE_RESPONSE; import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; @@ -92,7 +95,7 @@ public class IntroduceeEngine currentState.name()); } if (currentState == ERROR) return noUpdate(localState); - else abortSession(currentState, localState); + else return abortSession(currentState, localState); } if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) { @@ -194,6 +197,11 @@ public class IntroduceeEngine } // we are done (probably declined response) and ignore this message else if (currentState == FINISHED) { + if(action == REMOTE_DECLINE || action == REMOTE_ACCEPT) { + // record response data, + // so we later know which response was ours + addResponseData(localState, msg); + } return noUpdate(localState); } // this should not happen @@ -355,8 +363,14 @@ public class IntroduceeEngine msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); List<BdfDictionary> messages = Collections.singletonList(msg); - // TODO inform about protocol abort via new Event? - List<Event> events = Collections.emptyList(); + + // send abort event + ContactId contactId = + new ContactId(localState.getLong(CONTACT_ID_1).intValue()); + SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + Event event = new IntroductionAbortedEvent(contactId, sessionId); + List<Event> events = Collections.singletonList(event); + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, localState, messages, events); } diff --git a/briar-core/src/org/briarproject/introduction/IntroducerEngine.java b/briar-core/src/org/briarproject/introduction/IntroducerEngine.java index 796d528f1d09b5fb9b49963fef8a732efbcb25de..6beacb934456e2fecda959f7e1c627a7baa4ea93 100644 --- a/briar-core/src/org/briarproject/introduction/IntroducerEngine.java +++ b/briar-core/src/org/briarproject/introduction/IntroducerEngine.java @@ -5,6 +5,7 @@ import org.briarproject.api.ProtocolEngine; import org.briarproject.api.contact.ContactId; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.event.Event; +import org.briarproject.api.event.IntroductionAbortedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.introduction.IntroducerAction; @@ -56,7 +57,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_1 import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_2; import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; import static org.briarproject.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.api.introduction.IntroductionConstants.TIME; import static org.briarproject.api.introduction.IntroductionConstants.TYPE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; @@ -288,11 +288,11 @@ public class IntroducerEngine ContactId contactId = new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - AuthorId authorId = new AuthorId(localState.getRaw(AUTHOR_ID_1, new byte[32])); // TODO remove byte[] + AuthorId authorId = new AuthorId(localState.getRaw(AUTHOR_ID_1)); if (Arrays.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) { contactId = new ContactId(localState.getLong(CONTACT_ID_2).intValue()); - authorId = new AuthorId(localState.getRaw(AUTHOR_ID_2, new byte[32])); // TODO remove byte[] + authorId = new AuthorId(localState.getRaw(AUTHOR_ID_2)); } SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); @@ -365,8 +365,19 @@ public class IntroducerEngine msg2.put(SESSION_ID, localState.getRaw(SESSION_ID)); msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); messages.add(msg2); - // TODO inform about protocol abort via new Event? - List<Event> events = Collections.emptyList(); + + // send one abort event per contact + List<Event> events = new ArrayList<Event>(2); + SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + ContactId contactId1 = + new ContactId(localState.getLong(CONTACT_ID_1).intValue()); + ContactId contactId2 = + new ContactId(localState.getLong(CONTACT_ID_2).intValue()); + Event event1 = new IntroductionAbortedEvent(contactId1, sessionId); + events.add(event1); + Event event2 = new IntroductionAbortedEvent(contactId2, sessionId); + events.add(event2); + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, localState, messages, events); } diff --git a/briar-core/src/org/briarproject/introduction/IntroductionModule.java b/briar-core/src/org/briarproject/introduction/IntroductionModule.java index 633d14c15bbf8c579b259eca808a19b65ee7bee1..9e51aca734a7bec5c1317e413593cf2285861b45 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionModule.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionModule.java @@ -3,13 +3,9 @@ package org.briarproject.introduction; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.MessageQueueManager; import org.briarproject.api.contact.ContactManager; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; -import org.briarproject.api.db.DatabaseComponent; -import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.lifecycle.LifecycleManager; -import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.system.Clock; import javax.inject.Inject; @@ -18,17 +14,19 @@ import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +import static org.briarproject.api.sync.ValidationManager.MessageValidator; + @Module public class IntroductionModule { public static class EagerSingletons { @Inject IntroductionManager introductionManager; - @Inject IntroductionValidator introductionValidator; + @Inject MessageValidator introductionValidator; } @Provides @Singleton - IntroductionValidator getValidator(MessageQueueManager messageQueueManager, + MessageValidator getValidator(MessageQueueManager messageQueueManager, IntroductionManager introductionManager, MetadataEncoder metadataEncoder, ClientHelper clientHelper, Clock clock) {