From 9736f9d31fb6e34a42e98e3073ffced405d4c934 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 9 Oct 2019 09:31:04 -0300 Subject: [PATCH 1/2] [core] allow messages from private group sessions with responses get deleted --- .../privategroup/invitation/CreatorState.java | 5 + .../GroupInvitationManagerImpl.java | 133 +++++++++- .../privategroup/invitation/InviteeState.java | 5 + .../privategroup/invitation/PeerState.java | 5 + .../invitation/SessionParser.java | 2 + .../invitation/SessionParserImpl.java | 5 + .../briar/privategroup/invitation/State.java | 2 + .../GroupInvitationIntegrationTest.java | 245 +++++++++++++----- 8 files changed, 337 insertions(+), 65 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java index 6fe69a08c..779618f6c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java @@ -38,6 +38,11 @@ enum CreatorState implements State { return visibility; } + @Override + public boolean isAwaitingResponse() { + return this == INVITED; + } + static CreatorState fromValue(int value) throws FormatException { for (CreatorState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java index b2d80c7bb..ac660dd27 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java @@ -40,6 +40,7 @@ import org.briarproject.briar.client.ConversationClientImpl; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -369,7 +370,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } @Override - public Collection getMessageHeaders(Transaction txn, + public Collection getMessageHeaders( + Transaction txn, ContactId c) throws DbException { try { Contact contact = db.getContact(txn, c); @@ -633,8 +635,98 @@ class GroupInvitationManagerImpl extends ConversationClientImpl @Override public boolean deleteAllMessages(Transaction txn, ContactId c) throws DbException { - // TODO actually delete messages (#1627 and #1629) - return getMessageIds(txn, c).size() == 0; + // get ID of the contact group + GroupId g = getContactGroup(db.getContact(txn, c)).getId(); + + // get metadata for all messages in the + // (these are sessions *and* protocol messages) + Map metadata; + try { + metadata = clientHelper.getMessageMetadataAsDictionary(txn, g); + } catch (FormatException e) { + throw new DbException(e); + } + + // get all sessions and their states + Map sessions = new HashMap<>(); + for (BdfDictionary d : metadata.values()) { + if (!sessionParser.isSession(d)) continue; + Session session; + try { + Role role = sessionParser.getRole(d); + if (role == CREATOR) { + session = sessionParser.parseCreatorSession(g, d); + } else if (role == INVITEE) { + session = sessionParser.parseInviteeSession(g, d); + } else if (role == PEER) { + session = sessionParser.parsePeerSession(g, d); + } else throw new AssertionError("unknown role"); + } catch (FormatException e) { + throw new DbException(e); + } + sessions.put(session.getPrivateGroupId(), + new DeletableSession(session.getState())); + } + + // assign protocol messages to their sessions + for (Entry entry : metadata.entrySet()) { + // skip all sessions, we are only interested in messages + BdfDictionary d = entry.getValue(); + if (sessionParser.isSession(d)) continue; + + // parse message metadata and skip messages not visible in UI + MessageMetadata m; + try { + m = messageParser.parseMetadata(d); + } catch (FormatException e) { + throw new DbException(e); + } + if (!m.isVisibleInConversation()) continue; + + // add visible messages to session + DeletableSession session = sessions.get(m.getPrivateGroupId()); + session.messages.add(entry.getKey()); + } + + // get a set of all messages which were not ACKed by the contact + Set notAcked = new HashSet<>(); + for (MessageStatus status : db.getMessageStatus(txn, c, g)) { + if (!status.isSeen()) notAcked.add(status.getMessageId()); + } + boolean allDeleted = + deleteCompletedSessions(txn, sessions.values(), notAcked); + recalculateGroupCount(txn, g); + return allDeleted; + } + + private boolean deleteCompletedSessions(Transaction txn, + Collection sessions, Set notAcked) + throws DbException { + // find completed sessions to delete + boolean allDeleted = true; + for (DeletableSession session : sessions) { + if (session.state.isAwaitingResponse()) { + allDeleted = false; + continue; + } + // we can only delete sessions + // where delivery of all messages was confirmed (aka ACKed) + boolean allAcked = true; + for (MessageId m : session.messages) { + if (notAcked.contains(m)) { + allAcked = false; + allDeleted = false; + break; + } + } + if (allAcked) { + for (MessageId m : session.messages) { + db.deleteMessage(txn, m); + db.deleteMessageMetadata(txn, m); + } + } + } + return allDeleted; } private Set getMessageIds(Transaction txn, ContactId c) @@ -650,6 +742,31 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } } + private void recalculateGroupCount(Transaction txn, GroupId g) + throws DbException { + BdfDictionary query = messageParser.getMessagesVisibleInUiQuery(); + Map results; + try { + results = + clientHelper.getMessageMetadataAsDictionary(txn, g, query); + } catch (FormatException e) { + throw new DbException(e); + } + int msgCount = 0; + int unreadCount = 0; + for (Entry entry : results.entrySet()) { + MessageMetadata meta; + try { + meta = messageParser.parseMetadata(entry.getValue()); + } catch (FormatException e) { + throw new DbException(e); + } + msgCount++; + if (!meta.isRead()) unreadCount++; + } + messageTracker.resetGroupCount(txn, g, msgCount, unreadCount); + } + private static class StoredSession { private final MessageId storageId; @@ -660,4 +777,14 @@ class GroupInvitationManagerImpl extends ConversationClientImpl this.bdfSession = bdfSession; } } + + private static class DeletableSession { + + private final State state; + private final List messages = new ArrayList<>(); + + private DeletableSession(State state) { + this.state = state; + } + } } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java index f347ca061..e44990a89 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java @@ -40,6 +40,11 @@ enum InviteeState implements State { return visibility; } + @Override + public boolean isAwaitingResponse() { + return this == INVITED; + } + static InviteeState fromValue(int value) throws FormatException { for (InviteeState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java index 041528266..ca7fd08e4 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java @@ -40,6 +40,11 @@ enum PeerState implements State { return visibility; } + @Override + public boolean isAwaitingResponse() { + return false; + } + static PeerState fromValue(int value) throws FormatException { for (PeerState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java index 152ed067e..115f8531d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java @@ -15,6 +15,8 @@ interface SessionParser { Role getRole(BdfDictionary d) throws FormatException; + boolean isSession(BdfDictionary d); + CreatorSession parseCreatorSession(GroupId contactGroupId, BdfDictionary d) throws FormatException; diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java index 19a424d70..dd848f57e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java @@ -48,6 +48,11 @@ class SessionParserImpl implements SessionParser { return Role.fromValue(d.getLong(SESSION_KEY_ROLE).intValue()); } + @Override + public boolean isSession(BdfDictionary d) { + return d.getBoolean(SESSION_KEY_IS_SESSION, false); + } + @Override public CreatorSession parseCreatorSession(GroupId contactGroupId, BdfDictionary d) throws FormatException { diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java index 2313f96c7..02777a770 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java @@ -7,4 +7,6 @@ interface State { int getValue(); Visibility getVisibility(); + + boolean isAwaitingResponse(); } diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java index 23bd95af3..b5c904a31 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java @@ -31,7 +31,7 @@ import static org.junit.Assert.fail; public class GroupInvitationIntegrationTest extends BriarIntegrationTest { - private PrivateGroup privateGroup0; + private PrivateGroup privateGroup; private PrivateGroupManager groupManager0, groupManager1; private GroupInvitationManager groupInvitationManager0, groupInvitationManager1; @@ -46,12 +46,12 @@ public class GroupInvitationIntegrationTest groupInvitationManager0 = c0.getGroupInvitationManager(); groupInvitationManager1 = c1.getGroupInvitationManager(); - privateGroup0 = + privateGroup = privateGroupFactory.createPrivateGroup("Testgroup", author0); long joinTime = clock.currentTimeMillis(); GroupMessage joinMsg0 = groupMessageFactory - .createJoinMessage(privateGroup0.getId(), joinTime, author0); - groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true); + .createJoinMessage(privateGroup.getId(), joinTime, author0); + groupManager0.addPrivateGroup(privateGroup, joinMsg0, true); } @Override @@ -90,21 +90,19 @@ public class GroupInvitationIntegrationTest 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()); + assertEquals(privateGroup, item.getShareable()); + assertEquals(privateGroup.getId(), item.getId()); + assertEquals(privateGroup.getName(), item.getName()); assertFalse(item.isSubscribed()); - Collection messages = - db1.transactionWithResult(true, txn -> groupInvitationManager1 - .getMessageHeaders(txn, contactId0From1)); + Collection messages = getMessages0From1(); assertEquals(1, messages.size()); GroupInvitationRequest request = (GroupInvitationRequest) messages.iterator().next(); assertEquals(text, request.getText()); assertEquals(author0, request.getNameable().getCreator()); assertEquals(timestamp, request.getTimestamp()); - assertEquals(privateGroup0.getName(), request.getNameable().getName()); + assertEquals(privateGroup.getName(), request.getNameable().getName()); assertFalse(request.isLocal()); assertFalse(request.isRead()); assertFalse(request.canBeOpened()); @@ -120,18 +118,16 @@ public class GroupInvitationIntegrationTest assertFalse(groupInvitationManager1.getInvitations().isEmpty()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, false); + .respondToInvitation(contactId0From1, privateGroup, false); - Collection messages = - db1.transactionWithResult(true, txn -> groupInvitationManager1 - .getMessageHeaders(txn, contactId0From1)); + Collection messages = getMessages0From1(); assertEquals(2, messages.size()); boolean foundResponse = false; for (ConversationMessageHeader m : messages) { if (m instanceof GroupInvitationResponse) { foundResponse = true; GroupInvitationResponse response = (GroupInvitationResponse) m; - assertEquals(privateGroup0.getId(), response.getShareableId()); + assertEquals(privateGroup.getId(), response.getShareableId()); assertTrue(response.isLocal()); assertFalse(response.wasAccepted()); } @@ -140,16 +136,14 @@ public class GroupInvitationIntegrationTest sync1To0(1, true); - messages = db0.transactionWithResult(true, txn -> - groupInvitationManager0 - .getMessageHeaders(txn, contactId1From0)); + messages = getMessages1From0(); assertEquals(2, messages.size()); foundResponse = false; for (ConversationMessageHeader m : messages) { if (m instanceof GroupInvitationResponse) { foundResponse = true; GroupInvitationResponse response = (GroupInvitationResponse) m; - assertEquals(privateGroup0.getId(), response.getShareableId()); + assertEquals(privateGroup.getId(), response.getShareableId()); assertFalse(response.isLocal()); assertFalse(response.wasAccepted()); } @@ -168,9 +162,7 @@ public class GroupInvitationIntegrationTest sendInvitation(timestamp, null); // check that invitation message state is correct - Collection messages = - db0.transactionWithResult(true, txn -> groupInvitationManager0 - .getMessageHeaders(txn, contactId1From0)); + Collection messages = getMessages1From0(); assertEquals(1, messages.size()); assertMessageState(messages.iterator().next(), true, false, false); @@ -178,11 +170,9 @@ public class GroupInvitationIntegrationTest assertFalse(groupInvitationManager1.getInvitations().isEmpty()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); - messages = db1.transactionWithResult(true, - txn -> groupInvitationManager1 - .getMessageHeaders(txn, contactId0From1)); + messages = getMessages0From1(); assertEquals(2, messages.size()); boolean foundResponse = false; for (ConversationMessageHeader m : messages) { @@ -190,11 +180,11 @@ public class GroupInvitationIntegrationTest foundResponse = true; GroupInvitationResponse response = (GroupInvitationResponse) m; assertMessageState(response, true, false, false); - assertEquals(privateGroup0.getId(), response.getShareableId()); + assertEquals(privateGroup.getId(), response.getShareableId()); assertTrue(response.wasAccepted()); } else { GroupInvitationRequest request = (GroupInvitationRequest) m; - assertEquals(privateGroup0, request.getNameable()); + assertEquals(privateGroup, request.getNameable()); assertTrue(request.wasAnswered()); assertTrue(request.canBeOpened()); } @@ -203,16 +193,14 @@ public class GroupInvitationIntegrationTest sync1To0(1, true); - messages = db1.transactionWithResult(true, txn -> - groupInvitationManager0 - .getMessageHeaders(txn, contactId1From0)); + messages = getMessages1From0(); assertEquals(2, messages.size()); foundResponse = false; for (ConversationMessageHeader m : messages) { if (m instanceof GroupInvitationResponse) { foundResponse = true; GroupInvitationResponse response = (GroupInvitationResponse) m; - assertEquals(privateGroup0.getId(), response.getShareableId()); + assertEquals(privateGroup.getId(), response.getShareableId()); assertTrue(response.wasAccepted()); } } @@ -223,7 +211,7 @@ public class GroupInvitationIntegrationTest // group was added Collection groups = groupManager1.getPrivateGroups(); assertEquals(1, groups.size()); - assertEquals(privateGroup0, groups.iterator().next()); + assertEquals(privateGroup, groups.iterator().next()); } @Test @@ -240,12 +228,10 @@ public class GroupInvitationIntegrationTest // 1 has one unread message Group g0 = groupInvitationManager1.getContactGroup(contact0From1); assertGroupCount(messageTracker1, g0.getId(), 1, 1, timestamp); - ConversationMessageHeader m = db1.transactionWithResult(true, txn -> - groupInvitationManager1.getMessageHeaders(txn, contactId0From1) - .iterator().next()); + ConversationMessageHeader m = getMessages0From1().iterator().next(); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); // 1 has two messages, one still unread assertGroupCount(messageTracker1, g0.getId(), 2, 1); @@ -266,28 +252,28 @@ public class GroupInvitationIntegrationTest // invitation is not allowed before the first hasn't been answered assertFalse(groupInvitationManager0 - .isInvitationAllowed(contact1From0, privateGroup0.getId())); + .isInvitationAllowed(contact1From0, privateGroup.getId())); // deliver invitation and response sync0To1(1, true); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, false); + .respondToInvitation(contactId0From1, privateGroup, false); sync1To0(1, true); // after invitation was declined, inviting again is possible assertTrue(groupInvitationManager0 - .isInvitationAllowed(contact1From0, privateGroup0.getId())); + .isInvitationAllowed(contact1From0, privateGroup.getId())); // send and accept the second invitation sendInvitation(clock.currentTimeMillis(), "Second Invitation"); sync0To1(1, true); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); sync1To0(1, true); // invitation is not allowed since the member joined the group now assertFalse(groupInvitationManager0 - .isInvitationAllowed(contact1From0, privateGroup0.getId())); + .isInvitationAllowed(contact1From0, privateGroup.getId())); // don't allow another invitation request try { @@ -305,14 +291,14 @@ public class GroupInvitationIntegrationTest sync0To1(1, true); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, false); + .respondToInvitation(contactId0From1, privateGroup, false); sync1To0(1, true); sendInvitation(timestamp, "Second Invitation"); sync0To1(1, true); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); } @Test(expected = ProtocolStateException.class) @@ -325,7 +311,7 @@ public class GroupInvitationIntegrationTest // Creator leaves group assertEquals(1, groupManager0.getPrivateGroups().size()); - groupManager0.removePrivateGroup(privateGroup0.getId()); + groupManager0.removePrivateGroup(privateGroup.getId()); assertEquals(0, groupManager0.getPrivateGroups().size()); // Creator's leave message is delivered to invitee @@ -335,7 +321,7 @@ public class GroupInvitationIntegrationTest // thrown as the action has failed assertEquals(0, groupManager1.getPrivateGroups().size()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); } @Test @@ -348,7 +334,7 @@ public class GroupInvitationIntegrationTest // Creator leaves group assertEquals(1, groupManager0.getPrivateGroups().size()); - groupManager0.removePrivateGroup(privateGroup0.getId()); + groupManager0.removePrivateGroup(privateGroup.getId()); assertEquals(0, groupManager0.getPrivateGroups().size()); // Creator's leave message is delivered to invitee @@ -361,7 +347,7 @@ public class GroupInvitationIntegrationTest // as the action has succeeded assertEquals(0, groupManager1.getPrivateGroups().size()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, false); + .respondToInvitation(contactId0From1, privateGroup, false); } @Test @@ -375,15 +361,15 @@ public class GroupInvitationIntegrationTest // Creator leaves group assertEquals(1, groupManager0.getPrivateGroups().size()); - groupManager0.removePrivateGroup(privateGroup0.getId()); + groupManager0.removePrivateGroup(privateGroup.getId()); assertEquals(0, groupManager0.getPrivateGroups().size()); // Invitee accepts invitation assertEquals(0, groupManager1.getPrivateGroups().size()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); assertEquals(1, groupManager1.getPrivateGroups().size()); - assertFalse(groupManager1.isDissolved(privateGroup0.getId())); + assertFalse(groupManager1.isDissolved(privateGroup.getId())); // Invitee's join message is delivered to creator sync1To0(1, true); @@ -392,7 +378,7 @@ public class GroupInvitationIntegrationTest sync0To1(1, true); // Group is marked as dissolved - assertTrue(groupManager1.isDissolved(privateGroup0.getId())); + assertTrue(groupManager1.isDissolved(privateGroup.getId())); } @Test @@ -406,13 +392,13 @@ public class GroupInvitationIntegrationTest // Creator leaves group assertEquals(1, groupManager0.getPrivateGroups().size()); - groupManager0.removePrivateGroup(privateGroup0.getId()); + groupManager0.removePrivateGroup(privateGroup.getId()); assertEquals(0, groupManager0.getPrivateGroups().size()); // Invitee declines invitation assertEquals(0, groupManager1.getPrivateGroups().size()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, false); + .respondToInvitation(contactId0From1, privateGroup, false); assertEquals(0, groupManager1.getPrivateGroups().size()); // Invitee's leave message is delivered to creator @@ -434,7 +420,7 @@ public class GroupInvitationIntegrationTest // Invitee responds to invitation assertEquals(0, groupManager1.getPrivateGroups().size()); groupInvitationManager1 - .respondToInvitation(contactId0From1, privateGroup0, true); + .respondToInvitation(contactId0From1, privateGroup, true); assertEquals(1, groupManager1.getPrivateGroups().size()); // Invitee's (sharing) join message is delivered to creator @@ -448,11 +434,11 @@ public class GroupInvitationIntegrationTest // Creator leaves group assertEquals(1, groupManager0.getPrivateGroups().size()); - groupManager0.removePrivateGroup(privateGroup0.getId()); + groupManager0.removePrivateGroup(privateGroup.getId()); assertEquals(0, groupManager0.getPrivateGroups().size()); // Invitee leaves group - groupManager1.removePrivateGroup(privateGroup0.getId()); + groupManager1.removePrivateGroup(privateGroup.getId()); assertEquals(0, groupManager1.getPrivateGroups().size()); // Creator's leave message is delivered to invitee @@ -462,13 +448,148 @@ public class GroupInvitationIntegrationTest sync1To0(1, true); } + @Test + public void testDeletingAllMessagesWhenCompletingSession() + throws Exception { + // send invitation + sendInvitation(clock.currentTimeMillis(), null); + sync0To1(1, true); + + // messages can not be deleted + assertFalse(deleteAllMessages1From0()); + assertEquals(1, getMessages1From0().size()); + assertFalse(deleteAllMessages0From1()); + assertEquals(1, getMessages0From1().size()); + + // respond + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup, true); + sync1To0(1, true); + + // check group count + Group g1From0 = groupInvitationManager0.getContactGroup(contact1From0); + Group g0From1 = groupInvitationManager1.getContactGroup(contact0From1); + assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); + assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); + + // messages can be deleted now by creator, invitee needs to wait for ACK + assertTrue(deleteAllMessages1From0()); + assertEquals(0, getMessages1From0().size()); + assertTrue(deleteAllMessages1From0()); // a second time nothing happens + assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); + + // trying to delete fails for invitee + assertFalse(deleteAllMessages0From1()); + assertEquals(2, getMessages0From1().size()); + + // creator sends JOIN message and ACK for response + sync0To1(1, true); + + // now invitee can also delete messages + assertTrue(deleteAllMessages0From1()); + assertEquals(0, getMessages0From1().size()); + assertTrue(deleteAllMessages0From1()); // a second time nothing happens + assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); + + // invitee now leaves + groupManager1.removePrivateGroup(privateGroup.getId()); + sync1To0(1, true); + + // no new messages to delete + assertEquals(0, getMessages1From0().size()); + assertEquals(0, getMessages0From1().size()); + } + + @Test + public void testDeletingAllMessagesWhenDeclining() throws Exception { + // send invitation + sendInvitation(clock.currentTimeMillis(), null); + sync0To1(1, true); + + // respond + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup, false); + sync1To0(1, true); + + // check group count + Group g1From0 = groupInvitationManager0.getContactGroup(contact1From0); + Group g0From1 = groupInvitationManager1.getContactGroup(contact0From1); + assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); + assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); + + // messages can be deleted now by creator, invitee needs to wait for ACK + assertTrue(deleteAllMessages1From0()); + assertEquals(0, getMessages1From0().size()); + assertTrue(deleteAllMessages1From0()); // a second time nothing happens + + // trying to delete fails for invitee + assertFalse(deleteAllMessages0From1()); + assertEquals(2, getMessages0From1().size()); + + // creator sends ACK + sendAcks(c0, c1, contactId1From0, 1); + + // now invitee can also delete messages + assertTrue(deleteAllMessages0From1()); + assertEquals(0, getMessages0From1().size()); + assertTrue(deleteAllMessages0From1()); // a second time nothing happens + assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); + + // creator can re-invite + sendInvitation(clock.currentTimeMillis(), null); + sync0To1(1, true); + + // now new messages can not be deleted anymore + assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages0From1()); + + // responding again + groupInvitationManager1 + .respondToInvitation(contactId0From1, privateGroup, false); + sync1To0(1, true); + + // creator sends ACK + sendAcks(c0, c1, contactId1From0, 1); + + // asserting group counts + assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); + assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); + + // deleting is possible again + assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages0From1()); + assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); + assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); + } + + private Collection getMessages1From0() + throws DbException { + return db0.transactionWithResult(true, txn -> groupInvitationManager0 + .getMessageHeaders(txn, contactId1From0)); + } + + private Collection getMessages0From1() + throws DbException { + return db1.transactionWithResult(true, txn -> groupInvitationManager1 + .getMessageHeaders(txn, contactId0From1)); + } + + private boolean deleteAllMessages1From0() throws DbException { + return db0.transactionWithResult(false, txn -> groupInvitationManager0 + .deleteAllMessages(txn, contactId1From0)); + } + + private boolean deleteAllMessages0From1() throws DbException { + return db1.transactionWithResult(false, txn -> groupInvitationManager1 + .deleteAllMessages(txn, contactId0From1)); + } + private void sendInvitation(long timestamp, @Nullable String text) throws DbException { byte[] signature = groupInvitationFactory.signInvitation(contact1From0, - privateGroup0.getId(), timestamp, author0.getPrivateKey()); - groupInvitationManager0 - .sendInvitation(privateGroup0.getId(), contactId1From0, text, - timestamp, signature); + privateGroup.getId(), timestamp, author0.getPrivateKey()); + groupInvitationManager0.sendInvitation(privateGroup.getId(), + contactId1From0, text, timestamp, signature); } } From 04f1036dbfa709b3ded8fc57f19144b8fec8698f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 9 Oct 2019 16:55:43 -0300 Subject: [PATCH 2/2] [android] Change non-deletion message to refer to ongoing sessions --- briar-android/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 70206eb30..6069fc981 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -139,7 +139,7 @@ Confirm Message Deletion Are you sure that you want to delete all messages? Could not delete all messages - Messages related to introductions or invitations cannot be deleted at the moment. + Messages related to ongoing introductions or invitations cannot be deleted until they conclude. Delete contact Confirm Contact Deletion Are you sure that you want to remove this contact and all messages exchanged with this contact?