Merge branch '732-reveal-backennd' into 'master'

Add support for revealing contacts to the PrivateGroupManager

This also adds three integration tests and improves some small details here and there in the private group client.

Prerequisite for #732.

See merge request !396
This commit is contained in:
akwizgran
2016-11-10 17:00:31 +00:00
16 changed files with 582 additions and 208 deletions

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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))

View File

@@ -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))

View File

@@ -43,7 +43,7 @@ class GroupItem {
}
Author getCreator() {
return privateGroup.getAuthor();
return privateGroup.getCreator();
}
String getName() {

View File

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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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";
}

View File

@@ -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))

View File

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

View File

@@ -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) {