package org.briarproject; import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.contact.Contact; import org.briarproject.api.data.BdfList; import org.briarproject.api.db.Transaction; 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.sync.GroupId; import org.briarproject.api.sync.MessageId; import org.junit.Before; import org.junit.Test; import java.util.Collection; 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.GroupInvitationFactory.SIGNING_LABEL_INVITE; import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PrivateGroupManagerTest extends BriarIntegrationTest { private PrivateGroup privateGroup0; private GroupId groupId0; private PrivateGroupManager groupManager0, groupManager1, groupManager2; @Before public void setUp() throws Exception { super.setUp(); groupManager0 = c0.getPrivateGroupManager(); groupManager1 = c1.getPrivateGroupManager(); groupManager2 = c2.getPrivateGroupManager(); privateGroup0 = privateGroupFactory.createPrivateGroup("Testgroup", author0); groupId0 = privateGroup0.getId(); } @Test public void testSendingMessage() throws Exception { addGroup(); // create and add test message long time = clock.currentTimeMillis(); String body = "This is a test message!"; MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0); GroupMessage msg = groupMessageFactory .createGroupMessage(groupId0, time, null, author0, body, previousMsgId); groupManager0.addLocalMessage(msg); assertEquals(msg.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // sync test message sync0To1(1, true); // assert that message arrived as expected Collection headers = groupManager1.getHeaders(groupId0); assertEquals(3, headers.size()); GroupMessageHeader header = null; for (GroupMessageHeader h : headers) { if (!(h instanceof JoinMessageHeader)) { header = h; } } assertTrue(header != null); assertFalse(header.isRead()); assertEquals(author0, header.getAuthor()); assertEquals(time, header.getTimestamp()); assertEquals(VERIFIED, header.getAuthorStatus()); assertEquals(body, groupManager1.getMessageBody(header.getId())); GroupCount count = groupManager1.getGroupCount(groupId0); assertEquals(2, count.getUnreadCount()); assertEquals(time, count.getLatestMsgTime()); assertEquals(3, count.getMsgCount()); } @Test public void testMessageWithWrongPreviousMsgId() throws Exception { addGroup(); // create and add test message with no previousMsgId @SuppressWarnings("ConstantConditions") GroupMessage msg = groupMessageFactory .createGroupMessage(groupId0, clock.currentTimeMillis(), null, author0, "test", null); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); // create and add test message with random previousMsgId MessageId previousMsgId = new MessageId(TestUtils.getRandomId()); msg = groupMessageFactory .createGroupMessage(groupId0, clock.currentTimeMillis(), null, author0, "test", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); // create and add test message with wrong previousMsgId previousMsgId = groupManager1.getPreviousMsgId(groupId0); msg = groupMessageFactory .createGroupMessage(groupId0, clock.currentTimeMillis(), null, author0, "test", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); } @Test public void testMessageWithWrongParentMsgId() throws Exception { addGroup(); // create and add test message with random parentMsgId MessageId parentMsgId = new MessageId(TestUtils.getRandomId()); MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0); GroupMessage msg = groupMessageFactory .createGroupMessage(groupId0, clock.currentTimeMillis(), parentMsgId, author0, "test", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); // create and add test message with wrong parentMsgId parentMsgId = previousMsgId; msg = groupMessageFactory .createGroupMessage(groupId0, clock.currentTimeMillis(), parentMsgId, author0, "test", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); } @Test public void testMessageWithWrongTimestamp() throws Exception { addGroup(); // create and add test message with wrong timestamp MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0); GroupMessage msg = groupMessageFactory .createGroupMessage(groupId0, 42, null, author0, "test", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); // create and add test message with good timestamp long time = clock.currentTimeMillis(); msg = groupMessageFactory .createGroupMessage(groupId0, time, null, author0, "test", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, true); assertEquals(3, groupManager1.getHeaders(groupId0).size()); // create and add test message with same timestamp as previous message previousMsgId = msg.getMessage().getId(); msg = groupMessageFactory .createGroupMessage(groupId0, time, previousMsgId, author0, "test2", previousMsgId); groupManager0.addLocalMessage(msg); // sync test message sync0To1(1, false); // assert that message did not arrive assertEquals(3, groupManager1.getHeaders(groupId0).size()); } @Test public void testWrongJoinMessages1() throws Exception { // author0 joins privateGroup0 with wrong join message long joinTime = clock.currentTimeMillis(); GroupMessage joinMsg0 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0, joinTime, getRandomBytes(12)); groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // share the group with 1 Transaction txn0 = db0.startTransaction(false); db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); // author1 joins privateGroup0 with wrong timestamp joinTime = clock.currentTimeMillis(); long inviteTime = joinTime; Contact c1 = contactManager0.getContact(contactId1From0); byte[] creatorSignature = groupInvitationFactory .signInvitation(c1, privateGroup0.getId(), inviteTime, author0.getPrivateKey()); GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, inviteTime, creatorSignature); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // share the group with 0 Transaction txn1 = db1.startTransaction(false); db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(), SHARED); db1.commitTransaction(txn1); db1.endTransaction(txn1); // sync join messages sync0To1(1, false); // assert that 0 never joined the group from 1's perspective assertEquals(1, groupManager1.getHeaders(groupId0).size()); sync1To0(1, false); // assert that 1 never joined the group from 0's perspective assertEquals(1, groupManager0.getHeaders(groupId0).size()); } @Test public void testWrongJoinMessages2() throws Exception { // author0 joins privateGroup0 with wrong member's join message long joinTime = clock.currentTimeMillis(); long inviteTime = joinTime - 1; BdfList toSign = groupInvitationFactory .createInviteToken(author0.getId(), author0.getId(), privateGroup0.getId(), inviteTime); byte[] creatorSignature = clientHelper .sign(SIGNING_LABEL_INVITE, toSign, author0.getPrivateKey()); // join message should not include invite time and creator's signature GroupMessage joinMsg0 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0, inviteTime, creatorSignature); groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // share the group with 1 Transaction txn0 = db0.startTransaction(false); db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); // author1 joins privateGroup0 with wrong signature in join message joinTime = clock.currentTimeMillis(); inviteTime = joinTime - 1; // signature uses joiner's key, not creator's key Contact c1 = contactManager0.getContact(contactId1From0); creatorSignature = groupInvitationFactory .signInvitation(c1, privateGroup0.getId(), inviteTime, author1.getPrivateKey()); GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, inviteTime, creatorSignature); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // share the group with 0 Transaction txn1 = db1.startTransaction(false); db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(), SHARED); db1.commitTransaction(txn1); db1.endTransaction(txn1); // sync join messages sync0To1(1, false); // assert that 0 never joined the group from 1's perspective assertEquals(1, groupManager1.getHeaders(groupId0).size()); sync1To0(1, false); // assert that 1 never joined the group from 0's perspective assertEquals(1, groupManager0.getHeaders(groupId0).size()); } @Test public void testGetMembers() throws Exception { addGroup(); Collection 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 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 { addGroup(); Collection 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 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 { addGroup(); // share the group with 2 Transaction txn0 = db0.startTransaction(false); db0.setGroupVisibility(txn0, contactId2From0, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); // author2 joins privateGroup0 long joinTime = clock.currentTimeMillis(); long inviteTime = joinTime - 1; Contact c2 = contactManager0.getContact(contactId2From0); byte[] creatorSignature = groupInvitationFactory .signInvitation(c2, privateGroup0.getId(), inviteTime, author0.getPrivateKey()); GroupMessage joinMsg2 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author2, inviteTime, creatorSignature); Transaction txn2 = db2.startTransaction(false); groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2, false); // share the group with 0 db2.setGroupVisibility(txn2, contactId0From1, privateGroup0.getId(), SHARED); db2.commitTransaction(txn2); db2.endTransaction(txn2); // sync join messages sync2To0(1, true); sync0To2(2, true); sync0To1(1, true); // check that everybody sees everybody else as joined Collection members0 = groupManager0.getMembers(groupId0); assertEquals(3, members0.size()); Collection members1 = groupManager1.getMembers(groupId0); assertEquals(3, members1.size()); Collection 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 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 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 { addGroup(); // 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)); } private void addGroup() throws Exception { // author0 joins privateGroup0 long joinTime = clock.currentTimeMillis(); GroupMessage joinMsg0 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0); groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // share the group with 1 Transaction txn0 = db0.startTransaction(false); db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); // author1 joins privateGroup0 joinTime = clock.currentTimeMillis(); long inviteTime = joinTime - 1; Contact c1 = contactManager0.getContact(contactId1From0); byte[] creatorSignature = groupInvitationFactory .signInvitation(c1, privateGroup0.getId(), inviteTime, author0.getPrivateKey()); GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, inviteTime, creatorSignature); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); // share the group with 0 Transaction txn1 = db1.startTransaction(false); db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(), SHARED); db1.commitTransaction(txn1); db1.endTransaction(txn1); assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // sync join messages sync0To1(1, true); sync1To0(1, true); } }