diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 6ff778d86d62aa63fe035f7a0f434455f2dea65f..815c8de62e1100d526fb247df3e923631d250429 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -8,6 +8,7 @@ apply plugin: 'de.undercouch.download' repositories { jcenter() + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } dependencies { @@ -44,6 +45,18 @@ dependencies { compile 'com.google.zxing:core:3.2.1' apt 'com.google.dagger:dagger-compiler:2.0.2' provided 'javax.annotation:jsr250-api:1.0' + + testCompile 'junit:junit:4.12' + testCompile 'net.jodah:concurrentunit:0.4.2' + testApt 'com.google.dagger:dagger-compiler:2.4' + testCompile project(path: ':briar-tests') + testCompile 'org.robolectric:robolectric:3.1-SNAPSHOT' + testCompile 'org.mockito:mockito-core:1.10.19' + + androidTestCompile 'org.robolectric:robolectric:3.0' + androidTestCompile 'org.mockito:mockito-core:1.10.19' + androidTestApt 'com.google.dagger:dagger-compiler:2.4' + androidTestCompile 'junit:junit:4.12' } dependencyVerification { @@ -81,7 +94,19 @@ android { } // Move the tests to tests/java, tests/res, etc... - instrumentTest.setRoot('tests') + //instrumentTest.setRoot('tests') +// unitTest.setRoot('tests') + + androidTest.setRoot('androidTest') + androidTest { + java.srcDirs = ['androidTest/java'] + } + + test.setRoot('test') + test { + java.srcDirs = ['test/java'] + } + // Move the build types to build-types/<type> // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... diff --git a/briar-android/proguard-rules.txt b/briar-android/proguard-rules.txt index 48d785a430ba139f85a88a0a71396a4b5b5671fc..df28bc5075f62e695960851810dc023df16c815a 100644 --- a/briar-android/proguard-rules.txt +++ b/briar-android/proguard-rules.txt @@ -48,6 +48,9 @@ -keep class roboguice.** { *; } -keep class dagger.** { *; } -keep class com.google.** { *; } +-keep class javax.** { *; } +-keep class org.eclipse.** { *; } +-keep class com.squareup.** { *; } -dontwarn org.h2.** -dontnote org.h2.** diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java index 33accc97024d629403cd73066a47f8244d17a3e8..70d3b00ab50423e47010f09743806733e548322c 100644 --- a/briar-android/src/org/briarproject/android/ActivityModule.java +++ b/briar-android/src/org/briarproject/android/ActivityModule.java @@ -54,34 +54,33 @@ public class ActivityModule { @ActivityScope @Provides - SetupController provideSetupController( - SetupControllerImp setupControllerImp) { - return setupControllerImp; + protected SetupController provideSetupController() { + return new SetupControllerImp(); } @ActivityScope @Provides - ConfigController provideConfigController( + protected ConfigController provideConfigController( ConfigControllerImp configControllerImp) { return configControllerImp; } @ActivityScope @Provides - SharedPreferences provideSharedPreferences(Activity activity) { + protected SharedPreferences provideSharedPreferences(Activity activity) { return activity.getSharedPreferences("db", Context.MODE_PRIVATE); } @ActivityScope @Provides - PasswordController providePasswordController( + protected PasswordController providePasswordController( PasswordControllerImp passwordControllerImp) { return passwordControllerImp; } @ActivityScope @Provides - BriarController provideBriarController( + protected BriarController provideBriarController( BriarControllerImp briarControllerImp) { activity.addLifecycleController(briarControllerImp); return briarControllerImp; @@ -89,7 +88,7 @@ public class ActivityModule { @ActivityScope @Provides - NavDrawerController provideNavDrawerController( + protected NavDrawerController provideNavDrawerController( NavDrawerControllerImp navDrawerControllerImp) { activity.addLifecycleController(navDrawerControllerImp); if (activity instanceof TransportStateListener) { @@ -101,7 +100,7 @@ public class ActivityModule { @ActivityScope @Provides - BriarServiceConnection provideBriarServiceConnection() { + protected BriarServiceConnection provideBriarServiceConnection() { return new BriarServiceConnection(); } diff --git a/briar-android/src/org/briarproject/android/BaseActivity.java b/briar-android/src/org/briarproject/android/BaseActivity.java index e14eb29633248662d037fdae8d640b66d8d4ad1f..fa2e68d6b8e57d60d3f7d693a5b14fb0e7a72275 100644 --- a/briar-android/src/org/briarproject/android/BaseActivity.java +++ b/briar-android/src/org/briarproject/android/BaseActivity.java @@ -38,7 +38,7 @@ public abstract class BaseActivity extends AppCompatActivity { activityComponent = DaggerActivityComponent.builder() .androidComponent(applicationComponent) - .activityModule(new ActivityModule(this)) + .activityModule(getActivityModule()) .build(); injectActivity(activityComponent); @@ -48,6 +48,11 @@ public abstract class BaseActivity extends AppCompatActivity { } } + // This exists to make test overrides easier + protected ActivityModule getActivityModule() { + return new ActivityModule(this); + } + @Override protected void onResume() { super.onResume(); diff --git a/briar-android/src/org/briarproject/android/SetupActivity.java b/briar-android/src/org/briarproject/android/SetupActivity.java index f54d1e861ac51342aacbdf84388844d9536b32c2..c3cb01cb31c1e6588835ff38cfdba9e983f7c41f 100644 --- a/briar-android/src/org/briarproject/android/SetupActivity.java +++ b/briar-android/src/org/briarproject/android/SetupActivity.java @@ -27,8 +27,6 @@ import javax.inject.Inject; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS; import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK; import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; @@ -66,7 +64,7 @@ public class SetupActivity extends BaseActivity implements OnClickListener, createAccountButton = (Button) findViewById(R.id.create_account); progress = (ProgressBar) findViewById(R.id.progress_wheel); - if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); +// if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); TextWatcher tw = new TextWatcher() { @Override @@ -125,6 +123,7 @@ public class SetupActivity extends BaseActivity implements OnClickListener, public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { hideSoftKeyboard(v); + return true; } diff --git a/briar-android/src/org/briarproject/system/AndroidSeedProvider.java b/briar-android/src/org/briarproject/system/AndroidSeedProvider.java index c15e0b411a1efdb7e5805ea1e85acee4a4168592..749d1c68bc465c676eaea169a15557b50356d177 100644 --- a/briar-android/src/org/briarproject/system/AndroidSeedProvider.java +++ b/briar-android/src/org/briarproject/system/AndroidSeedProvider.java @@ -8,6 +8,7 @@ import android.provider.Settings; import java.io.DataOutputStream; import java.io.IOException; +import java.util.logging.Logger; import javax.inject.Inject; @@ -15,6 +16,9 @@ import static android.provider.Settings.Secure.ANDROID_ID; class AndroidSeedProvider extends LinuxSeedProvider { + private static final Logger LOG = + Logger.getLogger(LinuxSeedProvider.class.getName()); + private final Context appContext; @Inject diff --git a/briar-android/test/java/briarproject/IntroductionIntegrationTest.java b/briar-android/test/java/briarproject/IntroductionIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e92b545461dc428f5baeacf415e97b6c434a331c --- /dev/null +++ b/briar-android/test/java/briarproject/IntroductionIntegrationTest.java @@ -0,0 +1,859 @@ +package briarproject; + +import net.jodah.concurrentunit.Waiter; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestDatabaseModule; +import org.briarproject.TestUtils; +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.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(); + } + } + + // 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 exception) { + eventWaiter.rethrow(exception); + } catch (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/test/java/briarproject/IntroductionIntegrationTestComponent.java b/briar-android/test/java/briarproject/IntroductionIntegrationTestComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..d86b50d7c9fc0421b51f628f024a0173e3e2a48f --- /dev/null +++ b/briar-android/test/java/briarproject/IntroductionIntegrationTestComponent.java @@ -0,0 +1,80 @@ +package briarproject; + +import org.briarproject.TestDatabaseModule; +import org.briarproject.TestPluginsModule; +import org.briarproject.TestSystemModule; +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-android/test/java/briarproject/MessageSizeIntegrationTest.java b/briar-android/test/java/briarproject/MessageSizeIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d46c26f434423879ff9e8b1662b6198a37fddffa --- /dev/null +++ b/briar-android/test/java/briarproject/MessageSizeIntegrationTest.java @@ -0,0 +1,90 @@ +package briarproject; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.UniqueId; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.PrivateKey; +import org.briarproject.api.forum.ForumConstants; +import org.briarproject.api.forum.ForumPost; +import org.briarproject.api.forum.ForumPostFactory; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.messaging.MessagingConstants; +import org.briarproject.api.messaging.PrivateMessage; +import org.briarproject.api.messaging.PrivateMessageFactory; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; +import org.junit.Test; + +import javax.inject.Inject; + +import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH; +import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH; +import static org.junit.Assert.assertTrue; + +public class MessageSizeIntegrationTest extends BriarTestCase { + + @Inject + CryptoComponent crypto; + @Inject + AuthorFactory authorFactory; + @Inject + PrivateMessageFactory privateMessageFactory; + @Inject + ForumPostFactory forumPostFactory; + + public MessageSizeIntegrationTest() throws Exception { + MessageSizeIntegrationTestComponent component = + DaggerMessageSizeIntegrationTestComponent.builder().build(); + component.inject(this); + } + + @Test + public void testPrivateMessageFitsIntoPacket() throws Exception { + // Create a maximum-length private message + GroupId groupId = new GroupId(TestUtils.getRandomId()); + long timestamp = Long.MAX_VALUE; + MessageId parent = new MessageId(TestUtils.getRandomId()); + String contentType = TestUtils.getRandomString( + MessagingConstants.MAX_CONTENT_TYPE_LENGTH); + byte[] body = new byte[MAX_PRIVATE_MESSAGE_BODY_LENGTH]; + PrivateMessage message = privateMessageFactory.createPrivateMessage( + groupId, timestamp, parent, contentType, body); + // Check the size of the serialised message + int length = message.getMessage().getRaw().length; + assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH + + MessagingConstants.MAX_CONTENT_TYPE_LENGTH + + MAX_PRIVATE_MESSAGE_BODY_LENGTH); + assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH); + } + + @Test + public void testForumPostFitsIntoPacket() throws Exception { + // Create a maximum-length author + String authorName = TestUtils.getRandomString( + MAX_AUTHOR_NAME_LENGTH); + byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH]; + Author author = authorFactory.createAuthor(authorName, authorPublic); + // Create a maximum-length forum post + GroupId groupId = new GroupId(TestUtils.getRandomId()); + long timestamp = Long.MAX_VALUE; + MessageId parent = new MessageId(TestUtils.getRandomId()); + String contentType = TestUtils.getRandomString( + ForumConstants.MAX_CONTENT_TYPE_LENGTH); + byte[] body = new byte[MAX_FORUM_POST_BODY_LENGTH]; + PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate(); + ForumPost post = forumPostFactory.createPseudonymousPost(groupId, + timestamp, parent, author, contentType, body, privateKey); + // Check the size of the serialised message + int length = post.getMessage().getRaw().length; + assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH + + MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH + + ForumConstants.MAX_CONTENT_TYPE_LENGTH + + MAX_FORUM_POST_BODY_LENGTH); + assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH); + } +} diff --git a/briar-android/test/java/briarproject/MessageSizeIntegrationTestComponent.java b/briar-android/test/java/briarproject/MessageSizeIntegrationTestComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..9f112fb6b90fba0275edf85e81494ea37116284f --- /dev/null +++ b/briar-android/test/java/briarproject/MessageSizeIntegrationTestComponent.java @@ -0,0 +1,37 @@ +package briarproject; + +import org.briarproject.TestDatabaseModule; +import org.briarproject.TestLifecycleModule; +import org.briarproject.TestSystemModule; +import org.briarproject.clients.ClientsModule; +import org.briarproject.crypto.CryptoModule; +import org.briarproject.data.DataModule; +import org.briarproject.db.DatabaseModule; +import org.briarproject.event.EventModule; +import org.briarproject.forum.ForumModule; +import org.briarproject.identity.IdentityModule; +import org.briarproject.messaging.MessagingModule; +import org.briarproject.sync.SyncModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestDatabaseModule.class, + TestLifecycleModule.class, + TestSystemModule.class, + ClientsModule.class, + CryptoModule.class, + DataModule.class, + DatabaseModule.class, + EventModule.class, + ForumModule.class, + IdentityModule.class, + MessagingModule.class, + SyncModule.class +}) +public interface MessageSizeIntegrationTestComponent { + void inject(MessageSizeIntegrationTest testCase); +} diff --git a/briar-android/test/java/briarproject/SimplexMessagingIntegrationTest.java b/briar-android/test/java/briarproject/SimplexMessagingIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..53e7d03a3a6b9bf59f61be5d568823b1b927e3c9 --- /dev/null +++ b/briar-android/test/java/briarproject/SimplexMessagingIntegrationTest.java @@ -0,0 +1,193 @@ +package briarproject; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestDatabaseModule; +import org.briarproject.TestUtils; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventListener; +import org.briarproject.api.event.MessageAddedEvent; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.identity.LocalAuthor; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.messaging.MessagingManager; +import org.briarproject.api.messaging.PrivateMessage; +import org.briarproject.api.messaging.PrivateMessageFactory; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.SyncSession; +import org.briarproject.api.sync.SyncSessionFactory; +import org.briarproject.api.transport.KeyManager; +import org.briarproject.api.transport.StreamContext; +import org.briarproject.api.transport.StreamReaderFactory; +import org.briarproject.api.transport.StreamWriterFactory; +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.InputStream; +import java.io.OutputStream; + +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.transport.TransportConstants.TAG_LENGTH; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class SimplexMessagingIntegrationTest extends BriarTestCase { + + private final File testDir = TestUtils.getTestDirectory(); + private final File aliceDir = new File(testDir, "alice"); + private final File bobDir = new File(testDir, "bob"); + private final SecretKey master = TestUtils.getSecretKey(); + private final long timestamp = System.currentTimeMillis(); + private final AuthorId aliceId = new AuthorId(TestUtils.getRandomId()); + private final AuthorId bobId = new AuthorId(TestUtils.getRandomId()); + + private SimplexMessagingIntegrationTestComponent alice, bob; + + @Before + public void setUp() { + assertTrue(testDir.mkdirs()); + alice = DaggerSimplexMessagingIntegrationTestComponent.builder() + .testDatabaseModule(new TestDatabaseModule(aliceDir)).build(); + bob = DaggerSimplexMessagingIntegrationTestComponent.builder() + .testDatabaseModule(new TestDatabaseModule(bobDir)).build(); + } + + @Test + public void testWriteAndRead() throws Exception { + read(write()); + } + + private byte[] write() throws Exception { + // Instantiate Alice's services + LifecycleManager lifecycleManager = alice.getLifecycleManager(); + IdentityManager identityManager = alice.getIdentityManager(); + ContactManager contactManager = alice.getContactManager(); + MessagingManager messagingManager = alice.getMessagingManager(); + KeyManager keyManager = alice.getKeyManager(); + PrivateMessageFactory privateMessageFactory = + alice.getPrivateMessageFactory(); + StreamWriterFactory streamWriterFactory = + alice.getStreamWriterFactory(); + SyncSessionFactory syncSessionFactory = alice.getSyncSessionFactory(); + + // Start the lifecycle manager + lifecycleManager.startServices(); + lifecycleManager.waitForStartup(); + // Add an identity for Alice + LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice", + new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp); + identityManager.addLocalAuthor(aliceAuthor); + // Add Bob as a contact + Author bobAuthor = new Author(bobId, "Bob", + new byte[MAX_PUBLIC_KEY_LENGTH]); + ContactId contactId = contactManager.addContact(bobAuthor, aliceId, + master, timestamp, true, true); + + // Send Bob a message + GroupId groupId = messagingManager.getConversationId(contactId); + byte[] body = "Hi Bob!".getBytes("UTF-8"); + PrivateMessage message = privateMessageFactory.createPrivateMessage( + groupId, timestamp, null, "text/plain", body); + messagingManager.addLocalMessage(message); + // Get a stream context + StreamContext ctx = keyManager.getStreamContext(contactId, + TRANSPORT_ID); + assertNotNull(ctx); + // Create a stream writer + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream streamWriter = streamWriterFactory.createStreamWriter( + out, ctx); + // Create an outgoing sync session + SyncSession session = syncSessionFactory.createSimplexOutgoingSession( + contactId, MAX_LATENCY, streamWriter); + // Write whatever needs to be written + session.run(); + streamWriter.close(); + + // Clean up + lifecycleManager.stopServices(); + lifecycleManager.waitForShutdown(); + + // Return the contents of the stream + return out.toByteArray(); + } + + private void read(byte[] stream) throws Exception { + // Instantiate Bob's services + LifecycleManager lifecycleManager = bob.getLifecycleManager(); + IdentityManager identityManager = bob.getIdentityManager(); + ContactManager contactManager = bob.getContactManager(); + KeyManager keyManager = bob.getKeyManager(); + StreamReaderFactory streamReaderFactory = bob.getStreamReaderFactory(); + SyncSessionFactory syncSessionFactory = bob.getSyncSessionFactory(); + // Bob needs a MessagingManager even though we're not using it directly + bob.getMessagingManager(); + + // Start the lifecyle manager + lifecycleManager.startServices(); + lifecycleManager.waitForStartup(); + // Add an identity for Bob + LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob", + new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp); + identityManager.addLocalAuthor(bobAuthor); + // Add Alice as a contact + Author aliceAuthor = new Author(aliceId, "Alice", + new byte[MAX_PUBLIC_KEY_LENGTH]); + ContactId contactId = contactManager.addContact(aliceAuthor, bobId, + master, timestamp, false, true); + + // Set up an event listener + MessageListener listener = new MessageListener(); + bob.getEventBus().addListener(listener); + // Read and recognise the tag + ByteArrayInputStream in = new ByteArrayInputStream(stream); + byte[] tag = new byte[TAG_LENGTH]; + int read = in.read(tag); + assertEquals(tag.length, read); + StreamContext ctx = keyManager.getStreamContext(TRANSPORT_ID, tag); + assertNotNull(ctx); + // Create a stream reader + InputStream streamReader = streamReaderFactory.createStreamReader( + in, ctx); + // Create an incoming sync session + SyncSession session = syncSessionFactory.createIncomingSession( + contactId, streamReader); + // No messages should have been added yet + assertFalse(listener.messageAdded); + // Read whatever needs to be read + session.run(); + streamReader.close(); + // The private message from Alice should have been added + assertTrue(listener.messageAdded); + + // Clean up + lifecycleManager.stopServices(); + lifecycleManager.waitForShutdown(); + } + + @After + public void tearDown() { + TestUtils.deleteTestDirectory(testDir); + } + + private static class MessageListener implements EventListener { + + private volatile boolean messageAdded = false; + + public void eventOccurred(Event e) { + if (e instanceof MessageAddedEvent) messageAdded = true; + } + } +} diff --git a/briar-android/test/java/briarproject/SimplexMessagingIntegrationTestComponent.java b/briar-android/test/java/briarproject/SimplexMessagingIntegrationTestComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..7691b81924be4882a19a2e26500345cd722e2436 --- /dev/null +++ b/briar-android/test/java/briarproject/SimplexMessagingIntegrationTestComponent.java @@ -0,0 +1,74 @@ +package briarproject; + +import org.briarproject.TestDatabaseModule; +import org.briarproject.TestPluginsModule; +import org.briarproject.TestSystemModule; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.messaging.MessagingManager; +import org.briarproject.api.messaging.PrivateMessageFactory; +import org.briarproject.api.sync.SyncSessionFactory; +import org.briarproject.api.transport.KeyManager; +import org.briarproject.api.transport.StreamReaderFactory; +import org.briarproject.api.transport.StreamWriterFactory; +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.lifecycle.LifecycleModule; +import org.briarproject.messaging.MessagingModule; +import org.briarproject.plugins.PluginsModule; +import org.briarproject.sync.SyncModule; +import org.briarproject.transport.TransportModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestDatabaseModule.class, + TestPluginsModule.class, + TestSystemModule.class, + ClientsModule.class, + ContactModule.class, + CryptoModule.class, + DataModule.class, + DatabaseModule.class, + EventModule.class, + IdentityModule.class, + LifecycleModule.class, + MessagingModule.class, + PluginsModule.class, + SyncModule.class, + TransportModule.class +}) +public interface SimplexMessagingIntegrationTestComponent { + + void inject(SimplexMessagingIntegrationTest testCase); + + LifecycleManager getLifecycleManager(); + + IdentityManager getIdentityManager(); + + ContactManager getContactManager(); + + MessagingManager getMessagingManager(); + + KeyManager getKeyManager(); + + PrivateMessageFactory getPrivateMessageFactory(); + + EventBus getEventBus(); + + StreamWriterFactory getStreamWriterFactory(); + + StreamReaderFactory getStreamReaderFactory(); + + SyncSessionFactory getSyncSessionFactory(); +} diff --git a/briar-android/test/java/briarproject/SyncIntegrationTest.java b/briar-android/test/java/briarproject/SyncIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8d5dabfc33fe352d4af2511d0d3bd4f3c80a34b1 --- /dev/null +++ b/briar-android/test/java/briarproject/SyncIntegrationTest.java @@ -0,0 +1,169 @@ +package briarproject; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.TransportId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.sync.Ack; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupFactory; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageFactory; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.sync.Offer; +import org.briarproject.api.sync.PacketReader; +import org.briarproject.api.sync.PacketReaderFactory; +import org.briarproject.api.sync.PacketWriter; +import org.briarproject.api.sync.PacketWriterFactory; +import org.briarproject.api.sync.Request; +import org.briarproject.api.transport.StreamContext; +import org.briarproject.api.transport.StreamReaderFactory; +import org.briarproject.api.transport.StreamWriterFactory; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collection; + +import javax.inject.Inject; + +import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; +import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SyncIntegrationTest extends BriarTestCase { + + @Inject + GroupFactory groupFactory; + @Inject + MessageFactory messageFactory; + @Inject + StreamReaderFactory streamReaderFactory; + @Inject + StreamWriterFactory streamWriterFactory; + @Inject + PacketReaderFactory packetReaderFactory; + @Inject + PacketWriterFactory packetWriterFactory; + @Inject + CryptoComponent crypto; + + private final ContactId contactId; + private final TransportId transportId; + private final SecretKey tagKey, headerKey; + private final long streamNumber; + private final Message message, message1; + private final Collection<MessageId> messageIds; + + public SyncIntegrationTest() throws Exception { + + SyncIntegrationTestComponent component = + DaggerSyncIntegrationTestComponent.builder().build(); + component.inject(this); + + contactId = new ContactId(234); + transportId = new TransportId("id"); + // Create the transport keys + tagKey = TestUtils.getSecretKey(); + headerKey = TestUtils.getSecretKey(); + streamNumber = 123; + // Create a group + ClientId clientId = new ClientId(TestUtils.getRandomId()); + byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH]; + Group group = groupFactory.createGroup(clientId, descriptor); + // Add two messages to the group + long timestamp = System.currentTimeMillis(); + byte[] body = "Hello world".getBytes("UTF-8"); + message = messageFactory.createMessage(group.getId(), timestamp, body); + message1 = messageFactory.createMessage(group.getId(), timestamp, body); + messageIds = Arrays.asList(message.getId(), message1.getId()); + } + + @Test + public void testWriteAndRead() throws Exception { + read(write()); + } + + private byte[] write() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StreamContext ctx = new StreamContext(contactId, transportId, tagKey, + headerKey, streamNumber); + OutputStream streamWriter = streamWriterFactory.createStreamWriter(out, + ctx); + PacketWriter packetWriter = packetWriterFactory.createPacketWriter( + streamWriter); + + packetWriter.writeAck(new Ack(messageIds)); + packetWriter.writeMessage(message.getRaw()); + packetWriter.writeMessage(message1.getRaw()); + packetWriter.writeOffer(new Offer(messageIds)); + packetWriter.writeRequest(new Request(messageIds)); + + streamWriter.flush(); + return out.toByteArray(); + } + + private void read(byte[] connectionData) throws Exception { + // Calculate the expected tag + byte[] expectedTag = new byte[TAG_LENGTH]; + crypto.encodeTag(expectedTag, tagKey, streamNumber); + + // Read the tag + InputStream in = new ByteArrayInputStream(connectionData); + byte[] tag = new byte[TAG_LENGTH]; + assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH)); + assertArrayEquals(expectedTag, tag); + + // Create the readers + StreamContext ctx = new StreamContext(contactId, transportId, tagKey, + headerKey, 0); + InputStream streamReader = streamReaderFactory.createStreamReader(in, + ctx); + PacketReader packetReader = packetReaderFactory.createPacketReader( + streamReader); + + // Read the ack + assertTrue(packetReader.hasAck()); + Ack a = packetReader.readAck(); + assertEquals(messageIds, a.getMessageIds()); + + // Read the messages + assertTrue(packetReader.hasMessage()); + Message m = packetReader.readMessage(); + checkMessageEquality(message, m); + assertTrue(packetReader.hasMessage()); + m = packetReader.readMessage(); + checkMessageEquality(message1, m); + assertFalse(packetReader.hasMessage()); + + // Read the offer + assertTrue(packetReader.hasOffer()); + Offer o = packetReader.readOffer(); + assertEquals(messageIds, o.getMessageIds()); + + // Read the request + assertTrue(packetReader.hasRequest()); + Request req = packetReader.readRequest(); + assertEquals(messageIds, req.getMessageIds()); + + in.close(); + } + + private void checkMessageEquality(Message m1, Message m2) { + assertArrayEquals(m1.getId().getBytes(), m2.getId().getBytes()); + assertArrayEquals(m1.getGroupId().getBytes(), + m2.getGroupId().getBytes()); + assertEquals(m1.getTimestamp(), m2.getTimestamp()); + assertEquals(m1.getLength(), m2.getLength()); + assertArrayEquals(m1.getRaw(), m2.getRaw()); + } +} diff --git a/briar-android/test/java/briarproject/SyncIntegrationTestComponent.java b/briar-android/test/java/briarproject/SyncIntegrationTestComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..ad3d4dfde1b5540991a2258a007ed7e7b84fd978 --- /dev/null +++ b/briar-android/test/java/briarproject/SyncIntegrationTestComponent.java @@ -0,0 +1,21 @@ +package briarproject; + +import org.briarproject.TestSystemModule; +import org.briarproject.crypto.CryptoModule; +import org.briarproject.sync.SyncModule; +import org.briarproject.transport.TransportModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestSystemModule.class, + CryptoModule.class, + SyncModule.class, + TransportModule.class +}) +public interface SyncIntegrationTestComponent { + void inject(SyncIntegrationTest testCase); +} diff --git a/briar-android/test/java/briarproject/activity/SetupActivityTest.java b/briar-android/test/java/briarproject/activity/SetupActivityTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a730cf91f83c3bb60383c0fd4344445e10ffac74 --- /dev/null +++ b/briar-android/test/java/briarproject/activity/SetupActivityTest.java @@ -0,0 +1,69 @@ +package briarproject.activity; + +import android.support.design.widget.TextInputLayout; +import android.widget.Button; +import android.widget.EditText; + +import org.briarproject.BuildConfig; +import org.briarproject.R; +import org.briarproject.android.ActivityModule; +import org.briarproject.android.SetupActivity; +import org.briarproject.android.util.StrengthMeter; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricGradleTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 22) +public class SetupActivityTest { + + private SetupActivity setupActivity; + TextInputLayout nicknameEntryWrapper; + TextInputLayout passwordEntryWrapper; + TextInputLayout passwordConfirmationWrapper; + EditText nicknameEntry; + EditText passwordEntry; + EditText passwordConfirmation; + StrengthMeter strengthMeter; + Button createAccountButton; + + class TestSetupActivity extends SetupActivity { + + @Override + protected ActivityModule getActivityModule() { + return super.getActivityModule(); + } + } + + @Before + public void setUp() { + setupActivity = Robolectric.setupActivity(SetupActivity.class); + nicknameEntryWrapper = (TextInputLayout) setupActivity + .findViewById(R.id.nickname_entry_wrapper); + passwordEntryWrapper = (TextInputLayout) setupActivity + .findViewById(R.id.password_entry_wrapper); + passwordConfirmationWrapper = (TextInputLayout) setupActivity + .findViewById(R.id.password_confirm_wrapper); + nicknameEntry = + (EditText) setupActivity.findViewById(R.id.nickname_entry); + passwordEntry = + (EditText) setupActivity.findViewById(R.id.password_entry); + passwordConfirmation = + (EditText) setupActivity.findViewById(R.id.password_confirm); + strengthMeter = + (StrengthMeter) setupActivity.findViewById(R.id.strength_meter); + createAccountButton = + (Button) setupActivity.findViewById(R.id.create_account); + + } + + @Test + public void test() { + + + + } +} diff --git a/briar-android/test/java/briarproject/activity/TestActivityModule.java b/briar-android/test/java/briarproject/activity/TestActivityModule.java new file mode 100644 index 0000000000000000000000000000000000000000..e5eba02f2950c960be5505884070564aff697a36 --- /dev/null +++ b/briar-android/test/java/briarproject/activity/TestActivityModule.java @@ -0,0 +1,19 @@ +package briarproject.activity; + +import org.briarproject.android.ActivityModule; +import org.briarproject.android.BaseActivity; + +public class TestActivityModule extends ActivityModule { + + public TestActivityModule(BaseActivity activity) { + super(activity); + } + + + /* + @Override + protected SetupController provideSetupController( + SetupControllerImp setupControllerImp) { + } + */ +} diff --git a/briar-android/test/java/briarproject/controller/SetupControllerTest.java b/briar-android/test/java/briarproject/controller/SetupControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8564a739395b4371a6b47b9becae8ad8b5b25144 --- /dev/null +++ b/briar-android/test/java/briarproject/controller/SetupControllerTest.java @@ -0,0 +1,17 @@ +package briarproject.controller; + +import org.briarproject.BriarTestCase; +import org.briarproject.android.controller.SetupController; +import org.junit.Before; + +import javax.inject.Inject; + +public class SetupControllerTest extends BriarTestCase { + + @Inject + SetupController setupController; + + @Before public void setUp() { + + } +} diff --git a/briar-core/src/org/briarproject/system/LinuxSeedProvider.java b/briar-core/src/org/briarproject/system/LinuxSeedProvider.java index 58b22d32adc29c43285a7c27aa6ce395b2e8af5d..730beef97af299b445c8179b39f9fc320dc4fc49 100644 --- a/briar-core/src/org/briarproject/system/LinuxSeedProvider.java +++ b/briar-core/src/org/briarproject/system/LinuxSeedProvider.java @@ -1,6 +1,6 @@ package org.briarproject.system; -import static java.util.logging.Level.WARNING; +import org.briarproject.api.system.SeedProvider; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -13,7 +13,7 @@ import java.util.Collections; import java.util.List; import java.util.logging.Logger; -import org.briarproject.api.system.SeedProvider; +import static java.util.logging.Level.WARNING; class LinuxSeedProvider implements SeedProvider { @@ -43,6 +43,8 @@ class LinuxSeedProvider implements SeedProvider { } catch (IOException e) { // On some devices /dev/urandom isn't writable - this isn't fatal if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } catch (NullPointerException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } // Read the seed from the pool try {