From 9660ff2fff0445df8f886b5cf54a26b18e1de0a6 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 8 Oct 2019 12:46:19 -0300 Subject: [PATCH] [core] delete conversation messages belonging to completed introduction sessions A session is completed if it returned to the START state and if all sent messages have been ACKed by the receiver. The session's metadata is kept in case the user restarts the session by doing another introduction. --- .../briar/introduction/IntroduceeState.java | 5 + .../briar/introduction/IntroducerState.java | 5 + .../introduction/IntroductionManagerImpl.java | 107 ++++- .../briar/introduction/State.java | 2 + .../IntroductionIntegrationTest.java | 406 +++++++++++++++--- .../briar/test/BriarIntegrationTest.java | 52 +++ 6 files changed, 523 insertions(+), 54 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java index 3a3e16af3..c422af82a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java @@ -29,6 +29,11 @@ enum IntroduceeState implements State { return value; } + @Override + public boolean isComplete() { + return this == START; + } + static IntroduceeState fromValue(int value) throws FormatException { for (IntroduceeState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java index 6514eca16..809380647 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java @@ -29,6 +29,11 @@ enum IntroducerState implements State { return value; } + @Override + public boolean isComplete() { + return this == START; + } + static IntroducerState fromValue(int value) throws FormatException { for (IntroducerState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 185c59c0b..9ad3c5370 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -42,6 +42,7 @@ import org.briarproject.briar.introduction.IntroducerSession.Introducee; 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; @@ -319,7 +320,7 @@ class IntroductionManagerImpl extends ConversationClientImpl if (ss == null) return true; IntroducerSession session = sessionParser.parseIntroducerSession(ss.bdfSession); - return session.getState() == START; + return session.getState().isComplete(); } @Override @@ -561,8 +562,98 @@ class IntroductionManagerImpl 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 group + Map messages; + try { + messages = clientHelper.getMessageMetadataAsDictionary(txn, g); + } catch (FormatException e) { + throw new DbException(e); + } + + // assign protocol messages to their sessions + Map sessions = new HashMap<>(); + for (Entry entry : messages.entrySet()) { + MessageMetadata m; + try { + m = messageParser.parseMetadata(entry.getValue()); + } catch (FormatException e) { + throw new DbException(e); + } + if (m.getSessionId() == null) { + // this can only be an unhandled REQUEST message + // that does not yet have a SessionId assigned + continue; + } + // get session from map or database + DeletableSession session = sessions.get(m.getSessionId()); + if (session == null) { + session = getDeletableSession(txn, g, m.getSessionId()); + sessions.put(m.getSessionId(), session); + } + 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()); + } + return deleteCompletedSessions(txn, sessions, notAcked); + } + + private DeletableSession getDeletableSession(Transaction txn, + GroupId introducerGroupId, SessionId sessionId) throws DbException { + try { + StoredSession ss = getSession(txn, sessionId); + if (ss == null) throw new AssertionError(); + Session s; + Role role = sessionParser.getRole(ss.bdfSession); + if (role == INTRODUCER) { + s = sessionParser.parseIntroducerSession(ss.bdfSession); + } else if (role == INTRODUCEE) { + s = sessionParser.parseIntroduceeSession(introducerGroupId, + ss.bdfSession); + } else throw new AssertionError(); + return new DeletableSession(s.getState()); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private boolean deleteCompletedSessions(Transaction txn, + Map sessions, Set notAcked) + throws DbException { + // find completed sessions to delete + boolean allDeleted = true; + for (DeletableSession session : sessions.values()) { + if (!session.state.isComplete()) { + 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; + } + } + // delete messages of session, if all were ACKed + if (allAcked) { + for (MessageId m : session.messages) { + db.deleteMessage(txn, m); + db.deleteMessageMetadata(txn, m); + } + // we can not delete the session as it might get restarted + // and then needs the previous MessageIds + } + } + return allDeleted; } private Set getMessageIds(Transaction txn, ContactId c) @@ -589,4 +680,14 @@ class IntroductionManagerImpl extends ConversationClientImpl } } + 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/introduction/State.java b/briar-core/src/main/java/org/briarproject/briar/introduction/State.java index 3063f9bd8..be7c0b735 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/State.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/State.java @@ -4,4 +4,6 @@ interface State { int getValue(); + boolean isComplete(); + } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 895fb5a16..bd63b9635 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -142,14 +142,10 @@ public class IntroductionIntegrationTest assertGroupCount(messageTracker0, g2.getId(), 1, 0); // check that request message states are correct - Collection messages = - db0.transactionWithResult(true, txn -> introductionManager0 - .getMessageHeaders(txn, contactId1From0)); + Collection messages = getMessages1From0(); assertEquals(1, messages.size()); assertMessageState(messages.iterator().next(), true, false, false); - messages = - db0.transactionWithResult(true, txn -> introductionManager0 - .getMessageHeaders(txn, contactId2From0)); + messages = getMessages2From0(); assertEquals(1, messages.size()); assertMessageState(messages.iterator().next(), true, false, false); @@ -162,9 +158,7 @@ public class IntroductionIntegrationTest assertGroupCount(messageTracker1, g1.getId(), 2, 1); // check that accept message state is correct - messages = - db1.transactionWithResult(true, txn -> introductionManager1 - .getMessageHeaders(txn, contactId0From1)); + messages = getMessages0From1(); assertEquals(2, messages.size()); for (ConversationMessageHeader h : messages) { if (h instanceof ConversationResponse) { @@ -327,22 +321,17 @@ public class IntroductionIntegrationTest Group g1 = introductionManager0.getContactGroup(introducee1); Group g2 = introductionManager0.getContactGroup(introducee2); - Collection messages = - db0.transactionWithResult(true, txn -> introductionManager0 - .getMessageHeaders(txn, contactId1From0)); + Collection messages = getMessages1From0(); assertEquals(2, messages.size()); assertGroupCount(messageTracker0, g1.getId(), 2, 1); - messages = db0.transactionWithResult(true, txn -> - introductionManager0.getMessageHeaders(txn, contactId2From0)); + messages = getMessages2From0(); assertEquals(2, messages.size()); assertGroupCount(messageTracker0, g2.getId(), 2, 1); - messages = db1.transactionWithResult(true, txn -> - introductionManager1.getMessageHeaders(txn, contactId0From1)); + messages = getMessages0From1(); assertEquals(2, messages.size()); assertGroupCount(messageTracker1, g1.getId(), 2, 1); // introducee2 should also have the decline response of introducee1 - messages = db2.transactionWithResult(true, txn -> - introductionManager2.getMessageHeaders(txn, contactId0From2)); + messages = getMessages0From2(); assertEquals(3, messages.size()); assertGroupCount(messageTracker2, g2.getId(), 3, 2); @@ -394,18 +383,13 @@ public class IntroductionIntegrationTest assertFalse(contactManager2 .contactExists(author1.getId(), author2.getId())); - Collection messages = - db0.transactionWithResult(true, txn -> introductionManager0 - .getMessageHeaders(txn, contactId1From0)); + Collection messages = getMessages1From0(); assertEquals(2, messages.size()); - messages = db0.transactionWithResult(true, txn -> - introductionManager0.getMessageHeaders(txn, contactId2From0)); + messages = getMessages2From0(); assertEquals(2, messages.size()); - messages = db1.transactionWithResult(true, txn -> - introductionManager1.getMessageHeaders(txn, contactId0From1)); + messages = getMessages0From1(); assertEquals(3, messages.size()); - messages = db2.transactionWithResult(true, txn -> - introductionManager2.getMessageHeaders(txn, contactId0From2)); + messages = getMessages0From2(); assertEquals(2, messages.size()); assertFalse(listener0.aborted); assertFalse(listener1.aborted); @@ -597,21 +581,13 @@ public class IntroductionIntegrationTest Group g1 = introductionManager0.getContactGroup(introducee1); Group g2 = introductionManager0.getContactGroup(introducee2); - assertEquals(2, db0.transactionWithResult(true, txn -> - introductionManager0.getMessageHeaders(txn, contactId1From0)) - .size()); + assertEquals(2, getMessages1From0().size()); assertGroupCount(messageTracker0, g1.getId(), 2, 1); - assertEquals(2, db0.transactionWithResult(true, txn -> - introductionManager0.getMessageHeaders(txn, contactId2From0)) - .size()); + assertEquals(2, getMessages2From0().size()); assertGroupCount(messageTracker0, g2.getId(), 2, 1); - assertEquals(2, db1.transactionWithResult(true, txn -> - introductionManager1.getMessageHeaders(txn, contactId0From1)) - .size()); + assertEquals(2, getMessages0From1().size()); assertGroupCount(messageTracker1, g1.getId(), 2, 1); - assertEquals(3, db2.transactionWithResult(true, txn -> - introductionManager2.getMessageHeaders(txn, contactId0From2)) - .size()); + assertEquals(3, getMessages0From2().size()); assertGroupCount(messageTracker2, g2.getId(), 3, 2); assertFalse(listener0.aborted); @@ -635,9 +611,7 @@ public class IntroductionIntegrationTest assertFalse(listener1.requestReceived); // make really sure we don't have that request - assertTrue(db1.transactionWithResult(true, txn -> - introductionManager1.getMessageHeaders(txn, contactId0From1)) - .isEmpty()); + assertTrue(getMessages0From1().isEmpty()); // The message was invalid, so no abort message was sent assertFalse(listener0.aborted); @@ -982,6 +956,7 @@ public class IntroductionIntegrationTest .getMessageMetadataAsDictionary(group0.getId()).size()); // ensure introducer has aborted the session + eventWaiter.await(TIMEOUT, 1); // wait for AbortEvent assertTrue(listener0.aborted); // sync REQUEST and ABORT message @@ -1154,6 +1129,316 @@ public class IntroductionIntegrationTest ); } + @Test + public void testDeletingAllMessagesWhenCompletingSession() + throws Exception { + addListeners(true, true); + + // make introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + + // sync first REQUEST message + sync0To1(1, true); + eventWaiter.await(TIMEOUT, 1); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages1From0()); + // introducee1 can not yet remove messages + assertFalse(deleteAllMessages0From1()); + + // sync second REQUEST message + sync0To2(1, true); + eventWaiter.await(TIMEOUT, 1); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages2From0()); + // introducee2 can not yet remove messages + assertFalse(deleteAllMessages0From2()); + + // sync first ACCEPT message + sync1To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages1From0()); + + // sync second ACCEPT message + sync2To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages2From0()); + + // sync forwarded ACCEPT messages to introducees + sync0To1(1, true); + sync0To2(1, true); + + // introducee1 can not yet remove messages + assertFalse(deleteAllMessages0From1()); + // introducee2 can not yet remove messages + assertFalse(deleteAllMessages0From2()); + + // sync first AUTH and its forward + sync1To0(1, true); + sync0To2(1, true); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages2From0()); + // introducee2 can not yet remove messages + assertFalse(deleteAllMessages0From2()); + + // sync second AUTH and its forward as well as the following ACTIVATE + sync2To0(2, true); + sync0To1(2, true); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages2From0()); + // introducee1 can not yet remove messages + assertFalse(deleteAllMessages0From1()); + + // sync second ACTIVATE and its forward + sync1To0(1, true); + sync0To2(1, true); + + // wait for introduction to succeed + eventWaiter.await(TIMEOUT, 2); + assertTrue(listener1.succeeded); + assertTrue(listener2.succeeded); + + // introducer can now remove messages + assertTrue(deleteAllMessages1From0()); + assertEquals(0, getMessages1From0().size()); + assertTrue(deleteAllMessages1From0()); // a second time returns true + + // introducee1 can not yet remove messages, because last not ACKed + assertFalse(deleteAllMessages0From1()); + assertEquals(2, getMessages0From1().size()); + + // ACK last message + sendAcks(c0, c1, contactId1From0, 1); + + // introducee1 can now remove messages + assertTrue(deleteAllMessages0From1()); + assertEquals(0, getMessages0From1().size()); + assertTrue(deleteAllMessages0From1()); // a second time returns true + + // introducee2 can remove messages (last message was incoming) + assertTrue(deleteAllMessages0From2()); + assertEquals(0, getMessages0From2().size()); + assertTrue(deleteAllMessages0From2()); // a second time returns true + + // a new introduction is still possible + assertTrue(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Ho!", time); + sync0To1(1, true); + sync0To2(1, true); + + // sync responses + sync1To0(1, true); + eventWaiter.await(TIMEOUT, 1); + sync2To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // no one should have aborted until now + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); + + // nobody can delete anything again + assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From2()); + } + + @Test + public void testDeletingAllMessagesWhenDeclining() throws Exception { + addListeners(false, false); + + // make introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + + // sync REQUEST messages + sync0To1(1, true); + eventWaiter.await(TIMEOUT, 1); + sync0To2(1, true); + eventWaiter.await(TIMEOUT, 1); + + // sync first DECLINE message + sync1To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages1From0()); + // introducee1 can not yet remove messages + assertFalse(deleteAllMessages0From1()); + + // sync second DECLINE message + sync2To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // introducer can not yet remove messages + assertFalse(deleteAllMessages2From0()); + // introducee2 can not yet remove messages + assertFalse(deleteAllMessages0From2()); + + // forward first DECLINE message + sync0To2(1, true); + + // introducee2 can now remove messages + assertTrue(deleteAllMessages0From2()); + assertEquals(0, getMessages0From2().size()); + assertTrue(deleteAllMessages0From2()); // a second time nothing happens + + // forward second DECLINE message + sync0To1(1, true); + + // introducee1 can now remove messages + assertTrue(deleteAllMessages0From1()); + assertEquals(0, getMessages0From1().size()); + assertTrue(deleteAllMessages0From1()); // a second time nothing happens + + // introducer can not yet remove messages + assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages2From0()); + + // introducer can remove messages after getting ACK from introducee1 + sendAcks(c1, c0, contactId0From1, 1); + assertTrue(deleteAllMessages1From0()); + assertEquals(0, getMessages1From0().size()); + assertTrue(deleteAllMessages1From0()); // a second time nothing happens + + // introducer can remove messages after getting ACK from introducee2 + sendAcks(c2, c0, contactId0From2, 1); + assertTrue(deleteAllMessages2From0()); + assertEquals(0, getMessages2From0().size()); + assertTrue(deleteAllMessages2From0()); // a second time nothing happens + + // a new introduction is still possible + assertTrue(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Ho!", time); + sync0To1(1, true); + sync0To2(1, true); + + // sync responses + sync1To0(1, true); + eventWaiter.await(TIMEOUT, 1); + sync2To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // no one should have aborted until now + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); + + // nobody can delete anything again + assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From2()); + } + + /** + * This test is testing that a session's deletable flag gets reset + * when the session is used again, + * so that it can not cause a session to get deleted prematurely. + */ + @Test + public void testDeletingOneSideOfSession() throws Exception { + addListeners(false, false); + + // make introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + + // sync REQUEST messages + sync0To1(1, true); + eventWaiter.await(TIMEOUT, 1); + sync0To2(1, true); + eventWaiter.await(TIMEOUT, 1); + + // sync DECLINE messages + sync1To0(1, true); + eventWaiter.await(TIMEOUT, 1); + sync2To0(1, true); + eventWaiter.await(TIMEOUT, 1); + + // forward DECLINE messages + sync0To2(1, true); + sync0To1(1, true); + + // introducer can remove messages after getting ACK from introducee1 + sendAcks(c1, c0, contactId0From1, 1); + assertTrue(deleteAllMessages1From0()); + assertEquals(0, getMessages1From0().size()); + assertTrue(deleteAllMessages1From0()); // a second time nothing happens + + // a new introduction is still possible + assertTrue(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Ho!", time); + sync0To1(1, true); + sync0To2(1, true); + + // sync and forward DECLINE messages + sync1To0(1, true); + sync2To0(1, true); + sync0To2(1, true); + sync0To1(1, true); + + // introducer can remove messages after getting ACK from introducee1 + sendAcks(c1, c0, contactId0From1, 1); + assertTrue(deleteAllMessages1From0()); + assertEquals(0, getMessages1From0().size()); + assertTrue(deleteAllMessages1From0()); // a second time nothing happens + + // introducer can remove messages after getting ACK from introducee2 + // if this succeeds, we still had the session object after delete above + sendAcks(c2, c0, contactId0From2, 1); + assertTrue(deleteAllMessages2From0()); + assertEquals(0, getMessages2From0().size()); + assertTrue(deleteAllMessages2From0()); // a second time nothing happens + + // no one should have aborted + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); + } + + private boolean deleteAllMessages1From0() throws DbException { + return db0.transactionWithResult(false, txn -> introductionManager0 + .deleteAllMessages(txn, contactId1From0)); + } + + private boolean deleteAllMessages2From0() throws DbException { + return db0.transactionWithResult(false, txn -> introductionManager0 + .deleteAllMessages(txn, contactId2From0)); + } + + private boolean deleteAllMessages0From1() throws DbException { + return db1.transactionWithResult(false, txn -> introductionManager1 + .deleteAllMessages(txn, contactId0From1)); + } + + private boolean deleteAllMessages0From2() throws DbException { + return db2.transactionWithResult(false, txn -> introductionManager2 + .deleteAllMessages(txn, contactId0From2)); + } + private void addTransportProperties() throws Exception { TransportPropertyManager tpm0 = c0.getTransportPropertyManager(); TransportPropertyManager tpm1 = c1.getTransportPropertyManager(); @@ -1174,28 +1459,47 @@ public class IntroductionIntegrationTest } private void assertDefaultUiMessages() throws DbException { - Collection messages = - db0.transactionWithResult(true, txn -> introductionManager0 - .getMessageHeaders(txn, contactId1From0)); + Collection messages = getMessages1From0(); assertEquals(2, messages.size()); assertMessagesAreAcked(messages); - messages = db0.transactionWithResult(true, txn -> - introductionManager0.getMessageHeaders(txn, contactId2From0)); + messages = getMessages2From0(); assertEquals(2, messages.size()); assertMessagesAreAcked(messages); - messages = db1.transactionWithResult(true, txn -> - introductionManager1.getMessageHeaders(txn, contactId0From1)); + messages = getMessages0From1(); assertEquals(2, messages.size()); assertMessagesAreAcked(messages); - messages = db2.transactionWithResult(true, txn -> - introductionManager2.getMessageHeaders(txn, contactId0From2)); + messages = getMessages0From2(); assertEquals(2, messages.size()); assertMessagesAreAcked(messages); } + private Collection getMessages1From0() + throws DbException { + return db0.transactionWithResult(true, txn -> introductionManager0 + .getMessageHeaders(txn, contactId1From0)); + } + + private Collection getMessages2From0() + throws DbException { + return db0.transactionWithResult(true, txn -> introductionManager0 + .getMessageHeaders(txn, contactId2From0)); + } + + private Collection getMessages0From1() + throws DbException { + return db1.transactionWithResult(true, txn -> introductionManager1 + .getMessageHeaders(txn, contactId0From1)); + } + + private Collection getMessages0From2() + throws DbException { + return db2.transactionWithResult(true, txn -> introductionManager2 + .getMessageHeaders(txn, contactId0From2)); + } + private void assertMessagesAreAcked( Collection messages) { for (ConversationMessageHeader msg : messages) { diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java index c5f69d771..fee6261db 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java @@ -25,6 +25,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; +import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.TestTransportConnectionReader; import org.briarproject.bramble.test.TestTransportConnectionWriter; @@ -123,11 +124,14 @@ public abstract class BriarIntegrationTest