diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java index 10d4280f41dcdc6634c535bd02d0ccf56abae2b3..e37000febf79b9d576f0619dc753b4d16aaa1b6f 100644 --- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java @@ -11,6 +11,7 @@ import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.KeyPair; import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.data.BdfList; +import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.event.Event; @@ -20,6 +21,7 @@ import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.privategroup.GroupMember; import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessageFactory; import org.briarproject.api.privategroup.GroupMessageHeader; @@ -59,6 +61,10 @@ import javax.inject.Inject; import static org.briarproject.TestPluginsModule.MAX_LATENCY; import static org.briarproject.TestUtils.getRandomBytes; import static org.briarproject.api.identity.Author.Status.VERIFIED; +import static org.briarproject.api.privategroup.Visibility.INVISIBLE; +import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT; +import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US; +import static org.briarproject.api.privategroup.Visibility.VISIBLE; import static org.briarproject.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; @@ -69,13 +75,16 @@ import static org.junit.Assert.assertTrue; public class PrivateGroupManagerTest extends BriarIntegrationTest { - private LifecycleManager lifecycleManager0, lifecycleManager1; - private SyncSessionFactory sync0, sync1; - private PrivateGroupManager groupManager0, groupManager1; - private ContactManager contactManager0, contactManager1; - private ContactId contactId0, contactId1; - private IdentityManager identityManager0, identityManager1; - private LocalAuthor author0, author1; + private LifecycleManager lifecycleManager0, lifecycleManager1, + lifecycleManager2; + private SyncSessionFactory sync0, sync1, sync2; + private PrivateGroupManager groupManager0, groupManager1, groupManager2; + private ContactManager contactManager0, contactManager1, contactManager2; + private ContactId contactId01, contactId02, contactId1, contactId2; + private IdentityManager identityManager0, identityManager1, + identityManager2; + private LocalAuthor author0, author1, author2; + private DatabaseComponent db0, db1, db2; private PrivateGroup privateGroup0; private GroupId groupId0; @@ -101,13 +110,14 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { private final File testDir = TestUtils.getTestDirectory(); private final SecretKey master = TestUtils.getSecretKey(); private final int TIMEOUT = 15000; + private final String AUTHOR0 = "Author 0"; private final String AUTHOR1 = "Author 1"; private final String AUTHOR2 = "Author 2"; private static final Logger LOG = Logger.getLogger(PrivateGroupManagerTest.class.getName()); - private PrivateGroupManagerTestComponent t0, t1; + private PrivateGroupManagerTestComponent t0, t1, t2; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -117,26 +127,36 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { PrivateGroupManagerTestComponent component = DaggerPrivateGroupManagerTestComponent.builder().build(); component.inject(this); - injectEagerSingletons(component); assertTrue(testDir.mkdirs()); - File t0Dir = new File(testDir, AUTHOR1); + File t0Dir = new File(testDir, AUTHOR0); t0 = DaggerPrivateGroupManagerTestComponent.builder() .testDatabaseModule(new TestDatabaseModule(t0Dir)).build(); injectEagerSingletons(t0); - File t1Dir = new File(testDir, AUTHOR2); + File t1Dir = new File(testDir, AUTHOR1); t1 = DaggerPrivateGroupManagerTestComponent.builder() .testDatabaseModule(new TestDatabaseModule(t1Dir)).build(); injectEagerSingletons(t1); + File t2Dir = new File(testDir, AUTHOR2); + t2 = DaggerPrivateGroupManagerTestComponent.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(); + db0 = t0.getDatabaseComponent(); + db1 = t1.getDatabaseComponent(); + db2 = t2.getDatabaseComponent(); groupManager0 = t0.getPrivateGroupManager(); groupManager1 = t1.getPrivateGroupManager(); + groupManager2 = t2.getPrivateGroupManager(); sync0 = t0.getSyncSessionFactory(); sync1 = t1.getSyncSessionFactory(); + sync2 = t2.getSyncSessionFactory(); // initialize waiters fresh for each test validationWaiter = new Waiter(); @@ -192,6 +212,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { defaultInit(); // create and add test message with no previousMsgId + @SuppressWarnings("ConstantConditions") GroupMessage msg = groupMessageFactory .createGroupMessage(groupId0, clock.currentTimeMillis(), null, author0, "test", null); @@ -323,17 +344,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { GroupMessage joinMsg0 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0, joinTime, getRandomBytes(12)); - groupManager0.addPrivateGroup(privateGroup0, joinMsg0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // make group visible to 1 - Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); - t0.getDatabaseComponent() - .setVisibleToContact(txn0, contactId1, privateGroup0.getId(), - true); - t0.getDatabaseComponent().commitTransaction(txn0); - t0.getDatabaseComponent().endTransaction(txn0); + Transaction txn0 = db0.startTransaction(false); + db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true); + db0.commitTransaction(txn0); + db0.endTransaction(txn0); // author1 joins privateGroup0 with wrong timestamp joinTime = clock.currentTimeMillis(); @@ -348,17 +367,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, inviteTime, creatorSignature); - groupManager1.addPrivateGroup(privateGroup0, joinMsg1); + groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // make group visible to 0 - Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); - t1.getDatabaseComponent() - .setVisibleToContact(txn1, contactId0, privateGroup0.getId(), - true); - t1.getDatabaseComponent().commitTransaction(txn1); - t1.getDatabaseComponent().endTransaction(txn1); + Transaction txn1 = db1.startTransaction(false); + db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true); + db1.commitTransaction(txn1); + db1.endTransaction(txn1); // sync join messages sync0To1(); @@ -394,17 +411,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { GroupMessage joinMsg0 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0, inviteTime, creatorSignature); - groupManager0.addPrivateGroup(privateGroup0, joinMsg0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // make group visible to 1 - Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); - t0.getDatabaseComponent() - .setVisibleToContact(txn0, contactId1, privateGroup0.getId(), - true); - t0.getDatabaseComponent().commitTransaction(txn0); - t0.getDatabaseComponent().endTransaction(txn0); + Transaction txn0 = db0.startTransaction(false); + db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true); + db0.commitTransaction(txn0); + db0.endTransaction(txn0); // author1 joins privateGroup0 with wrong signature in join message joinTime = clock.currentTimeMillis(); @@ -419,17 +434,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, inviteTime, creatorSignature); - groupManager1.addPrivateGroup(privateGroup0, joinMsg1); + groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // make group visible to 0 - Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); - t1.getDatabaseComponent() - .setVisibleToContact(txn1, contactId0, privateGroup0.getId(), - true); - t1.getDatabaseComponent().commitTransaction(txn1); - t1.getDatabaseComponent().endTransaction(txn1); + Transaction txn1 = db1.startTransaction(false); + db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true); + db1.commitTransaction(txn1); + db1.endTransaction(txn1); // sync join messages sync0To1(); @@ -445,6 +458,193 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { assertEquals(1, groupManager0.getHeaders(groupId0).size()); } + @Test + public void testGetMembers() throws Exception { + defaultInit(); + + Collection<GroupMember> members0 = groupManager0.getMembers(groupId0); + assertEquals(2, members0.size()); + for (GroupMember m : members0) { + if (m.getAuthor().equals(author0)) { + assertEquals(VISIBLE, m.getVisibility()); + } else { + assertEquals(author1, m.getAuthor()); + assertEquals(VISIBLE, m.getVisibility()); + } + } + + Collection<GroupMember> members1 = groupManager1.getMembers(groupId0); + assertEquals(2, members1.size()); + for (GroupMember m : members1) { + if (m.getAuthor().equals(author1)) { + assertEquals(VISIBLE, m.getVisibility()); + } else { + assertEquals(author0, m.getAuthor()); + assertEquals(VISIBLE, m.getVisibility()); + } + } + } + + @Test + public void testJoinMessages() throws Exception { + defaultInit(); + + Collection<GroupMessageHeader> headers0 = + groupManager0.getHeaders(groupId0); + for (GroupMessageHeader h : headers0) { + if (h instanceof JoinMessageHeader) { + JoinMessageHeader j = (JoinMessageHeader) h; + // all relationships of the creator are visible + assertEquals(VISIBLE, j.getVisibility()); + } + } + + Collection<GroupMessageHeader> headers1 = + groupManager1.getHeaders(groupId0); + for (GroupMessageHeader h : headers1) { + if (h instanceof JoinMessageHeader) { + JoinMessageHeader j = (JoinMessageHeader) h; + if (h.getAuthor().equals(author1)) + // we are visible to ourselves + assertEquals(VISIBLE, j.getVisibility()); + else + // our relationship to the creator is visible + assertEquals(VISIBLE, j.getVisibility()); + } + } + } + + @Test + public void testRevealingRelationships() throws Exception { + defaultInit(); + + // make group visible to 2 + Transaction txn0 = db0.startTransaction(false); + db0.setVisibleToContact(txn0, contactId2, privateGroup0.getId(), true); + db0.commitTransaction(txn0); + db0.endTransaction(txn0); + + // author2 joins privateGroup0 + long joinTime = clock.currentTimeMillis(); + long inviteTime = joinTime - 1; + Group invitationGroup = contactGroupFactory + .createContactGroup(CLIENT_ID, author0.getId(), + author2.getId()); + BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(), + privateGroup0.getId()); + byte[] creatorSignature = + clientHelper.sign(toSign, author0.getPrivateKey()); + GroupMessage joinMsg2 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author2, + inviteTime, creatorSignature); + Transaction txn2 = db2.startTransaction(false); + groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2, false); + + // make group visible to 0 + db2.setVisibleToContact(txn2, contactId01, privateGroup0.getId(), true); + db2.commitTransaction(txn2); + db2.endTransaction(txn2); + + // sync join messages + deliverMessage(sync2, contactId2, sync0, contactId02, "2 to 0"); + deliveryWaiter.await(TIMEOUT, 1); + deliverMessage(sync0, contactId02, sync2, contactId2, "0 to 2"); + deliveryWaiter.await(TIMEOUT, 2); + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + + // check that everybody sees everybody else as joined + Collection<GroupMember> members0 = groupManager0.getMembers(groupId0); + assertEquals(3, members0.size()); + Collection<GroupMember> members1 = groupManager1.getMembers(groupId0); + assertEquals(3, members1.size()); + Collection<GroupMember> members2 = groupManager2.getMembers(groupId0); + assertEquals(3, members2.size()); + + // assert that contact relationship is not revealed initially + for (GroupMember m : members1) { + if (m.getAuthor().equals(author2)) { + assertEquals(INVISIBLE, m.getVisibility()); + } + } + for (GroupMember m : members2) { + if (m.getAuthor().equals(author1)) { + assertEquals(INVISIBLE, m.getVisibility()); + } + } + + // reveal contact relationship + Transaction txn1 = db1.startTransaction(false); + groupManager1 + .relationshipRevealed(txn1, groupId0, author2.getId(), false); + db1.commitTransaction(txn1); + db1.endTransaction(txn1); + txn2 = db2.startTransaction(false); + groupManager2 + .relationshipRevealed(txn2, groupId0, author1.getId(), true); + db2.commitTransaction(txn2); + db2.endTransaction(txn2); + + // assert that contact relationship is now revealed properly + members1 = groupManager1.getMembers(groupId0); + for (GroupMember m : members1) { + if (m.getAuthor().equals(author2)) { + assertEquals(REVEALED_BY_US, m.getVisibility()); + } + } + members2 = groupManager2.getMembers(groupId0); + for (GroupMember m : members2) { + if (m.getAuthor().equals(author1)) { + assertEquals(REVEALED_BY_CONTACT, m.getVisibility()); + } + } + + // assert that join messages reflect revealed relationship + Collection<GroupMessageHeader> headers1 = + groupManager1.getHeaders(groupId0); + for (GroupMessageHeader h : headers1) { + if (h instanceof JoinMessageHeader) { + JoinMessageHeader j = (JoinMessageHeader) h; + if (h.getAuthor().equals(author2)) + // 1 revealed the relationship to 2 + assertEquals(REVEALED_BY_US, j.getVisibility()); + else + // 1's other relationship (to 1 and creator) are visible + assertEquals(VISIBLE, j.getVisibility()); + } + } + Collection<GroupMessageHeader> headers2 = + groupManager2.getHeaders(groupId0); + for (GroupMessageHeader h : headers2) { + if (h instanceof JoinMessageHeader) { + JoinMessageHeader j = (JoinMessageHeader) h; + if (h.getAuthor().equals(author1)) + // 2's relationship was revealed by 1 + assertEquals(REVEALED_BY_CONTACT, j.getVisibility()); + else + // 2's other relationship (to 2 and creator) are visible + assertEquals(VISIBLE, j.getVisibility()); + } + } + } + + @Test + public void testDissolveGroup() throws Exception { + defaultInit(); + + // group is not dissolved initially + assertFalse(groupManager1.isDissolved(groupId0)); + + // creator dissolves group + Transaction txn1 = db1.startTransaction(false); + groupManager1.markGroupDissolved(txn1, groupId0); + db1.commitTransaction(txn1); + db1.endTransaction(txn1); + + // group is dissolved now + assertTrue(groupManager1.isDissolved(groupId0)); + } + @After public void tearDown() throws Exception { stopLifecycles(); @@ -483,7 +683,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { byte[] publicKey0 = keyPair0.getPublic().getEncoded(); byte[] privateKey0 = keyPair0.getPrivate().getEncoded(); author0 = authorFactory - .createLocalAuthor(AUTHOR1, publicKey0, privateKey0); + .createLocalAuthor(AUTHOR0, publicKey0, privateKey0); identityManager0.registerLocalAuthor(author0); privateGroup0 = privateGroupFactory.createPrivateGroup("Testgroup", author0); @@ -493,21 +693,34 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { byte[] publicKey1 = keyPair1.getPublic().getEncoded(); byte[] privateKey1 = keyPair1.getPrivate().getEncoded(); author1 = authorFactory - .createLocalAuthor(AUTHOR2, publicKey1, privateKey1); + .createLocalAuthor(AUTHOR1, publicKey1, privateKey1); identityManager1.registerLocalAuthor(author1); + + KeyPair keyPair2 = crypto.generateSignatureKeyPair(); + byte[] publicKey2 = keyPair2.getPublic().getEncoded(); + byte[] privateKey2 = keyPair2.getPrivate().getEncoded(); + author2 = authorFactory + .createLocalAuthor(AUTHOR2, publicKey2, privateKey2); + identityManager2.registerLocalAuthor(author2); } private void addDefaultContacts() throws DbException { - // sharer adds invitee as contact - contactId1 = contactManager0.addContact(author1, - author0.getId(), master, clock.currentTimeMillis(), true, - true, true - ); - // invitee adds sharer back - contactId0 = contactManager1.addContact(author0, - author1.getId(), master, clock.currentTimeMillis(), true, - true, true - ); + // creator adds invitee as contact + contactId1 = contactManager0 + .addContact(author1, author0.getId(), master, + clock.currentTimeMillis(), true, true, true); + // invitee adds creator back + contactId01 = contactManager1 + .addContact(author0, author1.getId(), master, + clock.currentTimeMillis(), true, true, true); + // creator adds invitee as contact + contactId2 = contactManager0 + .addContact(author2, author0.getId(), master, + clock.currentTimeMillis(), true, true, true); + // invitee adds creator back + contactId02 = contactManager2 + .addContact(author0, author2.getId(), master, + clock.currentTimeMillis(), true, true, true); } private void listenToEvents() { @@ -515,6 +728,8 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { t0.getEventBus().addListener(listener0); Listener listener1 = new Listener(); t1.getEventBus().addListener(listener1); + Listener listener2 = new Listener(); + t2.getEventBus().addListener(listener2); } private void addGroup() throws Exception { @@ -522,17 +737,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { long joinTime = clock.currentTimeMillis(); GroupMessage joinMsg0 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0); - groupManager0.addPrivateGroup(privateGroup0, joinMsg0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // make group visible to 1 - Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); - t0.getDatabaseComponent() - .setVisibleToContact(txn0, contactId1, privateGroup0.getId(), - true); - t0.getDatabaseComponent().commitTransaction(txn0); - t0.getDatabaseComponent().endTransaction(txn0); + Transaction txn0 = db0.startTransaction(false); + db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true); + db0.commitTransaction(txn0); + db0.endTransaction(txn0); // author1 joins privateGroup0 joinTime = clock.currentTimeMillis(); @@ -547,17 +760,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, inviteTime, creatorSignature); - groupManager1.addPrivateGroup(privateGroup0, joinMsg1); - assertEquals(joinMsg1.getMessage().getId(), - groupManager1.getPreviousMsgId(groupId0)); + groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); // make group visible to 0 - Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); - t1.getDatabaseComponent() - .setVisibleToContact(txn1, contactId0, privateGroup0.getId(), - true); - t1.getDatabaseComponent().commitTransaction(txn1); - t1.getDatabaseComponent().endTransaction(txn1); + Transaction txn1 = db1.startTransaction(false); + db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true); + db1.commitTransaction(txn1); + db1.endTransaction(txn1); + assertEquals(joinMsg1.getMessage().getId(), + groupManager1.getPreviousMsgId(groupId0)); // sync join messages sync0To1(); @@ -567,11 +778,11 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { } private void sync0To1() throws IOException, TimeoutException { - deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); + deliverMessage(sync0, contactId01, sync1, contactId1, "0 to 1"); } private void sync1To0() throws IOException, TimeoutException { - deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); + deliverMessage(sync1, contactId1, sync0, contactId01, "1 to 0"); } private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId, @@ -600,18 +811,23 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // Start the lifecycle manager and wait for it to finish lifecycleManager0 = t0.getLifecycleManager(); lifecycleManager1 = t1.getLifecycleManager(); - lifecycleManager0.startServices(AUTHOR1); - lifecycleManager1.startServices(AUTHOR2); + lifecycleManager2 = t2.getLifecycleManager(); + lifecycleManager0.startServices(AUTHOR0); + lifecycleManager1.startServices(AUTHOR1); + lifecycleManager2.startServices(AUTHOR2); 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 injectEagerSingletons( diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java index e59f6cdacab3fef325737597d2c1a59d105aa5b9..75a194d3246ddbba327f39d0c64d7d1c2dd45b72 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java @@ -105,7 +105,7 @@ public class GroupActivity extends ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setSubtitle(getString(R.string.groups_created_by, - group.getAuthor().getName())); + group.getCreator().getName())); } controller.isCreator(group, new UiResultExceptionHandler<Boolean, DbException>(this) { diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java index 89de8033149d97050882402892d25185e36cd6d8..e0ba5d3d89fcd318a334117813d2ccebe90de489 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -196,7 +196,7 @@ public class GroupControllerImpl extends try { LocalAuthor author = identityManager.getLocalAuthor(); boolean isCreator = - author.getId().equals(group.getAuthor().getId()); + author.getId().equals(group.getCreator().getId()); handler.onResult(isCreator); } catch (DbException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java index b37fc5e499829e0ef914a95937c225b28eabc3f2..4f7ff84a55ec56fcfad74fde5cb71406fa1442b2 100644 --- a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java @@ -117,7 +117,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl public void run() { LOG.info("Adding group to database..."); try { - groupManager.addPrivateGroup(group, joinMsg); + groupManager.addPrivateGroup(group, joinMsg, true); handler.onResult(group.getId()); } catch (DbException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java index 31c6458a654bc0fbbd409e2e1810b85a36836aab..ded8051817fd42bedf7ca52bcefd95be37e85ac6 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java @@ -43,7 +43,7 @@ class GroupItem { } Author getCreator() { - return privateGroup.getAuthor(); + return privateGroup.getCreator(); } String getName() { diff --git a/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java b/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java index ef0c73d91f2e1a5cacccea458d6f1bbe0a44c92f..9ff87a510b2d04fc92af80ae264d9121ef183265 100644 --- a/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java @@ -7,6 +7,8 @@ import org.briarproject.api.privategroup.GroupMember; import javax.annotation.concurrent.Immutable; +import static org.briarproject.api.privategroup.Visibility.INVISIBLE; + @Immutable @NotNullByDefault class MemberListItem { @@ -17,7 +19,7 @@ class MemberListItem { public MemberListItem(GroupMember groupMember) { this.member = groupMember.getAuthor(); - this.sharing = groupMember.isShared(); + this.sharing = groupMember.getVisibility() != INVISIBLE; // TODO #732 this.status = groupMember.getStatus(); } diff --git a/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java b/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..40de39d1eb2cd591054c38ecc913f4ccb224a355 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java @@ -0,0 +1,30 @@ +package org.briarproject.api.privategroup; + +import org.briarproject.api.event.Event; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.sync.GroupId; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class ContactRelationshipRevealedEvent extends Event { + + private final GroupId groupId; + private final Visibility visibility; + + public ContactRelationshipRevealedEvent(GroupId groupId, + Visibility visibility) { + this.groupId = groupId; + this.visibility = visibility; + } + + public GroupId getGroupId() { + return groupId; + } + + public Visibility getVisibility() { + return visibility; + } + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMember.java b/briar-api/src/org/briarproject/api/privategroup/GroupMember.java index e433aa792b30ad07efc8d1944122d856fa4f0bcd..2638dfe706b6e37b4006e45fb6f335d776825714 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMember.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMember.java @@ -12,12 +12,12 @@ public class GroupMember { private final Author author; private final Status status; - private final boolean shared; + private final Visibility visibility; - public GroupMember(Author author, Status status, boolean shared) { + public GroupMember(Author author, Status status, Visibility visibility) { this.author = author; this.status = status; - this.shared = shared; + this.visibility = visibility; } public Author getAuthor() { @@ -28,8 +28,8 @@ public class GroupMember { return status; } - public boolean isShared() { - return shared; + public Visibility getVisibility() { + return visibility; } } diff --git a/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java index ef8c3b337d72124fd5d42b075f5e8edea894a53f..8b14717a6e622d9f3a52f745673bb90e46088d39 100644 --- a/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java +++ b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java @@ -1,10 +1,6 @@ package org.briarproject.api.privategroup; -import org.briarproject.api.identity.Author; import org.briarproject.api.nullsafety.NotNullByDefault; -import org.briarproject.api.sync.GroupId; -import org.briarproject.api.sync.MessageId; -import org.jetbrains.annotations.Nullable; import javax.annotation.concurrent.Immutable; @@ -12,10 +8,16 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class JoinMessageHeader extends GroupMessageHeader { - public JoinMessageHeader(GroupId groupId, MessageId id, - @Nullable MessageId parentId, long timestamp, Author author, - Author.Status authorStatus, boolean read) { - super(groupId, id, parentId, timestamp, author, authorStatus, read); + private final Visibility visibility; + + public JoinMessageHeader(GroupMessageHeader h, Visibility visibility) { + super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(), + h.getAuthor(), h.getAuthorStatus(), h.isRead()); + this.visibility = visibility; + } + + public Visibility getVisibility() { + return visibility; } } diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java index ab8d520590072fc3bd2e6328a565f92173a47ab9..6bb6fdbe9eefb5cbb6ad24426ba9d3443960e7bb 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java @@ -12,20 +12,20 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class PrivateGroup extends NamedGroup implements Shareable { - private final Author author; + private final Author creator; - public PrivateGroup(Group group, String name, Author author, byte[] salt) { + public PrivateGroup(Group group, String name, Author creator, byte[] salt) { super(group, name, salt); - this.author = author; + this.creator = creator; } - public Author getAuthor() { - return author; + public Author getCreator() { + return creator; } @Override public boolean equals(Object o) { - return o instanceof PrivateGroup && super.equals(o); + return o instanceof PrivateGroup && super.equals(o); } } diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java index 4a96a843b999537f809ca6fa99a33c69aab5cdab..636678052d41359dd1ba0c8aefcc04a823167807 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java @@ -1,9 +1,11 @@ package org.briarproject.api.privategroup; +import org.briarproject.api.FormatException; import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; @@ -23,19 +25,21 @@ public interface PrivateGroupManager extends MessageTracker { * Adds a new private group and joins it. * * @param group The private group to add - * @param joinMsg The new member's join message + * @param joinMsg The creators's join message + * @param creator True if the group is added by its creator */ - void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg) - throws DbException; + void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg, + boolean creator) throws DbException; /** * Adds a new private group and joins it. * * @param group The private group to add * @param joinMsg The new member's join message + * @param creator True if the group is added by its creator */ void addPrivateGroup(Transaction txn, PrivateGroup group, - GroupMessage joinMsg) throws DbException; + GroupMessage joinMsg, boolean creator) throws DbException; /** * Removes a dissolved private group. @@ -43,7 +47,7 @@ public interface PrivateGroupManager extends MessageTracker { void removePrivateGroup(GroupId g) throws DbException; /** - * Gets the MessageId of your previous message sent to the group + * Gets the MessageId of the user's previous message sent to the group */ MessageId getPreviousMsgId(GroupId g) throws DbException; @@ -103,6 +107,17 @@ public interface PrivateGroupManager extends MessageTracker { */ boolean isMember(Transaction txn, GroupId g, Author a) throws DbException; + /** + * This method needs to be called when a contact relationship + * has been revealed between the user and the Author with AuthorId a + * in the Group identified by the GroupId g. + * + * @param byContact true if the remote contact has revealed + * the relationship first. Otherwise false. + */ + void relationshipRevealed(Transaction txn, GroupId g, AuthorId a, + boolean byContact) throws FormatException, DbException; + /** * Registers a hook to be called when members are added * or groups are removed. diff --git a/briar-api/src/org/briarproject/api/privategroup/Visibility.java b/briar-api/src/org/briarproject/api/privategroup/Visibility.java new file mode 100644 index 0000000000000000000000000000000000000000..4ff09c3ed49eee9c318f64711b5e30a889f43ca3 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/Visibility.java @@ -0,0 +1,27 @@ +package org.briarproject.api.privategroup; + +import org.briarproject.api.FormatException; + +public enum Visibility { + + INVISIBLE(0), + VISIBLE(1), + REVEALED_BY_US(2), + REVEALED_BY_CONTACT(3); + + int value; + + Visibility(int value) { + this.value = value; + } + + public static Visibility valueOf(int value) throws FormatException { + for (Visibility v : values()) if (v.value == value) return v; + throw new FormatException(); + } + + public int getInt() { + return value; + } + +} diff --git a/briar-core/src/org/briarproject/privategroup/GroupConstants.java b/briar-core/src/org/briarproject/privategroup/GroupConstants.java index 8bc82be42b4d6146282ec1d90496a7df1c35d349..72f57748c89f72a54b812da31aca614e8acef5eb 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupConstants.java +++ b/briar-core/src/org/briarproject/privategroup/GroupConstants.java @@ -2,20 +2,22 @@ package org.briarproject.privategroup; import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; -interface Constants { +interface GroupConstants { // Database keys String KEY_TYPE = "type"; String KEY_TIMESTAMP = "timestamp"; String KEY_READ = MSG_KEY_READ; String KEY_PARENT_MSG_ID = "parentMsgId"; - String KEY_NEW_MEMBER_MSG_ID = "newMemberMsgId"; String KEY_PREVIOUS_MSG_ID = "previousMsgId"; String KEY_MEMBER_ID = "memberId"; String KEY_MEMBER_NAME = "memberName"; String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey"; - String KEY_MEMBERS = "members"; - String KEY_DISSOLVED = "dissolved"; + String GROUP_KEY_MEMBERS = "members"; + String GROUP_KEY_OUR_GROUP = "ourGroup"; + String GROUP_KEY_CREATOR_ID = "creatorId"; + String GROUP_KEY_DISSOLVED = "dissolved"; + String GROUP_KEY_VISIBILITY = "visibility"; } diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index dda084d2e1e4fa7c13495468ab036dcf15692ee2..54b6324fc5b2a3d52d8d87716e364f52905c3d8c 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -29,14 +29,14 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH import static org.briarproject.api.privategroup.MessageType.JOIN; import static org.briarproject.api.privategroup.MessageType.POST; import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; -import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; -import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; -import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; -import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; -import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; -import static org.briarproject.privategroup.Constants.KEY_READ; -import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; -import static org.briarproject.privategroup.Constants.KEY_TYPE; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY; +import static org.briarproject.privategroup.GroupConstants.KEY_PARENT_MSG_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_READ; +import static org.briarproject.privategroup.GroupConstants.KEY_TIMESTAMP; +import static org.briarproject.privategroup.GroupConstants.KEY_TYPE; class GroupMessageValidator extends BdfMessageValidator { @@ -98,7 +98,7 @@ class GroupMessageValidator extends BdfMessageValidator { PrivateGroup pg = privateGroupFactory.parsePrivateGroup(g); // invite is null if the member is the creator of the private group - Author creator = pg.getAuthor(); + Author creator = pg.getCreator(); BdfList invite = body.getOptionalList(3); if (invite == null) { if (!member.equals(creator)) diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index b6117f65f198a4029d1eb8021069c031cacf3366..c2bdf6cf493060e57de864e01fbb16c985c5150a 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -2,8 +2,7 @@ package org.briarproject.privategroup; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.contact.Contact; -import org.briarproject.api.contact.ContactId; +import org.briarproject.api.clients.ProtocolStateException; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; @@ -19,6 +18,7 @@ import org.briarproject.api.identity.Author.Status; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.privategroup.ContactRelationshipRevealedEvent; import org.briarproject.api.privategroup.GroupMember; import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessageHeader; @@ -27,6 +27,7 @@ import org.briarproject.api.privategroup.MessageType; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.Visibility; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; @@ -48,20 +49,25 @@ import java.util.concurrent.CopyOnWriteArrayList; import javax.inject.Inject; import static org.briarproject.api.identity.Author.Status.OURSELVES; -import static org.briarproject.api.identity.Author.Status.UNVERIFIED; -import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.privategroup.MessageType.JOIN; import static org.briarproject.api.privategroup.MessageType.POST; -import static org.briarproject.privategroup.Constants.KEY_DISSOLVED; -import static org.briarproject.privategroup.Constants.KEY_MEMBERS; -import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; -import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; -import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; -import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; -import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; -import static org.briarproject.privategroup.Constants.KEY_READ; -import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; -import static org.briarproject.privategroup.Constants.KEY_TYPE; +import static org.briarproject.api.privategroup.Visibility.INVISIBLE; +import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT; +import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US; +import static org.briarproject.api.privategroup.Visibility.VISIBLE; +import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_CREATOR_ID; +import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_DISSOLVED; +import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_MEMBERS; +import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_OUR_GROUP; +import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_VISIBILITY; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY; +import static org.briarproject.privategroup.GroupConstants.KEY_PARENT_MSG_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_READ; +import static org.briarproject.privategroup.GroupConstants.KEY_TIMESTAMP; +import static org.briarproject.privategroup.GroupConstants.KEY_TYPE; @NotNullByDefault public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @@ -84,11 +90,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } @Override - public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg) - throws DbException { + public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg, + boolean creator) throws DbException { Transaction txn = db.startTransaction(false); try { - addPrivateGroup(txn, group, joinMsg); + addPrivateGroup(txn, group, joinMsg, creator); db.commitTransaction(txn); } finally { db.endTransaction(txn); @@ -97,12 +103,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @Override public void addPrivateGroup(Transaction txn, PrivateGroup group, - GroupMessage joinMsg) throws DbException { + GroupMessage joinMsg, boolean creator) throws DbException { try { db.addGroup(txn, group.getGroup()); + AuthorId creatorId = group.getCreator().getId(); BdfDictionary meta = BdfDictionary.of( - new BdfEntry(KEY_MEMBERS, new BdfList()), - new BdfEntry(KEY_DISSOLVED, false) + new BdfEntry(GROUP_KEY_MEMBERS, new BdfList()), + new BdfEntry(GROUP_KEY_CREATOR_ID, creatorId), + new BdfEntry(GROUP_KEY_OUR_GROUP, creator), + new BdfEntry(GROUP_KEY_DISSOLVED, false) ); clientHelper.mergeGroupMetadata(txn, group.getId(), meta); joinPrivateGroup(txn, joinMsg); @@ -118,9 +127,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); trackOutgoingMessage(txn, m.getMessage()); - addMember(txn, m.getMessage().getGroupId(), m.getMember()); + addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE); setPreviousMsgId(txn, m.getMessage().getGroupId(), m.getMessage().getId()); + attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true, VISIBLE); } @Override @@ -171,7 +181,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements public void markGroupDissolved(Transaction txn, GroupId g) throws DbException { BdfDictionary meta = BdfDictionary.of( - new BdfEntry(KEY_DISSOLVED, true) + new BdfEntry(GROUP_KEY_DISSOLVED, true) ); try { clientHelper.mergeGroupMetadata(txn, g, meta); @@ -283,7 +293,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements public boolean isDissolved(GroupId g) throws DbException { try { BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g); - return meta.getBoolean(KEY_DISSOLVED); + return meta.getBoolean(GROUP_KEY_DISSOLVED); } catch (FormatException e) { throw new DbException(e); } @@ -322,11 +332,22 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements for (AuthorId id : authors) { statuses.put(id, identityManager.getAuthorStatus(txn, id)); } - // Parse the metadata + // get current visibilities for join messages + Map<Author, Visibility> visibilities = getMembers(txn, g); + // parse the metadata for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) { BdfDictionary meta = entry.getValue(); - headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta, - statuses)); + if (meta.getLong(KEY_TYPE) == JOIN.getInt()) { + Author member = getAuthor(meta); + Visibility v = visibilities.get(member); + headers.add( + getJoinMessageHeader(txn, g, entry.getKey(), meta, + statuses, v)); + } else { + headers.add( + getGroupMessageHeader(txn, g, entry.getKey(), meta, + statuses)); + } } db.commitTransaction(txn); return headers; @@ -356,19 +377,17 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } boolean read = meta.getBoolean(KEY_READ); - if (meta.getLong(KEY_TYPE) == JOIN.getInt()) { - return new JoinMessageHeader(g, id, parentId, timestamp, author, - status, read); - } return new GroupMessageHeader(g, id, parentId, timestamp, author, status, read); } - private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g, - MessageId id, BdfDictionary meta) - throws DbException, FormatException { - return getGroupMessageHeader(txn, g, id, meta, - Collections.<AuthorId, Status>emptyMap()); + private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g, + MessageId id, BdfDictionary meta, Map<AuthorId, Status> statuses, + Visibility v) throws DbException, FormatException { + + GroupMessageHeader header = + getGroupMessageHeader(txn, g, id, meta, statuses); + return new JoinMessageHeader(header, v); } @Override @@ -376,18 +395,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements Transaction txn = db.startTransaction(true); try { Collection<GroupMember> members = new ArrayList<GroupMember>(); - Collection<Author> authors = getMembers(txn, g); - for (Author a : authors) { - Status status = identityManager.getAuthorStatus(txn, a.getId()); - boolean shared = false; - if (status == VERIFIED || status == UNVERIFIED) { - Collection<Contact> contacts = - db.getContactsByAuthorId(txn, a.getId()); - if (contacts.size() != 1) throw new DbException(); - ContactId c = contacts.iterator().next().getId(); - shared = db.isVisibleToContact(txn, c, g); - } - members.add(new GroupMember(a, status, shared)); + Map<Author, Visibility> authors = getMembers(txn, g); + for (Entry<Author, Visibility> m : authors.entrySet()) { + Status status = identityManager + .getAuthorStatus(txn, m.getKey().getId()); + members.add(new GroupMember(m.getKey(), status, m.getValue())); } db.commitTransaction(txn); return members; @@ -396,17 +408,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } } - private Collection<Author> getMembers(Transaction txn, GroupId g) + private Map<Author, Visibility> getMembers(Transaction txn, GroupId g) throws DbException { try { - Collection<Author> members = new ArrayList<Author>(); BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g); - BdfList list = meta.getList(KEY_MEMBERS); - for (Object o : list) { - BdfDictionary d = (BdfDictionary) o; + BdfList list = meta.getList(GROUP_KEY_MEMBERS); + Map<Author, Visibility> members = + new HashMap<Author, Visibility>(list.size()); + for (int i = 0 ; i < list.size(); i++) { + BdfDictionary d = list.getDictionary(i); Author member = getAuthor(d); - members.add(member); + Visibility v = getVisibility(d); + members.put(member, v); } return members; } catch (FormatException e) { @@ -417,12 +431,35 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @Override public boolean isMember(Transaction txn, GroupId g, Author a) throws DbException { - for (Author member : getMembers(txn, g)) { + for (Author member : getMembers(txn, g).keySet()) { if (member.equals(a)) return true; } return false; } + @Override + public void relationshipRevealed(Transaction txn, GroupId g, AuthorId a, + boolean byContact) throws FormatException, DbException { + BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g); + BdfList members = meta.getList(GROUP_KEY_MEMBERS); + Visibility v = INVISIBLE; + boolean foundMember = false; + for (int i = 0 ; i < members.size(); i++) { + BdfDictionary d = members.getDictionary(i); + AuthorId memberId = new AuthorId(d.getRaw(KEY_MEMBER_ID)); + if (a.equals(memberId)) { + foundMember = true; + Visibility vOld = getVisibility(d); + if (vOld != INVISIBLE) throw new ProtocolStateException(); + v = byContact ? REVEALED_BY_CONTACT : REVEALED_BY_US; + d.put(GROUP_KEY_VISIBILITY, v.getInt()); + } + } + if (!foundMember) throw new ProtocolStateException(); + clientHelper.mergeGroupMetadata(txn, g, meta); + txn.attach(new ContactRelationshipRevealedEvent(g, v)); + } + @Override public void registerPrivateGroupHook(PrivateGroupHook hook) { hooks.add(hook); @@ -432,47 +469,14 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements protected boolean incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { - long timestamp = meta.getLong(KEY_TIMESTAMP); MessageType type = MessageType.valueOf(meta.getLong(KEY_TYPE).intValue()); switch (type) { case JOIN: - addMember(txn, m.getGroupId(), getAuthor(meta)); - trackIncomingMessage(txn, m); - attachGroupMessageAddedEvent(txn, m, meta, false); + handleJoinMessage(txn, m, meta); return true; case POST: - // timestamp must be greater than the timestamps of parent post - byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID); - if (parentIdBytes != null) { - MessageId parentId = new MessageId(parentIdBytes); - BdfDictionary parentMeta = clientHelper - .getMessageMetadataAsDictionary(txn, parentId); - if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP)) - throw new FormatException(); - MessageType parentType = MessageType - .valueOf(parentMeta.getLong(KEY_TYPE).intValue()); - if (parentType != POST) - throw new FormatException(); - } - // and the member's previous message - byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID); - MessageId previousMsgId = new MessageId(previousMsgIdBytes); - BdfDictionary previousMeta = clientHelper - .getMessageMetadataAsDictionary(txn, previousMsgId); - if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP)) - throw new FormatException(); - // previous message must be from same member - if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID), - previousMeta.getRaw(KEY_MEMBER_ID))) - throw new FormatException(); - // previous message must be a POST or JOIN - MessageType previousType = MessageType - .valueOf(previousMeta.getLong(KEY_TYPE).intValue()); - if (previousType != JOIN && previousType != POST) - throw new FormatException(); - trackIncomingMessage(txn, m); - attachGroupMessageAddedEvent(txn, m, meta, false); + handleGroupMessage(txn, m, meta); return true; default: // the validator should only let valid types pass @@ -480,24 +484,93 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } } + private void handleJoinMessage(Transaction txn, Message m, + BdfDictionary meta) throws FormatException, DbException { + // find out if contact relationship is visible and then add new member + Author member = getAuthor(meta); + BdfDictionary groupMeta = clientHelper + .getGroupMetadataAsDictionary(txn, m.getGroupId()); + boolean ourGroup = groupMeta.getBoolean(GROUP_KEY_OUR_GROUP); + Visibility v = VISIBLE; + if (!ourGroup) { + AuthorId creatorId = new AuthorId( + groupMeta.getRaw(GROUP_KEY_CREATOR_ID)); + if (!creatorId.equals(member.getId())) + v = INVISIBLE; + } + addMember(txn, m.getGroupId(), member, v); + // track message and broadcast event + trackIncomingMessage(txn, m); + attachJoinMessageAddedEvent(txn, m, meta, false, v); + } + + private void handleGroupMessage(Transaction txn, Message m, + BdfDictionary meta) throws FormatException, DbException { + // timestamp must be greater than the timestamps of parent post + long timestamp = meta.getLong(KEY_TIMESTAMP); + byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID); + if (parentIdBytes != null) { + MessageId parentId = new MessageId(parentIdBytes); + BdfDictionary parentMeta = clientHelper + .getMessageMetadataAsDictionary(txn, parentId); + if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP)) + throw new FormatException(); + MessageType parentType = MessageType + .valueOf(parentMeta.getLong(KEY_TYPE).intValue()); + if (parentType != POST) + throw new FormatException(); + } + // and the member's previous message + byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID); + MessageId previousMsgId = new MessageId(previousMsgIdBytes); + BdfDictionary previousMeta = clientHelper + .getMessageMetadataAsDictionary(txn, previousMsgId); + if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP)) + throw new FormatException(); + // previous message must be from same member + if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID), + previousMeta.getRaw(KEY_MEMBER_ID))) + throw new FormatException(); + // previous message must be a POST or JOIN + MessageType previousType = MessageType + .valueOf(previousMeta.getLong(KEY_TYPE).intValue()); + if (previousType != JOIN && previousType != POST) + throw new FormatException(); + // track message and broadcast event + trackIncomingMessage(txn, m); + attachGroupMessageAddedEvent(txn, m, meta, false); + } + private void attachGroupMessageAddedEvent(Transaction txn, Message m, BdfDictionary meta, boolean local) throws DbException, FormatException { GroupMessageHeader h = - getGroupMessageHeader(txn, m.getGroupId(), m.getId(), meta); + getGroupMessageHeader(txn, m.getGroupId(), m.getId(), meta, + Collections.<AuthorId, Status>emptyMap()); Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local); txn.attach(e); } - private void addMember(Transaction txn, GroupId g, Author a) + private void attachJoinMessageAddedEvent(Transaction txn, Message m, + BdfDictionary meta, boolean local, Visibility v) + throws DbException, FormatException { + JoinMessageHeader h = + getJoinMessageHeader(txn, m.getGroupId(), m.getId(), meta, + Collections.<AuthorId, Status>emptyMap(), v); + Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local); + txn.attach(e); + } + + private void addMember(Transaction txn, GroupId g, Author a, Visibility v) throws DbException, FormatException { BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g); - BdfList members = meta.getList(KEY_MEMBERS); + BdfList members = meta.getList(GROUP_KEY_MEMBERS); members.add(BdfDictionary.of( new BdfEntry(KEY_MEMBER_ID, a.getId()), new BdfEntry(KEY_MEMBER_NAME, a.getName()), - new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()) + new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()), + new BdfEntry(GROUP_KEY_VISIBILITY, v.getInt()) )); clientHelper.mergeGroupMetadata(txn, g, meta); for (PrivateGroupHook hook : hooks) { @@ -512,4 +585,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return new Author(authorId, name, publicKey); } + private Visibility getVisibility(BdfDictionary meta) + throws FormatException { + return Visibility + .valueOf(meta.getLong(GROUP_KEY_VISIBILITY).intValue()); + } + } diff --git a/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java index b3048c7779a76b9e878f5cbe0037fb3769ace04c..86e8688a0da4f839669fad67ac05f9bd2abe489f 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java @@ -105,7 +105,7 @@ abstract class AbstractProtocolEngine<S extends Session> } Message m = messageEncoder.encodeInviteMessage( session.getContactGroupId(), privateGroup.getId(), - timestamp, privateGroup.getName(), privateGroup.getAuthor(), + timestamp, privateGroup.getName(), privateGroup.getCreator(), privateGroup.getSalt(), message, signature); sendMessage(txn, m, INVITE, privateGroup.getId(), true); return m; @@ -183,7 +183,8 @@ abstract class AbstractProtocolEngine<S extends Session> GroupMessage joinMessage = groupMessageFactory.createJoinMessage( privateGroup.getId(), timestamp, member, invite.getTimestamp(), invite.getSignature()); - privateGroupManager.addPrivateGroup(txn, privateGroup, joinMessage); + privateGroupManager + .addPrivateGroup(txn, privateGroup, joinMessage, false); } long getLocalTimestamp(S session) {