diff --git a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java index 1e0486df4cd4c3986660958518619ff63162ddf2..fcd3205e73a759c6f3d652c21df7eb9a7aeb5cd5 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java @@ -39,6 +39,7 @@ import org.briarproject.introduction.IntroductionGroupFactory; import org.briarproject.introduction.IntroductionModule; import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.privategroup.PrivateGroupModule; +import org.briarproject.privategroup.invitation.GroupInvitationModule; import org.briarproject.properties.PropertiesModule; import org.briarproject.sharing.SharingModule; import org.briarproject.sync.SyncModule; @@ -180,6 +181,7 @@ public abstract class BriarIntegrationTest extends BriarTestCase { component.inject(new CryptoModule.EagerSingletons()); component.inject(new ContactModule.EagerSingletons()); component.inject(new ForumModule.EagerSingletons()); + component.inject(new GroupInvitationModule.EagerSingletons()); component.inject(new IntroductionModule.EagerSingletons()); component.inject(new PropertiesModule.EagerSingletons()); component.inject(new PrivateGroupModule.EagerSingletons()); diff --git a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java index 835c345bd758afd9684e936fc421b6fb29b815b4..7d4cdf2a8a8197f63954c8d58676d9d8fd019edd 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java +++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java @@ -14,6 +14,7 @@ import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.sync.SyncSessionFactory; import org.briarproject.blogs.BlogsModule; @@ -78,6 +79,8 @@ public interface BriarIntegrationTestComponent { void inject(ForumModule.EagerSingletons init); + void inject(GroupInvitationModule.EagerSingletons init); + void inject(IntroductionModule.EagerSingletons init); void inject(LifecycleModule.EagerSingletons init); @@ -114,6 +117,8 @@ public interface BriarIntegrationTestComponent { ForumManager getForumManager(); + GroupInvitationManager getGroupInvitationManager(); + IntroductionManager getIntroductionManager(); MessageTracker getMessageTracker(); diff --git a/briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..198a3039d65b155f54d23429b1bd2048a9f34253 --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java @@ -0,0 +1,415 @@ +package org.briarproject; + +import org.briarproject.api.clients.ProtocolStateException; +import org.briarproject.api.db.DbException; +import org.briarproject.api.privategroup.GroupMessage; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationRequest; +import org.briarproject.api.privategroup.invitation.GroupInvitationResponse; +import org.briarproject.api.sharing.InvitationMessage; +import org.briarproject.api.sync.Group; +import org.jetbrains.annotations.Nullable; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; + +import static junit.framework.TestCase.fail; +import static org.briarproject.TestUtils.assertGroupCount; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GroupInvitationIntegrationTest extends BriarIntegrationTest { + + private PrivateGroup privateGroup0; + private PrivateGroupManager groupManager0, groupManager1; + private GroupInvitationManager groupInvitationManager0, + groupInvitationManager1; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + groupManager0 = c0.getPrivateGroupManager(); + groupManager1 = c1.getPrivateGroupManager(); + groupInvitationManager0 = c0.getGroupInvitationManager(); + groupInvitationManager1 = c1.getGroupInvitationManager(); + + privateGroup0 = + privateGroupFactory.createPrivateGroup("Testgroup", author0); + long joinTime = clock.currentTimeMillis(); + GroupMessage joinMsg0 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); + } + + @Test + public void testSendInvitation() throws Exception { + long timestamp = clock.currentTimeMillis(); + String msg = "Hi!"; + sendInvitation(timestamp, msg); + + sync0To1(1, true); + + Collection<GroupInvitationItem> invitations = + groupInvitationManager1.getInvitations(); + assertEquals(1, invitations.size()); + GroupInvitationItem item = invitations.iterator().next(); + assertEquals(contact0From1, item.getCreator()); + assertEquals(privateGroup0, item.getShareable()); + assertEquals(privateGroup0.getId(), item.getId()); + assertEquals(privateGroup0.getName(), item.getName()); + assertFalse(item.isSubscribed()); + + Collection<InvitationMessage> messages = + groupInvitationManager1.getInvitationMessages(contactId0From1); + assertEquals(1, messages.size()); + GroupInvitationRequest request = + (GroupInvitationRequest) messages.iterator().next(); + assertEquals(msg, request.getMessage()); + assertEquals(author0, request.getCreator()); + assertEquals(timestamp, request.getTimestamp()); + assertEquals(contactId0From1, request.getContactId()); + assertEquals(privateGroup0.getName(), request.getGroupName()); + assertFalse(request.isLocal()); + assertFalse(request.isRead()); + } + + @Test + public void testInvitationDecline() throws Exception { + long timestamp = clock.currentTimeMillis(); + sendInvitation(timestamp, null); + + sync0To1(1, true); + assertFalse(groupInvitationManager1.getInvitations().isEmpty()); + + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, false); + + Collection<InvitationMessage> messages = + groupInvitationManager1.getInvitationMessages(contactId0From1); + assertEquals(2, messages.size()); + boolean foundResponse = false; + for (InvitationMessage m : messages) { + if (m instanceof GroupInvitationResponse) { + foundResponse = true; + GroupInvitationResponse response = (GroupInvitationResponse) m; + assertEquals(contactId0From1, response.getContactId()); + assertTrue(response.isLocal()); + assertFalse(response.wasAccepted()); + } + } + assertTrue(foundResponse); + + sync1To0(1, true); + + messages = + groupInvitationManager0.getInvitationMessages(contactId1From0); + assertEquals(2, messages.size()); + foundResponse = false; + for (InvitationMessage m : messages) { + if (m instanceof GroupInvitationResponse) { + foundResponse = true; + GroupInvitationResponse response = (GroupInvitationResponse) m; + assertEquals(contactId0From1, response.getContactId()); + assertFalse(response.isLocal()); + assertFalse(response.wasAccepted()); + } + } + assertTrue(foundResponse); + + // no invitations are open + assertTrue(groupInvitationManager1.getInvitations().isEmpty()); + // no groups were added + assertEquals(0, groupManager1.getPrivateGroups().size()); + } + + @Test + public void testInvitationAccept() throws Exception { + long timestamp = clock.currentTimeMillis(); + sendInvitation(timestamp, null); + + sync0To1(1, true); + assertFalse(groupInvitationManager1.getInvitations().isEmpty()); + + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + + Collection<InvitationMessage> messages = + groupInvitationManager1.getInvitationMessages(contactId0From1); + assertEquals(2, messages.size()); + boolean foundResponse = false; + for (InvitationMessage m : messages) { + if (m instanceof GroupInvitationResponse) { + foundResponse = true; + GroupInvitationResponse response = (GroupInvitationResponse) m; + assertTrue(response.wasAccepted()); + } + } + assertTrue(foundResponse); + + sync1To0(1, true); + + messages = + groupInvitationManager0.getInvitationMessages(contactId1From0); + assertEquals(2, messages.size()); + foundResponse = false; + for (InvitationMessage m : messages) { + if (m instanceof GroupInvitationResponse) { + foundResponse = true; + GroupInvitationResponse response = (GroupInvitationResponse) m; + assertTrue(response.wasAccepted()); + } + } + assertTrue(foundResponse); + + // no invitations are open + assertTrue(groupInvitationManager1.getInvitations().isEmpty()); + // group was added + Collection<PrivateGroup> groups = groupManager1.getPrivateGroups(); + assertEquals(1, groups.size()); + assertEquals(privateGroup0, groups.iterator().next()); + } + + @Test + public void testGroupCount() throws Exception { + long timestamp = clock.currentTimeMillis(); + sendInvitation(timestamp, null); + + // 0 has one read outgoing message + Group g1 = groupInvitationManager0.getContactGroup(contact1From0); + assertGroupCount(messageTracker0, g1.getId(), 1, 0, timestamp); + + sync0To1(1, true); + + // 1 has one unread message + Group g0 = groupInvitationManager1.getContactGroup(contact0From1); + assertGroupCount(messageTracker1, g0.getId(), 1, 1, timestamp); + InvitationMessage m = + groupInvitationManager1.getInvitationMessages(contactId0From1) + .iterator().next(); + + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + + // 1 has two messages, one still unread + assertGroupCount(messageTracker1, g0.getId(), 2, 1); + + // now all messages should be read + groupInvitationManager1.setReadFlag(g0.getId(), m.getId(), true); + assertGroupCount(messageTracker1, g0.getId(), 2, 0); + + sync1To0(1, true); + + // now 0 has two messages, one of them unread + assertGroupCount(messageTracker0, g1.getId(), 2, 1); + } + + @Test + public void testMultipleInvitations() throws Exception { + sendInvitation(clock.currentTimeMillis(), null); + + // invitation is not allowed before the first hasn't been answered + assertFalse(groupInvitationManager0 + .isInvitationAllowed(contact1From0, privateGroup0.getId())); + + // deliver invitation and response + sync0To1(1, true); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, false); + sync1To0(1, true); + + // after invitation was declined, inviting again is possible + assertTrue(groupInvitationManager0 + .isInvitationAllowed(contact1From0, privateGroup0.getId())); + + // send and accept the second invitation + sendInvitation(clock.currentTimeMillis(), "Second Invitation"); + sync0To1(1, true); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + sync1To0(1, true); + + // invitation is not allowed since the member joined the group now + assertFalse(groupInvitationManager0 + .isInvitationAllowed(contact1From0, privateGroup0.getId())); + + // don't allow another invitation request + try { + sendInvitation(clock.currentTimeMillis(), "Third Invitation"); + fail(); + } catch (ProtocolStateException e) { + // expected + } + } + + @Test(expected = ProtocolStateException.class) + public void testInvitationsWithSameTimestamp() throws Exception { + long timestamp = clock.currentTimeMillis(); + sendInvitation(timestamp, null); + sync0To1(1, true); + + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, false); + sync1To0(1, true); + + sendInvitation(timestamp, "Second Invitation"); + sync0To1(1, true); + + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + } + + @Test(expected = ProtocolStateException.class) + public void testCreatorLeavesBeforeInvitationAccepted() throws Exception { + // Creator invites invitee to join group + sendInvitation(clock.currentTimeMillis(), null); + + // Creator's invite message is delivered to invitee + sync0To1(1, true); + + // Creator leaves group + assertEquals(1, groupManager0.getPrivateGroups().size()); + groupManager0.removePrivateGroup(privateGroup0.getId()); + assertEquals(0, groupManager0.getPrivateGroups().size()); + + // Creator's leave message is delivered to invitee + sync0To1(1, true); + + // Invitee accepts invitation, but it's no longer open - exception is + // thrown as the action has failed + assertEquals(0, groupManager1.getPrivateGroups().size()); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + } + + @Test + public void testCreatorLeavesBeforeInvitationDeclined() throws Exception { + // Creator invites invitee to join group + sendInvitation(clock.currentTimeMillis(), null); + + // Creator's invite message is delivered to invitee + sync0To1(1, true); + + // Creator leaves group + assertEquals(1, groupManager0.getPrivateGroups().size()); + groupManager0.removePrivateGroup(privateGroup0.getId()); + assertEquals(0, groupManager0.getPrivateGroups().size()); + + // Creator's leave message is delivered to invitee + sync0To1(1, true); + + // Invitee declines invitation, but it's no longer open - no exception + // as the action has succeeded + assertEquals(0, groupManager1.getPrivateGroups().size()); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, false); + } + + @Test + public void testCreatorLeavesConcurrentlyWithInvitationAccepted() + throws Exception { + // Creator invites invitee to join group + sendInvitation(clock.currentTimeMillis(), null); + + // Creator's invite message is delivered to invitee + sync0To1(1, true); + + // Creator leaves group + assertEquals(1, groupManager0.getPrivateGroups().size()); + groupManager0.removePrivateGroup(privateGroup0.getId()); + assertEquals(0, groupManager0.getPrivateGroups().size()); + + // Invitee accepts invitation + assertEquals(0, groupManager1.getPrivateGroups().size()); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + assertEquals(1, groupManager1.getPrivateGroups().size()); + assertFalse(groupManager1.isDissolved(privateGroup0.getId())); + + // Invitee's join message is delivered to creator + sync1To0(1, true); + + // Creator's leave message is delivered to invitee + sync0To1(1, true); + + // Group is marked as dissolved + assertTrue(groupManager1.isDissolved(privateGroup0.getId())); + } + + @Test + public void testCreatorLeavesConcurrentlyWithInvitationDeclined() + throws Exception { + // Creator invites invitee to join group + sendInvitation(clock.currentTimeMillis(), null); + + // Creator's invite message is delivered to invitee + sync0To1(1, true); + + // Creator leaves group + assertEquals(1, groupManager0.getPrivateGroups().size()); + groupManager0.removePrivateGroup(privateGroup0.getId()); + assertEquals(0, groupManager0.getPrivateGroups().size()); + + // Invitee declines invitation + assertEquals(0, groupManager1.getPrivateGroups().size()); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, false); + assertEquals(0, groupManager1.getPrivateGroups().size()); + + // Invitee's leave message is delivered to creator + sync1To0(1, true); + + // Creator's leave message is delivered to invitee + sync0To1(1, true); + } + + @Test + public void testCreatorLeavesConcurrentlyWithMemberLeaving() + throws Exception { + // Creator invites invitee to join group + sendInvitation(clock.currentTimeMillis(), null); + + // Creator's invite message is delivered to invitee + sync0To1(1, true); + + // Invitee responds to invitation + assertEquals(0, groupManager1.getPrivateGroups().size()); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + assertEquals(1, groupManager1.getPrivateGroups().size()); + + // Invitee's join message is delivered to creator + sync1To0(1, true); + + // Creator leaves group + assertEquals(1, groupManager0.getPrivateGroups().size()); + groupManager0.removePrivateGroup(privateGroup0.getId()); + assertEquals(0, groupManager0.getPrivateGroups().size()); + + // Invitee leaves group + groupManager1.removePrivateGroup(privateGroup0.getId()); + assertEquals(0, groupManager1.getPrivateGroups().size()); + + // Creator's leave message is delivered to invitee + sync0To1(1, true); + + // Invitee's leave message is delivered to creator + sync1To0(1, true); + } + + private void sendInvitation(long timestamp, @Nullable String msg) throws + DbException { + byte[] signature = groupInvitationFactory.signInvitation(contact1From0, + privateGroup0.getId(), timestamp, author0.getPrivateKey()); + groupInvitationManager0 + .sendInvitation(privateGroup0.getId(), contactId1From0, msg, + timestamp, signature); + } + +} diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f1b96df29fb5011dc98d50b08948a0a5d8c7036a --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java @@ -0,0 +1,205 @@ +package org.briarproject; + +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.db.DbException; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.privategroup.GroupMember; +import org.briarproject.api.privategroup.GroupMessage; +import org.briarproject.api.privategroup.GroupMessageHeader; +import org.briarproject.api.privategroup.JoinMessageHeader; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.Nullable; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; + +import static org.briarproject.api.identity.Author.Status.OURSELVES; +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.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * This class tests how PrivateGroupManager and GroupInvitationManager + * play together. + */ +public class PrivateGroupIntegrationTest extends BriarIntegrationTest { + + private GroupId groupId0; + private PrivateGroup privateGroup0; + private PrivateGroupManager groupManager0, groupManager1, groupManager2; + private GroupInvitationManager groupInvitationManager0, + groupInvitationManager1, groupInvitationManager2; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + groupManager0 = c0.getPrivateGroupManager(); + groupManager1 = c1.getPrivateGroupManager(); + groupManager2 = c2.getPrivateGroupManager(); + groupInvitationManager0 = c0.getGroupInvitationManager(); + groupInvitationManager1 = c1.getGroupInvitationManager(); + groupInvitationManager2 = c2.getGroupInvitationManager(); + + privateGroup0 = + privateGroupFactory.createPrivateGroup("Test Group", author0); + groupId0 = privateGroup0.getId(); + long joinTime = clock.currentTimeMillis(); + GroupMessage joinMsg0 = groupMessageFactory + .createJoinMessage(groupId0, joinTime, author0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); + } + + @Test + public void testMembership() throws Exception { + sendInvitation(contactId1From0, clock.currentTimeMillis(), "Hi!"); + + // our group has only one member (ourselves) + Collection<GroupMember> members = groupManager0.getMembers(groupId0); + assertEquals(1, members.size()); + assertEquals(author0, members.iterator().next().getAuthor()); + assertEquals(OURSELVES, members.iterator().next().getStatus()); + + sync0To1(1, true); + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + sync1To0(1, true); + + // sync group join messages + sync0To1(2, true); // + one invitation protocol join message + sync1To0(1, true); + + // now the group has two members + members = groupManager0.getMembers(groupId0); + assertEquals(2, members.size()); + for (GroupMember m : members) { + if (m.getStatus() == OURSELVES) { + assertEquals(author0.getId(), m.getAuthor().getId()); + } else { + assertEquals(author1.getId(), m.getAuthor().getId()); + } + } + + members = groupManager1.getMembers(groupId0); + assertEquals(2, members.size()); + for (GroupMember m : members) { + if (m.getStatus() == OURSELVES) { + assertEquals(author1.getId(), m.getAuthor().getId()); + } else { + assertEquals(author0.getId(), m.getAuthor().getId()); + } + } + } + + @Test + public void testRevealContacts() throws Exception { + // invite two contacts + sendInvitation(contactId1From0, clock.currentTimeMillis(), "Hi 1!"); + sendInvitation(contactId2From0, clock.currentTimeMillis(), "Hi 2!"); + sync0To1(1, true); + sync0To2(1, true); + + // accept both invitations + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup0, true); + groupInvitationManager2 + .respondToInvitation(contactId0From2, privateGroup0, true); + sync1To0(1, true); + sync2To0(1, true); + + // sync group join messages + sync0To1(2, true); // + one invitation protocol join message + assertEquals(2, groupManager1.getMembers(groupId0).size()); + sync1To0(1, true); + assertEquals(2, groupManager0.getMembers(groupId0).size()); + sync0To2(3, true); // 2 join messages and 1 invite join message + assertEquals(3, groupManager2.getMembers(groupId0).size()); + sync2To0(1, true); + assertEquals(3, groupManager0.getMembers(groupId0).size()); + sync0To1(1, true); + assertEquals(3, groupManager1.getMembers(groupId0).size()); + + // 1 and 2 add each other as contacts + addContacts1And2(); + + // their relationship is still invisible + assertEquals(INVISIBLE, + getGroupMember(groupManager1, author2.getId()).getVisibility()); + assertEquals(INVISIBLE, + getGroupMember(groupManager2, author1.getId()).getVisibility()); + + // 1 reveals the contact relationship to 2 + assertTrue(contactId2From1 != null); + groupInvitationManager1.revealRelationship(contactId2From1, groupId0); + sync1To2(1, true); + sync2To1(1, true); + + // their relationship is now revealed + assertEquals(REVEALED_BY_US, + getGroupMember(groupManager1, author2.getId()).getVisibility()); + assertEquals(REVEALED_BY_CONTACT, + getGroupMember(groupManager2, author1.getId()).getVisibility()); + + // 2 sends a message to the group + long time = clock.currentTimeMillis(); + String body = "This is a test message!"; + MessageId previousMsgId = groupManager2.getPreviousMsgId(groupId0); + GroupMessage msg = groupMessageFactory + .createGroupMessage(groupId0, time, null, author2, body, + previousMsgId); + groupManager2.addLocalMessage(msg); + + // 1 has only the three join messages in the group + Collection<GroupMessageHeader> headers = + groupManager1.getHeaders(groupId0); + assertEquals(3, headers.size()); + + // message should sync to 1 without creator (0) being involved + sync2To1(1, true); + headers = groupManager1.getHeaders(groupId0); + assertEquals(4, headers.size()); + boolean foundPost = false; + for (GroupMessageHeader h : headers) { + if (h instanceof JoinMessageHeader) continue; + foundPost = true; + assertEquals(time, h.getTimestamp()); + assertEquals(groupId0, h.getGroupId()); + assertEquals(author2.getId(), h.getAuthor().getId()); + } + assertTrue(foundPost); + + // message should sync from 1 to 0 without 2 being involved + sync1To0(1, true); + headers = groupManager0.getHeaders(groupId0); + assertEquals(4, headers.size()); + } + + private void sendInvitation(ContactId c, long timestamp, + @Nullable String msg) throws DbException { + Contact contact = contactManager0.getContact(c); + byte[] signature = groupInvitationFactory + .signInvitation(contact, groupId0, timestamp, + author0.getPrivateKey()); + groupInvitationManager0 + .sendInvitation(groupId0, c, msg, timestamp, signature); + } + + private GroupMember getGroupMember(PrivateGroupManager groupManager, + AuthorId a) throws DbException { + Collection<GroupMember> members = groupManager.getMembers(groupId0); + for (GroupMember m : members) { + if (m.getAuthor().getId().equals(a)) return m; + } + throw new AssertionError(); + } + +} diff --git a/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java index 5be3b4573143e41cb97234ffbebcba73e9ca8723..6873a910dbe705aba397ab1fab3444bbd0f954b4 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java @@ -74,7 +74,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl private final Group localGroup; @Inject - protected GroupInvitationManagerImpl(DatabaseComponent db, + GroupInvitationManagerImpl(DatabaseComponent db, ClientHelper clientHelper, MetadataParser metadataParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory, diff --git a/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java index fe03fdb7c6ac5207fa9916905b7c28137b798b63..231af013511dc8b8dd2df537266948ae4e8094ea 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java @@ -148,10 +148,11 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { case DISSOLVED: return abort(txn, s); // Invalid in these states case INVITED: + case LEFT: + return onRemoteLeaveWhenNotSubscribed(txn, s, m); case ACCEPTED: case JOINED: - case LEFT: - return onRemoteLeave(txn, s, m); + return onRemoteLeaveWhenSubscribed(txn, s, m); case ERROR: return s; // Ignored in this state default: @@ -260,8 +261,23 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { s.getInviteTimestamp(), JOINED); } - private InviteeSession onRemoteLeave(Transaction txn, InviteeSession s, - LeaveMessage m) throws DbException, FormatException { + private InviteeSession onRemoteLeaveWhenNotSubscribed(Transaction txn, + InviteeSession s, LeaveMessage m) + throws DbException, FormatException { + // The timestamp must be higher than the last invite message, if any + if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s); + // The dependency, if any, must be the last remote message + if (!isValidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + // Move to the DISSOLVED state + return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(), + s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(), + s.getInviteTimestamp(), DISSOLVED); + } + + private InviteeSession onRemoteLeaveWhenSubscribed(Transaction txn, + InviteeSession s, LeaveMessage m) + throws DbException, FormatException { // The timestamp must be higher than the last invite message, if any if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s); // The dependency, if any, must be the last remote message diff --git a/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java b/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java index af048f6f1a7e68d14d7fb9d07f7fb489bd4179fd..52fcd5d721ff0a81d4b34e9a5fb762cff4617fb4 100644 --- a/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java +++ b/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java @@ -8,7 +8,6 @@ import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.identity.Author; -import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.MessageId; @@ -29,9 +28,6 @@ import static org.junit.Assert.assertFalse; public class ForumPostValidatorTest extends ValidatorTestCase { - private final AuthorFactory authorFactory = - context.mock(AuthorFactory.class); - private final MessageId parentId = new MessageId(TestUtils.getRandomId()); private final String authorName = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);