diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index 4b504cf28..692a46a28 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -736,18 +736,10 @@ public class ConversationActivity extends BriarActivity List attachmentHeaders) { if (isNullOrEmpty(text) && attachmentHeaders.isEmpty()) throw new AssertionError(); - long timestamp = System.currentTimeMillis(); - timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); - viewModel.sendMessage(text, attachmentHeaders, timestamp); + viewModel.sendMessage(text, attachmentHeaders); textInputView.clearText(); } - private long getMinTimestampForNewMessage() { - // Don't use an earlier timestamp than the newest message - ConversationItem item = adapter.getLastItem(); - return item == null ? 0 : item.getTime() + 1; - } - private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) { if (h == null) return; addConversationItem(h.accept(visitor)); @@ -955,13 +947,11 @@ public class ConversationActivity extends BriarActivity adapter.notifyItemChanged(position, item); } runOnDbThread(() -> { - long timestamp = System.currentTimeMillis(); - timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); try { switch (item.getRequestType()) { case INTRODUCTION: respondToIntroductionRequest(item.getSessionId(), - accept, timestamp); + accept); break; case FORUM: respondToForumRequest(item.getSessionId(), accept); @@ -1037,9 +1027,8 @@ public class ConversationActivity extends BriarActivity @DatabaseExecutor private void respondToIntroductionRequest(SessionId sessionId, - boolean accept, long time) throws DbException { - introductionManager.respondToIntroduction(contactId, sessionId, time, - accept); + boolean accept) throws DbException { + introductionManager.respondToIntroduction(contactId, sessionId, accept); } @DatabaseExecutor diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index 0e2c4a60a..d81f72958 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -35,6 +35,7 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent; +import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.messaging.MessagingManager; @@ -90,6 +91,7 @@ public class ConversationViewModel extends DbViewModel private final AttachmentRetriever attachmentRetriever; private final AttachmentCreator attachmentCreator; private final AutoDeleteManager autoDeleteManager; + private final ConversationManager conversationManager; @Nullable private ContactId contactId = null; @@ -125,7 +127,8 @@ public class ConversationViewModel extends DbViewModel PrivateMessageFactory privateMessageFactory, AttachmentRetriever attachmentRetriever, AttachmentCreator attachmentCreator, - AutoDeleteManager autoDeleteManager) { + AutoDeleteManager autoDeleteManager, + ConversationManager conversationManager) { super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.db = db; this.eventBus = eventBus; @@ -137,6 +140,7 @@ public class ConversationViewModel extends DbViewModel this.attachmentRetriever = attachmentRetriever; this.attachmentCreator = attachmentCreator; this.autoDeleteManager = autoDeleteManager; + this.conversationManager = conversationManager; messagingGroupId = map(contactItem, c -> messagingManager.getContactGroup(c.getContact()).getId()); contactDeleted.setValue(false); @@ -244,14 +248,13 @@ public class ConversationViewModel extends DbViewModel } @UiThread - void sendMessage(@Nullable String text, - List headers, long timestamp) { + void sendMessage(@Nullable String text, List headers) { // messagingGroupId is loaded with the contact observeForeverOnce(messagingGroupId, groupId -> { requireNonNull(groupId); observeForeverOnce(privateMessageFormat, format -> storeMessage(requireNonNull(contactId), groupId, text, - headers, timestamp, format)); + headers, format)); }); } @@ -313,8 +316,10 @@ public class ConversationViewModel extends DbViewModel private PrivateMessage createMessage(Transaction txn, ContactId c, GroupId groupId, @Nullable String text, - List headers, long timestamp, - PrivateMessageFormat format) throws DbException { + List headers, PrivateMessageFormat format) + throws DbException { + long timestamp = + conversationManager.getTimestampForOutgoingMessage(txn, c); try { if (format == TEXT_ONLY) { return privateMessageFactory.createLegacyPrivateMessage( @@ -335,13 +340,13 @@ public class ConversationViewModel extends DbViewModel @UiThread private void storeMessage(ContactId c, GroupId groupId, @Nullable String text, List headers, - long timestamp, PrivateMessageFormat format) { + PrivateMessageFormat format) { runOnDbThread(() -> { try { db.transaction(false, txn -> { long start = now(); PrivateMessage m = createMessage(txn, c, groupId, text, - headers, timestamp, format); + headers, format); messagingManager.addLocalMessage(txn, m); logDuration(LOG, "Storing message", start); Message message = m.getMessage(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java index 94ea3a4f3..399c4f64d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java @@ -165,10 +165,9 @@ class IntroductionViewModel extends ContactsViewModel { runOnDbThread(() -> { // actually make the introduction try { - long timestamp = System.currentTimeMillis(); introductionManager.makeIntroduction( info.getContact1().getContact(), - info.getContact2().getContact(), text, timestamp); + info.getContact2().getContact(), text); } catch (DbException e) { logException(LOG, WARNING, e); androidExecutor.runOnUiThread(() -> Toast.makeText( diff --git a/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java b/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java index e44adbd7d..cecbe7a36 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java @@ -48,6 +48,13 @@ public interface ConversationManager { */ GroupCount getGroupCount(Transaction txn, ContactId c) throws DbException; + /** + * Returns a timestamp for an outgoing message, which is later than the + * timestamp of any visible message sent or received so far. + */ + long getTimestampForOutgoingMessage(Transaction txn, ContactId c) + throws DbException; + /** * Deletes all messages exchanged with the given contact. */ diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java index 8eef68eea..71c28028f 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java @@ -36,13 +36,13 @@ public interface IntroductionManager extends ConversationClient { /** * Sends two initial introduction messages. */ - void makeIntroduction(Contact c1, Contact c2, @Nullable String text, - long timestamp) throws DbException; + void makeIntroduction(Contact c1, Contact c2, @Nullable String text) + throws DbException; /** * Responds to an introduction. */ void respondToIntroduction(ContactId contactId, SessionId sessionId, - long timestamp, boolean accept) throws DbException; + boolean accept) throws DbException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java index 0444a5e15..95e500e94 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java @@ -20,11 +20,11 @@ import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.introduction.IntroductionResponse; @@ -61,7 +61,7 @@ abstract class AbstractProtocolEngine> protected final MessageEncoder messageEncoder; protected final ClientVersioningManager clientVersioningManager; protected final AutoDeleteManager autoDeleteManager; - protected final Clock clock; + protected final ConversationManager conversationManager; AbstractProtocolEngine( DatabaseComponent db, @@ -75,7 +75,7 @@ abstract class AbstractProtocolEngine> MessageEncoder messageEncoder, ClientVersioningManager clientVersioningManager, AutoDeleteManager autoDeleteManager, - Clock clock) { + ConversationManager conversationManager) { this.db = db; this.clientHelper = clientHelper; this.contactManager = contactManager; @@ -87,7 +87,7 @@ abstract class AbstractProtocolEngine> this.messageEncoder = messageEncoder; this.clientVersioningManager = clientVersioningManager; this.autoDeleteManager = autoDeleteManager; - this.clock = clock; + this.conversationManager = conversationManager; } Message sendRequestMessage(Transaction txn, PeerSession s, @@ -231,14 +231,10 @@ abstract class AbstractProtocolEngine> return !dependency.equals(lastRemoteMessageId); } - long getLocalTimestamp(long localTimestamp, long requestTimestamp) { - return Math.max( - clock.currentTimeMillis(), - Math.max( - localTimestamp, - requestTimestamp - ) + 1 - ); + long getTimestampForOutgoingMessage(Transaction txn, GroupId contactGroupId) + throws DbException { + ContactId c = getContactId(txn, contactGroupId); + return conversationManager.getTimestampForOutgoingMessage(txn, c); } private ContactId getContactId(Transaction txn, GroupId contactGroupId) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 99e20927f..8031115fe 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -22,7 +22,6 @@ import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.versioning.ClientVersioningManager; @@ -30,6 +29,7 @@ import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.introduction.IntroductionRequest; @@ -44,6 +44,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static java.lang.Math.max; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; @@ -77,16 +78,16 @@ class IntroduceeProtocolEngine AuthorManager authorManager, MessageParser messageParser, MessageEncoder messageEncoder, - Clock clock, IntroductionCrypto crypto, KeyManager keyManager, TransportPropertyManager transportPropertyManager, ClientVersioningManager clientVersioningManager, - AutoDeleteManager autoDeleteManager) { + AutoDeleteManager autoDeleteManager, + ConversationManager conversationManager) { super(db, clientHelper, contactManager, contactGroupFactory, messageTracker, identityManager, authorManager, messageParser, messageEncoder, clientVersioningManager, autoDeleteManager, - clock); + conversationManager); this.crypto = crypto; this.keyManager = keyManager; this.transportPropertyManager = transportPropertyManager; @@ -94,18 +95,18 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onRequestAction(Transaction txn, - IntroduceeSession session, @Nullable String text, long timestamp) { + IntroduceeSession session, @Nullable String text) { throw new UnsupportedOperationException(); // Invalid in this role } @Override public IntroduceeSession onAcceptAction(Transaction txn, - IntroduceeSession session, long timestamp) throws DbException { + IntroduceeSession session) throws DbException { switch (session.getState()) { case AWAIT_RESPONSES: case REMOTE_DECLINED: case REMOTE_ACCEPTED: - return onLocalAccept(txn, session, timestamp); + return onLocalAccept(txn, session); case START: case LOCAL_DECLINED: case LOCAL_ACCEPTED: @@ -119,12 +120,12 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onDeclineAction(Transaction txn, - IntroduceeSession session, long timestamp) throws DbException { + IntroduceeSession session) throws DbException { switch (session.getState()) { case AWAIT_RESPONSES: case REMOTE_DECLINED: case REMOTE_ACCEPTED: - return onLocalDecline(txn, session, timestamp); + return onLocalDecline(txn, session); case START: case LOCAL_DECLINED: case LOCAL_ACCEPTED: @@ -275,7 +276,7 @@ class IntroduceeProtocolEngine } private IntroduceeSession onLocalAccept(Transaction txn, - IntroduceeSession s, long timestamp) throws DbException { + IntroduceeSession s) throws DbException { // Mark the request message unavailable to answer markRequestsUnavailableToAnswer(txn, s); @@ -287,7 +288,7 @@ class IntroduceeProtocolEngine transportPropertyManager.getLocalProperties(txn); // Send a ACCEPT message - long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s)); + long localTimestamp = getLocalTimestamp(txn, s); Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey, localTimestamp, transportProperties, true); // Track the message @@ -312,12 +313,12 @@ class IntroduceeProtocolEngine } private IntroduceeSession onLocalDecline(Transaction txn, - IntroduceeSession s, long timestamp) throws DbException { + IntroduceeSession s) throws DbException { // Mark the request message unavailable to answer markRequestsUnavailableToAnswer(txn, s); // Send a DECLINE message - long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s)); + long localTimestamp = getLocalTimestamp(txn, s); Message sent = sendDeclineMessage(txn, s, localTimestamp, true); // Track the message @@ -415,8 +416,8 @@ class IntroduceeProtocolEngine return abort(txn, s); } if (s.getState() != AWAIT_AUTH) throw new AssertionError(); - Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac, - signature); + long localTimestamp = getLocalTimestamp(txn, s); + Message sent = sendAuthMessage(txn, s, localTimestamp, mac, signature); return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey, aliceMacKey, bobMacKey); } @@ -465,7 +466,8 @@ class IntroduceeProtocolEngine // send ACTIVATE message with a MAC byte[] mac = crypto.activateMac(s); - Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s), mac); + long localTimestamp = getLocalTimestamp(txn, s); + Message sent = sendActivateMessage(txn, s, localTimestamp, mac); // Move to AWAIT_ACTIVATE state and clear key material from session return IntroduceeSession.awaitActivate(s, m, sent, keys); @@ -516,7 +518,8 @@ class IntroduceeProtocolEngine markRequestsUnavailableToAnswer(txn, s); // Send an ABORT message - Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); + long localTimestamp = getLocalTimestamp(txn, s); + Message sent = sendAbortMessage(txn, s, localTimestamp); // Broadcast abort event for testing txn.attach(new IntroductionAbortedEvent(s.getSessionId())); @@ -531,9 +534,18 @@ class IntroduceeProtocolEngine return isInvalidDependency(s.getLastRemoteMessageId(), dependency); } - private long getLocalTimestamp(IntroduceeSession s) { - return getLocalTimestamp(s.getLocalTimestamp(), - s.getRequestTimestamp()); + /** + * Returns a timestamp for an outgoing message, which is later than the + * timestamp of any message sent or received so far in the conversation + * or the session. + */ + private long getLocalTimestamp(Transaction txn, IntroduceeSession s) + throws DbException { + long conversationTimestamp = + getTimestampForOutgoingMessage(txn, s.getContactGroupId()); + long sessionTimestamp = + max(s.getLocalTimestamp(), s.getRequestTimestamp()) + 1; + return max(conversationTimestamp, sessionTimestamp); } private void addSessionId(Transaction txn, MessageId m, SessionId sessionId) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index bd784c0b1..fc4040d1f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -12,11 +12,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; +import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; import org.briarproject.briar.introduction.IntroducerSession.Introducee; @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static java.lang.Math.max; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B; @@ -56,20 +57,20 @@ class IntroducerProtocolEngine MessageEncoder messageEncoder, ClientVersioningManager clientVersioningManager, AutoDeleteManager autoDeleteManager, - Clock clock) { + ConversationManager conversationManager) { super(db, clientHelper, contactManager, contactGroupFactory, messageTracker, identityManager, authorManager, messageParser, messageEncoder, clientVersioningManager, autoDeleteManager, - clock); + conversationManager); } @Override public IntroducerSession onRequestAction(Transaction txn, - IntroducerSession s, @Nullable String text, long timestamp) + IntroducerSession s, @Nullable String text) throws DbException { switch (s.getState()) { case START: - return onLocalRequest(txn, s, text, timestamp); + return onLocalRequest(txn, s, text); case AWAIT_RESPONSES: case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_B: @@ -89,37 +90,24 @@ class IntroducerProtocolEngine @Override public IntroducerSession onAcceptAction(Transaction txn, - IntroducerSession s, long timestamp) { + IntroducerSession s) { throw new UnsupportedOperationException(); // Invalid in this role } @Override public IntroducerSession onDeclineAction(Transaction txn, - IntroducerSession s, long timestamp) { + IntroducerSession s) { throw new UnsupportedOperationException(); // Invalid in this role } IntroducerSession onIntroduceeRemoved(Transaction txn, Introducee remainingIntroducee, IntroducerSession session) throws DbException { - // abort session - IntroducerSession s = abort(txn, session); - // reset information for introducee that was removed - Introducee introduceeA, introduceeB; - if (remainingIntroducee.author.equals(s.getIntroduceeA().author)) { - introduceeA = s.getIntroduceeA(); - introduceeB = - new Introducee(s.getSessionId(), s.getIntroduceeB().groupId, - s.getIntroduceeB().author); - } else if (remainingIntroducee.author - .equals(s.getIntroduceeB().author)) { - introduceeA = - new Introducee(s.getSessionId(), s.getIntroduceeA().groupId, - s.getIntroduceeA().author); - introduceeB = s.getIntroduceeB(); - } else throw new DbException(); + // abort session with remaining introducee + IntroducerSession s = abort(txn, session, remainingIntroducee); return new IntroducerSession(s.getSessionId(), s.getState(), - s.getRequestTimestamp(), introduceeA, introduceeB); + s.getRequestTimestamp(), s.getIntroduceeA(), + s.getIntroduceeB()); } @Override @@ -229,13 +217,11 @@ class IntroducerProtocolEngine } private IntroducerSession onLocalRequest(Transaction txn, - IntroducerSession s, @Nullable String text, long timestamp) - throws DbException { + IntroducerSession s, @Nullable String text) throws DbException { // Send REQUEST messages - long maxIntroduceeTimestamp = - Math.max(getLocalTimestamp(s, s.getIntroduceeA()), - getLocalTimestamp(s, s.getIntroduceeB())); - long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp); + long timestampA = getLocalTimestamp(txn, s, s.getIntroduceeA()); + long timestampB = getLocalTimestamp(txn, s, s.getIntroduceeB()); + long localTimestamp = max(timestampA, timestampB); Message sentA = sendRequestMessage(txn, s.getIntroduceeA(), localTimestamp, s.getIntroduceeB().author, text); Message sentB = sendRequestMessage(txn, s.getIntroduceeB(), @@ -275,11 +261,10 @@ class IntroducerProtocolEngine // Forward ACCEPT message Introducee i = getOtherIntroducee(s, m.getGroupId()); - long timestamp = getLocalTimestamp(s, i); - Message sent = - sendAcceptMessage(txn, i, timestamp, m.getEphemeralPublicKey(), - m.getAcceptTimestamp(), m.getTransportProperties(), - false); + long localTimestamp = getLocalTimestamp(txn, s, i); + Message sent = sendAcceptMessage(txn, i, localTimestamp, + m.getEphemeralPublicKey(), m.getAcceptTimestamp(), + m.getTransportProperties(), false); // Create the next state IntroducerState state = AWAIT_AUTHS; @@ -336,7 +321,8 @@ class IntroducerProtocolEngine // Forward ACCEPT message Introducee i = getOtherIntroducee(s, m.getGroupId()); - Message sent = sendAcceptMessage(txn, i, getLocalTimestamp(s, i), + long localTimestamp = getLocalTimestamp(txn, s, i); + Message sent = sendAcceptMessage(txn, i, localTimestamp, m.getEphemeralPublicKey(), m.getAcceptTimestamp(), m.getTransportProperties(), false); @@ -387,8 +373,8 @@ class IntroducerProtocolEngine // Forward DECLINE message Introducee i = getOtherIntroducee(s, m.getGroupId()); - long timestamp = getLocalTimestamp(s, i); - Message sent = sendDeclineMessage(txn, i, timestamp, false); + long localTimestamp = getLocalTimestamp(txn, s, i); + Message sent = sendDeclineMessage(txn, i, localTimestamp, false); // Create the next state IntroducerState state = START; @@ -439,8 +425,8 @@ class IntroducerProtocolEngine // Forward DECLINE message Introducee i = getOtherIntroducee(s, m.getGroupId()); - long timestamp = getLocalTimestamp(s, i); - Message sent = sendDeclineMessage(txn, i, timestamp, false); + long localTimestamp = getLocalTimestamp(txn, s, i); + Message sent = sendDeclineMessage(txn, i, localTimestamp, false); Introducee introduceeA, introduceeB; Author sender, other; @@ -480,8 +466,8 @@ class IntroducerProtocolEngine // Forward AUTH message Introducee i = getOtherIntroducee(s, m.getGroupId()); - long timestamp = getLocalTimestamp(s, i); - Message sent = sendAuthMessage(txn, i, timestamp, m.getMac(), + long localTimestamp = getLocalTimestamp(txn, s, i); + Message sent = sendAuthMessage(txn, i, localTimestamp, m.getMac(), m.getSignature()); // Move to the next state @@ -516,8 +502,8 @@ class IntroducerProtocolEngine // Forward ACTIVATE message Introducee i = getOtherIntroducee(s, m.getGroupId()); - long timestamp = getLocalTimestamp(s, i); - Message sent = sendActivateMessage(txn, i, timestamp, m.getMac()); + long localTimestamp = getLocalTimestamp(txn, s, i); + Message sent = sendActivateMessage(txn, i, localTimestamp, m.getMac()); // Move to the next state IntroducerState state = START; @@ -539,8 +525,9 @@ class IntroducerProtocolEngine IntroducerSession s, AbortMessage m) throws DbException { // Forward ABORT message Introducee i = getOtherIntroducee(s, m.getGroupId()); - long timestamp = getLocalTimestamp(s, i); - Message sent = sendAbortMessage(txn, i, timestamp); + long localTimestamp = max(i.getLocalTimestamp(), + s.getRequestTimestamp()) + 1; + Message sent = sendAbortMessage(txn, i, localTimestamp); // Broadcast abort event for testing txn.attach(new IntroductionAbortedEvent(s.getSessionId())); @@ -558,15 +545,42 @@ class IntroducerProtocolEngine s.getRequestTimestamp(), introduceeA, introduceeB); } - private IntroducerSession abort(Transaction txn, - IntroducerSession s) throws DbException { + private IntroducerSession abort(Transaction txn, IntroducerSession s, + Introducee remainingIntroducee) throws DbException { + // Broadcast abort event for testing + txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + + // Send an ABORT message to the remaining introducee + long localTimestamp = getLocalTimestamp(txn, s, remainingIntroducee); + Message sent = + sendAbortMessage(txn, remainingIntroducee, localTimestamp); + // Reset the session back to initial state + Introducee introduceeA = s.getIntroduceeA(); + Introducee introduceeB = s.getIntroduceeB(); + if (remainingIntroducee.author.equals(introduceeA.author)) { + introduceeA = new Introducee(introduceeA, sent); + introduceeB = new Introducee(s.getSessionId(), introduceeB.groupId, + introduceeB.author); + } else if (remainingIntroducee.author.equals(introduceeB.author)) { + introduceeA = new Introducee(s.getSessionId(), introduceeA.groupId, + introduceeA.author); + introduceeB = new Introducee(introduceeB, sent); + } else { + throw new DbException(); + } + return new IntroducerSession(s.getSessionId(), START, + s.getRequestTimestamp(), introduceeA, introduceeB); + } + + private IntroducerSession abort(Transaction txn, IntroducerSession s) + throws DbException { // Broadcast abort event for testing txn.attach(new IntroductionAbortedEvent(s.getSessionId())); // Send an ABORT message to both introducees - long timestampA = getLocalTimestamp(s, s.getIntroduceeA()); + long timestampA = getLocalTimestamp(txn, s, s.getIntroduceeA()); Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA); - long timestampB = getLocalTimestamp(s, s.getIntroduceeB()); + long timestampB = getLocalTimestamp(txn, s, s.getIntroduceeB()); Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB); // Reset the session back to initial state Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA); @@ -596,9 +610,17 @@ class IntroducerProtocolEngine return isInvalidDependency(expected, dependency); } - private long getLocalTimestamp(IntroducerSession s, PeerSession p) { - return getLocalTimestamp(p.getLocalTimestamp(), - s.getRequestTimestamp()); + /** + * Returns a timestamp for an outgoing message, which is later than the + * timestamp of any message sent or received so far in the conversation + * or the session. + */ + private long getLocalTimestamp(Transaction txn, IntroducerSession s, + PeerSession p) throws DbException { + long conversationTimestamp = + getTimestampForOutgoingMessage(txn, p.getContactGroupId()); + long sessionTimestamp = + max(p.getLocalTimestamp(), s.getRequestTimestamp()) + 1; + return max(conversationTimestamp, sessionTimestamp); } - } 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 48eb1df7a..f3c232330 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 @@ -315,8 +315,8 @@ class IntroductionManagerImpl extends ConversationClientImpl } @Override - public void makeIntroduction(Contact c1, Contact c2, @Nullable String text, - long timestamp) throws DbException { + public void makeIntroduction(Contact c1, Contact c2, @Nullable String text) + throws DbException { Transaction txn = db.startTransaction(false); try { // Look up the session, if there is one @@ -348,8 +348,7 @@ class IntroductionManagerImpl extends ConversationClientImpl storageId = ss.storageId; } // Handle the request action - session = introducerEngine - .onRequestAction(txn, session, text, timestamp); + session = introducerEngine.onRequestAction(txn, session, text); // Store the updated session storeSession(txn, storageId, session); db.commitTransaction(txn); @@ -362,7 +361,7 @@ class IntroductionManagerImpl extends ConversationClientImpl @Override public void respondToIntroduction(ContactId contactId, SessionId sessionId, - long timestamp, boolean accept) throws DbException { + boolean accept) throws DbException { Transaction txn = db.startTransaction(false); try { // Look up the session @@ -380,11 +379,9 @@ class IntroductionManagerImpl extends ConversationClientImpl .parseIntroduceeSession(contactGroupId, ss.bdfSession); // Handle the join or leave action if (accept) { - session = introduceeEngine - .onAcceptAction(txn, session, timestamp); + session = introduceeEngine.onAcceptAction(txn, session); } else { - session = introduceeEngine - .onDeclineAction(txn, session, timestamp); + session = introduceeEngine.onDeclineAction(txn, session); } // Store the updated session storeSession(txn, ss.storageId, session); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java index 855cce767..b9eb7aba3 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java @@ -8,16 +8,14 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import javax.annotation.Nullable; @NotNullByDefault -interface ProtocolEngine { +interface ProtocolEngine> { - S onRequestAction(Transaction txn, S session, @Nullable String text, - long timestamp) throws DbException; - - S onAcceptAction(Transaction txn, S session, long timestamp) + S onRequestAction(Transaction txn, S session, @Nullable String text) throws DbException; - S onDeclineAction(Transaction txn, S session, long timestamp) - throws DbException; + S onAcceptAction(Transaction txn, S session) throws DbException; + + S onDeclineAction(Transaction txn, S session) throws DbException; S onRequestMessage(Transaction txn, S session, RequestMessage m) throws DbException, FormatException; diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java index 7d06d5efa..1abbf6461 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationMessageHeader; @@ -20,16 +21,20 @@ import java.util.concurrent.CopyOnWriteArraySet; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static java.lang.Math.max; + @ThreadSafe @NotNullByDefault class ConversationManagerImpl implements ConversationManager { private final DatabaseComponent db; + private final Clock clock; private final Set clients; @Inject - ConversationManagerImpl(DatabaseComponent db) { + ConversationManagerImpl(DatabaseComponent db, Clock clock) { this.db = db; + this.clock = clock; clients = new CopyOnWriteArraySet<>(); } @@ -76,6 +81,14 @@ class ConversationManagerImpl implements ConversationManager { return new GroupCount(msgCount, unreadCount, latestTime); } + @Override + public long getTimestampForOutgoingMessage(Transaction txn, ContactId c) + throws DbException { + long now = clock.currentTimeMillis(); + GroupCount gc = getGroupCount(txn, c); + return max(now, gc.getLatestMsgTime() + 1); + } + @Override public DeletionResult deleteAllMessages(ContactId c) throws DbException { return db.transactionWithResult(false, txn -> { 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 feb1e65ee..bbf2434ce 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 @@ -140,11 +140,9 @@ public class IntroductionIntegrationTest addListeners(true, true); // make introduction - long time = clock.currentTimeMillis(); Contact introducee1 = contact1From0; Contact introducee2 = contact2From0; - introductionManager0 - .makeIntroduction(introducee1, introducee2, "Hi!", time); + introductionManager0.makeIntroduction(introducee1, introducee2, "Hi!"); // check that messages are tracked properly Group g1 = introductionManager0.getContactGroup(introducee1); @@ -264,11 +262,9 @@ public class IntroductionIntegrationTest addListeners(false, true); // make introduction - long time = clock.currentTimeMillis(); Contact introducee1 = contact1From0; Contact introducee2 = contact2From0; - introductionManager0 - .makeIntroduction(introducee1, introducee2, null, time); + introductionManager0.makeIntroduction(introducee1, introducee2, null); // sync request messages sync0To1(1, true); @@ -356,9 +352,8 @@ public class IntroductionIntegrationTest addListeners(true, false); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync request messages sync0To1(1, true); @@ -412,9 +407,8 @@ public class IntroductionIntegrationTest addListeners(false, true); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync request messages sync0To1(1, true); @@ -438,9 +432,8 @@ public class IntroductionIntegrationTest assertFalse(listener1.aborted); assertFalse(listener2.aborted); - time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync request messages sync0To1(1, true); @@ -457,9 +450,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync first request message sync0To1(1, true); @@ -482,7 +474,7 @@ public class IntroductionIntegrationTest // answer request manually introductionManager2.respondToIntroduction(contactId0From2, - listener2.sessionId, time, true); + listener2.sessionId, true); // sync second response and AUTH sync2To0(2, true); @@ -518,11 +510,10 @@ public class IntroductionIntegrationTest listener2.answerRequests = false; // make introduction - long time = clock.currentTimeMillis(); Contact introducee1 = contact1From0; Contact introducee2 = contact2From0; introductionManager0 - .makeIntroduction(introducee1, introducee2, null, time); + .makeIntroduction(introducee1, introducee2, null); // sync request messages sync0To1(1, true); @@ -564,7 +555,7 @@ public class IntroductionIntegrationTest // answer request manually introductionManager2.respondToIntroduction(contactId0From2, - listener2.sessionId, time, false); + listener2.sessionId, false); // now introducee2 should have returned to the START state introduceeSession = getIntroduceeSession(c2); @@ -611,9 +602,8 @@ public class IntroductionIntegrationTest addListeners(true, false); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact1From0, null, time); + .makeIntroduction(contact1From0, contact1From0, null); // sync request messages sync0To1(1, false); @@ -637,9 +627,8 @@ public class IntroductionIntegrationTest .canIntroduce(contact1From0, contact2From0)); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // no more introduction allowed while the existing one is in progress assertFalse(introductionManager0 @@ -647,7 +636,7 @@ public class IntroductionIntegrationTest // try it anyway and fail introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); } @Test @@ -661,9 +650,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync REQUEST messages sync0To1(1, true); @@ -719,9 +707,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync REQUEST messages sync0To1(1, true); @@ -766,9 +753,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync REQUEST to introducee1 sync0To1(1, true); @@ -803,9 +789,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync REQUEST to introducee1 sync0To1(1, true); @@ -838,9 +823,8 @@ public class IntroductionIntegrationTest addListeners(false, true); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync REQUEST to introducee1 sync0To1(1, true); @@ -873,9 +857,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make the introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // sync REQUEST messages sync0To1(1, true); @@ -914,9 +897,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync first request message sync0To1(1, true); @@ -943,9 +925,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync first request message sync0To1(1, true); @@ -987,9 +968,8 @@ public class IntroductionIntegrationTest @Test public void testIntroductionAfterReAddingContacts() throws Exception { // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // 0 and 1 remove and re-add each other contactManager0.removeContact(contactId1From0); @@ -1016,9 +996,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make new introduction - time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, null, time); + .makeIntroduction(contact1From0, contact2From0, null); // introduction should sync and not be INVALID or PENDING sync0To1(1, true); @@ -1032,9 +1011,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync request messages sync0To1(1, true); @@ -1147,9 +1125,8 @@ public class IntroductionIntegrationTest addListeners(true, true); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync first REQUEST message sync0To1(1, true); @@ -1292,7 +1269,7 @@ public class IntroductionIntegrationTest assertTrue(introductionManager0 .canIntroduce(contact1From0, contact2From0)); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Ho!", time); + .makeIntroduction(contact1From0, contact2From0, "Ho!"); sync0To1(1, true); sync0To2(1, true); @@ -1332,9 +1309,8 @@ public class IntroductionIntegrationTest addListeners(false, false); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync REQUEST messages sync0To1(1, true); @@ -1399,9 +1375,8 @@ public class IntroductionIntegrationTest // a new introduction is still possible assertTrue(introductionManager0 .canIntroduce(contact1From0, contact2From0)); - time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Ho!", time); + .makeIntroduction(contact1From0, contact2From0, "Ho!"); sync0To1(1, true); sync0To2(1, true); @@ -1428,9 +1403,8 @@ public class IntroductionIntegrationTest addListeners(false, false); // make introduction - long time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // sync REQUEST messages sync0To1(1, true); @@ -1458,9 +1432,8 @@ public class IntroductionIntegrationTest // a new introduction is still possible assertTrue(introductionManager0 .canIntroduce(contact1From0, contact2From0)); - time = clock.currentTimeMillis(); introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Ho!", time); + .makeIntroduction(contact1From0, contact2From0, "Ho!"); sync0To1(1, true); sync0To2(1, true); @@ -1496,9 +1469,8 @@ public class IntroductionIntegrationTest addListeners(false, false); // make introduction - long time = clock.currentTimeMillis(); - introductionManager0.makeIntroduction(contact1From0, contact2From0, - "Hi!", time); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, "Hi!"); // deleting the introduction for introducee1 will fail Collection m1From0 = getMessages1From0(); @@ -1795,16 +1767,13 @@ public class IntroductionIntegrationTest IntroductionRequest ir = introEvent.getMessageHeader(); ContactId contactId = introEvent.getContactId(); sessionId = ir.getSessionId(); - long time = clock.currentTimeMillis(); try { if (introducee == 1 && answerRequests) { - introductionManager1 - .respondToIntroduction(contactId, sessionId, - time, accept); + introductionManager1.respondToIntroduction(contactId, + sessionId, accept); } else if (introducee == 2 && answerRequests) { - introductionManager2 - .respondToIntroduction(contactId, sessionId, - time, accept); + introductionManager2.respondToIntroduction(contactId, + sessionId, accept); } } catch (DbException exception) { eventWaiter.rethrow(exception);