diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java index e516134467886402082cb7c16d0f16d1dcddb422..bfbdf5c8c79d66a02f8695c2d40c685e59c1555b 100644 --- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java @@ -49,6 +49,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.TimeoutException; import java.util.logging.Logger; @@ -65,15 +66,15 @@ import static org.junit.Assert.assertTrue; public class ForumSharingIntegrationTest extends BriarTestCase { - LifecycleManager lifecycleManager0, lifecycleManager1; - SyncSessionFactory sync0, sync1; - ForumManager forumManager0, forumManager1; - ContactManager contactManager0, contactManager1; - ContactId contactId0, contactId1; - IdentityManager identityManager0, identityManager1; - LocalAuthor author0, author1; + LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2; + SyncSessionFactory sync0, sync1, sync2; + ForumManager forumManager0, forumManager1, forumManager2; + ContactManager contactManager0, contactManager1, contactManager2; + ContactId contactId0, contactId2, contactId1, contactId21; + IdentityManager identityManager0, identityManager1, identityManager2; + LocalAuthor author0, author1, author2; Forum forum0; - SharerListener listener0; + SharerListener listener0, listener2; InviteeListener listener1; @Inject @@ -84,6 +85,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { // objects accessed from background threads need to be volatile private volatile ForumSharingManager forumSharingManager0; private volatile ForumSharingManager forumSharingManager1; + private volatile ForumSharingManager forumSharingManager2; private volatile Waiter eventWaiter; private volatile Waiter msgWaiter; @@ -92,11 +94,12 @@ public class ForumSharingIntegrationTest extends BriarTestCase { private final int TIMEOUT = 15000; private final String SHARER = "Sharer"; private final String INVITEE = "Invitee"; + private final String SHARER2 = "Sharer2"; private static final Logger LOG = Logger.getLogger(ForumSharingIntegrationTest.class.getName()); - private ForumSharingIntegrationTestComponent t0, t1; + private ForumSharingIntegrationTestComponent t0, t1, t2; @Rule public ExpectedException thrown=ExpectedException.none(); @@ -117,17 +120,26 @@ public class ForumSharingIntegrationTest extends BriarTestCase { t1 = DaggerForumSharingIntegrationTestComponent.builder() .testDatabaseModule(new TestDatabaseModule(t1Dir)).build(); injectEagerSingletons(t1); + File t2Dir = new File(testDir, SHARER2); + t2 = DaggerForumSharingIntegrationTestComponent.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(); forumManager0 = t0.getForumManager(); forumManager1 = t1.getForumManager(); + forumManager2 = t2.getForumManager(); forumSharingManager0 = t0.getForumSharingManager(); forumSharingManager1 = t1.getForumSharingManager(); + forumSharingManager2 = t2.getForumSharingManager(); sync0 = t0.getSyncSessionFactory(); sync1 = t1.getSyncSessionFactory(); + sync2 = t2.getSyncSessionFactory(); // initialize waiters fresh for each test eventWaiter = new Waiter(); @@ -474,7 +486,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase { assertEquals(1, forumManager1.getForums().size()); // invitee now shares same forum back - forumSharingManager1.sendForumInvitation(forum0.getId(), contactId0, + forumSharingManager1.sendForumInvitation(forum0.getId(), + contactId0, "I am re-sharing this forum with you."); // sync re-share invitation @@ -526,7 +539,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase { // contacts now remove each other contactManager0.removeContact(contactId1); + contactManager2.removeContact(contactId21); contactManager1.removeContact(contactId0); + contactManager1.removeContact(contactId2); // make sure sharer does share the forum with nobody now assertEquals(0, @@ -572,6 +587,75 @@ public class ForumSharingIntegrationTest extends BriarTestCase { } } + @Test + public void testTwoContactsShareSameForum() throws Exception { + startLifecycles(); + try { + // initialize + addDefaultIdentities(); + addDefaultContacts(); + addForumForSharer(); + + // second sharer adds the same forum + DatabaseComponent db2 = t2.getDatabaseComponent(); + Transaction txn = db2.startTransaction(false); + db2.addGroup(txn, forum0.getGroup()); + txn.setComplete(); + db2.endTransaction(txn); + + // add listeners + listener0 = new SharerListener(); + t0.getEventBus().addListener(listener0); + listener1 = new InviteeListener(true, false); + t1.getEventBus().addListener(listener1); + listener2 = new SharerListener(); + t2.getEventBus().addListener(listener2); + + // send invitation + forumSharingManager0 + .sendForumInvitation(forum0.getId(), contactId1, "Hi!"); + // sync first request message + syncToInvitee(); + + // second sharer sends invitation for same forum + forumSharingManager2 + .sendForumInvitation(forum0.getId(), contactId1, null); + // sync second request message + deliverMessage(sync2, contactId2, sync1, contactId1, + "Sharer2 to Invitee"); + + // make sure we have only one forum available + Collection<Forum> forums = + forumSharingManager1.getAvailableForums(); + assertEquals(1, forums.size()); + + // make sure both sharers actually share the forum + Collection<Contact> contacts = + forumSharingManager1.getSharedBy(forum0.getId()); + assertEquals(2, contacts.size()); + + // answer second request + Contact c2 = contactManager1.getContact(contactId2); + forumSharingManager1.respondToInvitation(forum0, c2, true); + // sync response + deliverMessage(sync1, contactId21, sync2, contactId2, + "Invitee to Sharer2"); + eventWaiter.await(TIMEOUT, 1); + assertTrue(listener2.responseReceived); + + // answer first request + Contact c0 = + contactManager1.getContact(contactId0); + forumSharingManager1.respondToInvitation(forum0, c0, true); + // sync response + syncToSharer(); + eventWaiter.await(TIMEOUT, 1); + assertTrue(listener0.responseReceived); + } finally { + stopLifecycles(); + } + } + @After public void tearDown() throws InterruptedException { @@ -609,7 +693,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase { requestReceived = true; Forum f = event.getForum(); try { - forumSharingManager0.respondToInvitation(f, true); + Contact c = contactManager0.getContact(contactId1); + forumSharingManager0.respondToInvitation(f, c, true); } catch (DbException ex) { eventWaiter.rethrow(ex); } finally { @@ -624,10 +709,15 @@ public class ForumSharingIntegrationTest extends BriarTestCase { public volatile boolean requestReceived = false; public volatile boolean responseReceived = false; - private final boolean accept; + private final boolean accept, answer; - InviteeListener(boolean accept) { + InviteeListener(boolean accept, boolean answer) { this.accept = accept; + this.answer = answer; + } + + InviteeListener(boolean accept) { + this(accept, true); } public void eventOccurred(Event e) { @@ -644,11 +734,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase { } else if (e instanceof ForumInvitationReceivedEvent) { ForumInvitationReceivedEvent event = (ForumInvitationReceivedEvent) e; - eventWaiter.assertEquals(contactId0, event.getContactId()); requestReceived = true; + if (!answer) return; Forum f = event.getForum(); try { - forumSharingManager1.respondToInvitation(f, accept); + Contact c = + contactManager1.getContact(event.getContactId()); + forumSharingManager1.respondToInvitation(f, c, accept); } catch (DbException ex) { eventWaiter.rethrow(ex); } finally { @@ -670,18 +762,23 @@ public class ForumSharingIntegrationTest extends BriarTestCase { // 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 defaultInit(boolean accept) throws DbException { @@ -700,6 +797,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase { TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), TestUtils.getRandomBytes(123)); identityManager1.addLocalAuthor(author1); + author2 = authorFactory.createLocalAuthor(SHARER2, + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + TestUtils.getRandomBytes(123)); + identityManager2.addLocalAuthor(author2); } private void addDefaultContacts() throws DbException { @@ -708,11 +809,20 @@ public class ForumSharingIntegrationTest extends BriarTestCase { author0.getId(), master, clock.currentTimeMillis(), true, true ); - // invitee adds sharer back + // second sharer does the same + contactId21 = contactManager2.addContact(author1, + author2.getId(), master, clock.currentTimeMillis(), true, + true + ); + // invitee adds sharers back contactId0 = contactManager1.addContact(author0, author1.getId(), master, clock.currentTimeMillis(), true, true ); + contactId2 = contactManager1.addContact(author2, + author1.getId(), master, clock.currentTimeMillis(), true, + true + ); } private void addForumForSharer() throws DbException { @@ -725,6 +835,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase { t0.getEventBus().addListener(listener0); listener1 = new InviteeListener(accept); t1.getEventBus().addListener(listener1); + listener2 = new SharerListener(); + t2.getEventBus().addListener(listener2); } private void syncToInvitee() throws IOException, TimeoutException { diff --git a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java b/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java index bcf39e8360766b1e722912f3a6c7faec994f4ceb..ed05d2863e38de8d947f730094691ef20954ba89 100644 --- a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java +++ b/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java @@ -153,7 +153,7 @@ public class AvailableForumsActivity extends BriarActivity @Override public void onItemClick(AvailableForumsItem item, boolean accept) { - respondToInvitation(item.getForum(), accept); + respondToInvitation(item, accept); // show toast int res = R.string.forum_declined_toast; @@ -161,12 +161,16 @@ public class AvailableForumsActivity extends BriarActivity Toast.makeText(this, res, LENGTH_SHORT).show(); } - private void respondToInvitation(final Forum f, final boolean accept) { - runOnDbThread(new Runnable() { + private void respondToInvitation(final AvailableForumsItem item, + final boolean accept) { + briarController.runOnDbThread(new Runnable() { @Override public void run() { try { - forumSharingManager.respondToInvitation(f, accept); + Forum f = item.getForum(); + for (Contact c : item.getContacts()) { + forumSharingManager.respondToInvitation(f, c, accept); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java index 9f4cc87b6620aebb593a32f3f2e9e3655207aa9a..d20f427f6dc95dea0b23bc3b6bbf6a3e35fb928f 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java @@ -24,7 +24,8 @@ public interface ForumSharingManager { /** * Responds to a pending forum invitation */ - void respondToInvitation(Forum f, boolean accept) throws DbException; + void respondToInvitation(Forum f, Contact c, boolean accept) + throws DbException; /** * Returns all forum sharing messages sent by the Contact diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java index 99cdce517842fcceb6ff98bc6b22b82921713388..01572fbcb6ec221dc7fb933488f0d84e341669d8 100644 --- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java @@ -283,13 +283,14 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook } @Override - public void respondToInvitation(Forum f, boolean accept) + public void respondToInvitation(Forum f, Contact c, boolean accept) throws DbException { Transaction txn = db.startTransaction(false); try { // find session state based on forum - InviteeSessionState localState = getSessionStateForResponse(txn, f); + InviteeSessionState localState = + getSessionStateForResponse(txn, f, c); // define action InviteeSessionState.Action localAction; @@ -562,17 +563,15 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook "More than one session state found for message with session ID " + Arrays.hashCode(sessionId.getBytes())); } - - for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { - return fromBdfDictionary(m.getValue()); - } - - if (warn && LOG.isLoggable(WARNING)) { - LOG.warning( - "No session state found for message with session ID " + - Arrays.hashCode(sessionId.getBytes())); + if (map.isEmpty()) { + if (warn && LOG.isLoggable(WARNING)) { + LOG.warning( + "No session state found for message with session ID " + + Arrays.hashCode(sessionId.getBytes())); + } + throw new FormatException(); } - throw new FormatException(); + return fromBdfDictionary(map.values().iterator().next()); } } @@ -590,11 +589,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook } private InviteeSessionState getSessionStateForResponse(Transaction txn, - Forum f) throws DbException, FormatException { + Forum f, Contact c) throws DbException, FormatException { // query for invitee states for that forum in state await response BdfDictionary query = BdfDictionary.of( new BdfEntry(IS_SHARER, false), + new BdfEntry(CONTACT_ID, c.getId().getInt()), new BdfEntry(FORUM_ID, f.getId()), new BdfEntry(STATE, InviteeSessionState.State.AWAIT_LOCAL_RESPONSE @@ -604,26 +604,24 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook Map<MessageId, BdfDictionary> map = clientHelper .getMessageMetadataAsDictionary(txn, localGroup.getId(), query); - // TODO we might have been invited to the same forum from several people - // in different sessions, so we need to handle this (#391) if (map.size() > 1 && LOG.isLoggable(WARNING)) { LOG.warning( "More than one session state found for forum with ID " + Arrays.hashCode(f.getId().getBytes()) + - " in state AWAIT_LOCAL_RESPONSE"); + " in state AWAIT_LOCAL_RESPONSE for contact " + + c.getAuthor().getName()); } - - for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { - return (InviteeSessionState) fromBdfDictionary(m.getValue()); - } - - if (LOG.isLoggable(WARNING)) { - LOG.warning( - "No session state found for forum with ID " + - Arrays.hashCode(f.getId().getBytes()) + - " in state AWAIT_LOCAL_RESPONSE"); + if (map.isEmpty()) { + if (LOG.isLoggable(WARNING)) { + LOG.warning( + "No session state found for forum with ID " + + Arrays.hashCode(f.getId().getBytes()) + + " in state AWAIT_LOCAL_RESPONSE"); + } + throw new DbException(); } - throw new DbException(); + return (InviteeSessionState) fromBdfDictionary( + map.values().iterator().next()); } private ForumSharingSessionState getSessionStateForLeaving(Transaction txn,