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 d81f72958..66884d6c1 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 @@ -328,7 +328,8 @@ public class ConversationViewModel extends DbViewModel return privateMessageFactory.createPrivateMessage(groupId, timestamp, text, headers); } else { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + timestamp); return privateMessageFactory.createPrivateMessage(groupId, timestamp, text, headers, timer); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/creation/CreateGroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/creation/CreateGroupControllerImpl.java index a5268cb5f..4f10d8fff 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/creation/CreateGroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/creation/CreateGroupControllerImpl.java @@ -173,7 +173,8 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl Contact contact = contactManager.getContact(txn, c); long timestamp = conversationManager .getTimestampForOutgoingMessage(txn, c); - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + timestamp); contexts.add(new InvitationContext(contact, timestamp, timer)); } catch (NoSuchContactException e) { // Continue diff --git a/briar-api/src/main/java/org/briarproject/briar/api/autodelete/AutoDeleteManager.java b/briar-api/src/main/java/org/briarproject/briar/api/autodelete/AutoDeleteManager.java index 251178817..8599892d1 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/autodelete/AutoDeleteManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/autodelete/AutoDeleteManager.java @@ -24,8 +24,26 @@ public interface AutoDeleteManager { */ int MINOR_VERSION = 0; - long getAutoDeleteTimer(Transaction txn, ContactId c) throws DbException; + /** + * Returns the auto-delete timer duration for the given contact, for use in + * a message with the given timestamp. The timestamp is stored. + */ + long getAutoDeleteTimer(Transaction txn, ContactId c, long timestamp) + throws DbException; + /** + * Sets the auto-delete timer duration for the given contact. + */ void setAutoDeleteTimer(Transaction txn, ContactId c, long timer) throws DbException; + + /** + * Receives an auto-delete timer duration from the given contact, carried + * in a message with the given timestamp. The local timer is set to the + * same duration unless it has been + * {@link #setAutoDeleteTimer(Transaction, ContactId, long) changed} more + * recently than the remote timer. + */ + void receiveAutoDeleteTimer(Transaction txn, ContactId c, long timer, + long timestamp) throws DbException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteConstants.java b/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteConstants.java index 7dc94eced..ff274fcc7 100644 --- a/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteConstants.java @@ -2,6 +2,28 @@ package org.briarproject.briar.autodelete; interface AutoDeleteConstants { - // Group metadata key for storing the auto-delete timer - String GROUP_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer"; + /** + * Group metadata key for storing the auto-delete timer duration. + */ + String GROUP_KEY_TIMER = "autoDeleteTimer"; + + /** + * Group metadata key for storing the timestamp of the latest incoming or + * outgoing message carrying an auto-delete timer (including a null timer). + */ + String GROUP_KEY_TIMESTAMP = "autoDeleteTimestamp"; + + /** + * Group metadata key for storing the previous auto-delete timer duration. + * This is used to decide whether a local change to the duration should be + * overwritten by a duration received from the contact. + */ + String GROUP_KEY_PREVIOUS_TIMER = "autoDeletePreviousTimer"; + + /** + * Special value for {@link #GROUP_KEY_PREVIOUS_TIMER} indicating that + * there are no local changes to the auto-delete timer duration that need + * to be compared with durations received from the contact. + */ + long NO_PREVIOUS_TIMER = 0; } diff --git a/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteManagerImpl.java index ce83a3085..e0644c63e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/autodelete/AutoDeleteManagerImpl.java @@ -17,19 +17,29 @@ import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.GroupFactory; import org.briarproject.briar.api.autodelete.AutoDeleteManager; +import java.util.logging.Logger; + import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MAX_AUTO_DELETE_TIMER_MS; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; -import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_AUTO_DELETE_TIMER; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_PREVIOUS_TIMER; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_TIMER; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_TIMESTAMP; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.NO_PREVIOUS_TIMER; @Immutable @NotNullByDefault class AutoDeleteManagerImpl implements AutoDeleteManager, OpenDatabaseHook, ContactHook { + private static final Logger LOG = + getLogger(AutoDeleteManagerImpl.class.getName()); + private final DatabaseComponent db; private final ClientHelper clientHelper; private final GroupFactory groupFactory; @@ -69,14 +79,22 @@ class AutoDeleteManagerImpl } @Override - public long getAutoDeleteTimer(Transaction txn, ContactId c) + public long getAutoDeleteTimer(Transaction txn, ContactId c, long timestamp) throws DbException { try { Group g = getGroup(db.getContact(txn, c)); BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g.getId()); - return meta.getLong(GROUP_KEY_AUTO_DELETE_TIMER, - NO_AUTO_DELETE_TIMER); + long timer = meta.getLong(GROUP_KEY_TIMER, NO_AUTO_DELETE_TIMER); + if (LOG.isLoggable(INFO)) { + LOG.info("Sending message with auto-delete timer " + timer); + } + // Update the timestamp and clear the previous timer, if any + meta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMESTAMP, timestamp), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER)); + clientHelper.mergeGroupMetadata(txn, g.getId(), meta); + return timer; } catch (FormatException e) { throw new DbException(e); } @@ -85,18 +103,60 @@ class AutoDeleteManagerImpl @Override public void setAutoDeleteTimer(Transaction txn, ContactId c, long timer) throws DbException { - if (timer != NO_AUTO_DELETE_TIMER && - (timer < MIN_AUTO_DELETE_TIMER_MS || - timer > MAX_AUTO_DELETE_TIMER_MS)) { - throw new IllegalArgumentException(); - } + validateTimer(timer); try { Group g = getGroup(db.getContact(txn, c)); - BdfDictionary meta = BdfDictionary.of( - new BdfEntry(GROUP_KEY_AUTO_DELETE_TIMER, timer)); + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, g.getId()); + long oldTimer = meta.getLong(GROUP_KEY_TIMER, NO_AUTO_DELETE_TIMER); + if (timer == oldTimer) return; + if (LOG.isLoggable(INFO)) { + LOG.info("Setting auto-delete timer to " + timer); + } + // Store the new timer and the previous timer + meta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, timer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, oldTimer)); clientHelper.mergeGroupMetadata(txn, g.getId(), meta); } catch (FormatException e) { - throw new AssertionError(e); + throw new DbException(e); + } + } + + @Override + public void receiveAutoDeleteTimer(Transaction txn, ContactId c, + long timer, long timestamp) throws DbException { + validateTimer(timer); + try { + Group g = getGroup(db.getContact(txn, c)); + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, g.getId()); + long oldTimestamp = meta.getLong(GROUP_KEY_TIMESTAMP, 0L); + if (timestamp <= oldTimestamp) return; + long oldTimer = + meta.getLong(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER); + meta = new BdfDictionary(); + if (oldTimer == NO_PREVIOUS_TIMER) { + // We don't have an unsent change. Mirror their timer + if (LOG.isLoggable(INFO)) { + LOG.info("Mirroring auto-delete timer " + timer); + } + meta.put(GROUP_KEY_TIMER, timer); + } else if (timer != oldTimer) { + // Their sent change trumps our unsent change. Mirror their + // timer and clear the previous timer to drop our unsent change + if (LOG.isLoggable(INFO)) { + LOG.info("Mirroring auto-delete timer " + timer + + " and forgetting unsent change"); + } + meta.put(GROUP_KEY_TIMER, timer); + meta.put(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER); + } + // Always update the timestamp + meta.put(GROUP_KEY_TIMESTAMP, timestamp); + clientHelper.mergeGroupMetadata(txn, g.getId(), meta); + } catch (FormatException e) { + throw new DbException(e); } } @@ -104,4 +164,12 @@ class AutoDeleteManagerImpl byte[] descriptor = c.getAuthor().getId().getBytes(); return groupFactory.createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); } + + private void validateTimer(long timer) { + if (timer != NO_AUTO_DELETE_TIMER && + (timer < MIN_AUTO_DELETE_TIMER_MS || + timer > MAX_AUTO_DELETE_TIMER_MS)) { + throw new IllegalArgumentException(); + } + } } 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 69bc47752..ee304f845 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 @@ -100,7 +100,8 @@ abstract class AbstractProtocolEngine> Message m; ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + timestamp); m = messageEncoder.encodeRequestMessage(s.getContactGroupId(), timestamp, s.getLastLocalMessageId(), author, text, timer); sendMessage(txn, REQUEST, s.getSessionId(), m, true, timer); @@ -120,7 +121,8 @@ abstract class AbstractProtocolEngine> Message m; ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + timestamp); m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(), timestamp, s.getLastLocalMessageId(), s.getSessionId(), ephemeralPublicKey, acceptTimestamp, transportProperties, @@ -141,7 +143,8 @@ abstract class AbstractProtocolEngine> Message m; ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + timestamp); m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(), timestamp, s.getLastLocalMessageId(), s.getSessionId(), timer); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java index a8d355005..96a2f17f7 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java @@ -152,9 +152,11 @@ abstract class AbstractProtocolEngine> ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { // Set auto-delete timer if manually accepting an invitation - long timer = visibleInUi - ? autoDeleteManager.getAutoDeleteTimer(txn, c) - : NO_AUTO_DELETE_TIMER; + long timer = NO_AUTO_DELETE_TIMER; + if (visibleInUi) { + timer = autoDeleteManager + .getAutoDeleteTimer(txn, c, localTimestamp); + } m = messageEncoder.encodeJoinMessage(s.getContactGroupId(), s.getPrivateGroupId(), localTimestamp, s.getLastLocalMessageId(), timer); @@ -179,9 +181,11 @@ abstract class AbstractProtocolEngine> ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { // Set auto-delete timer if manually accepting an invitation - long timer = visibleInUi - ? autoDeleteManager.getAutoDeleteTimer(txn, c) - : NO_AUTO_DELETE_TIMER; + long timer = NO_AUTO_DELETE_TIMER; + if (visibleInUi) { + timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + localTimestamp); + } m = messageEncoder.encodeLeaveMessage(s.getContactGroupId(), s.getPrivateGroupId(), localTimestamp, s.getLastLocalMessageId(), timer); diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java index 37726ac70..8c3c423a5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java @@ -142,7 +142,8 @@ abstract class ProtocolEngineImpl long localTimestamp = getTimestampForVisibleMessage(txn, s); ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + localTimestamp); m = messageEncoder.encodeInviteMessage(s.getContactGroupId(), localTimestamp, s.getLastLocalMessageId(), descriptor, text, timer); @@ -209,7 +210,8 @@ abstract class ProtocolEngineImpl long localTimestamp = getTimestampForVisibleMessage(txn, s); ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + localTimestamp); m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(), s.getShareableId(), localTimestamp, s.getLastLocalMessageId(), timer); @@ -263,7 +265,8 @@ abstract class ProtocolEngineImpl long localTimestamp = getTimestampForVisibleMessage(txn, s); ContactId c = getContactId(txn, s.getContactGroupId()); if (contactSupportsAutoDeletion(txn, c)) { - long timer = autoDeleteManager.getAutoDeleteTimer(txn, c); + long timer = autoDeleteManager.getAutoDeleteTimer(txn, c, + localTimestamp); m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(), s.getShareableId(), localTimestamp, s.getLastLocalMessageId(), timer); diff --git a/briar-core/src/test/java/org/briarproject/briar/autodelete/AutoDeleteManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/autodelete/AutoDeleteManagerImplTest.java index ef2e83f77..d63ac338e 100644 --- a/briar-core/src/test/java/org/briarproject/briar/autodelete/AutoDeleteManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/autodelete/AutoDeleteManagerImplTest.java @@ -18,12 +18,18 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MAX_AUTO_DELETE_TIMER_MS; +import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.api.autodelete.AutoDeleteManager.CLIENT_ID; import static org.briarproject.briar.api.autodelete.AutoDeleteManager.MAJOR_VERSION; -import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_AUTO_DELETE_TIMER; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_PREVIOUS_TIMER; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_TIMER; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.GROUP_KEY_TIMESTAMP; +import static org.briarproject.briar.autodelete.AutoDeleteConstants.NO_PREVIOUS_TIMER; import static org.junit.Assert.assertEquals; +// Thank you, I'm using them for readability +@SuppressWarnings("UnnecessaryLocalVariable") public class AutoDeleteManagerImplTest extends BrambleMockTestCase { private final DatabaseComponent db = context.mock(DatabaseComponent.class); @@ -35,6 +41,7 @@ public class AutoDeleteManagerImplTest extends BrambleMockTestCase { private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Contact contact = getContact(); + private final long now = System.currentTimeMillis(); private final AutoDeleteManagerImpl autoDeleteManager; @@ -113,15 +120,44 @@ public class AutoDeleteManagerImplTest extends BrambleMockTestCase { @Test public void testStoresTimer() throws Exception { Transaction txn = new Transaction(null, false); - long timer = MAX_AUTO_DELETE_TIMER_MS; - BdfDictionary meta = BdfDictionary.of( - new BdfEntry(GROUP_KEY_AUTO_DELETE_TIMER, timer)); + long oldTimer = MIN_AUTO_DELETE_TIMER_MS; + long newTimer = MAX_AUTO_DELETE_TIMER_MS; + BdfDictionary oldMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, oldTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER), + new BdfEntry(GROUP_KEY_TIMESTAMP, now)); + BdfDictionary newMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, newTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, oldTimer)); expectGetContact(txn); expectGetContactGroup(); context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(oldMeta)); oneOf(clientHelper).mergeGroupMetadata(txn, - contactGroup.getId(), meta); + contactGroup.getId(), newMeta); + }}); + + autoDeleteManager.setAutoDeleteTimer(txn, contact.getId(), newTimer); + } + + @Test + public void testDoesNotStoreTimerIfUnchanged() throws Exception { + Transaction txn = new Transaction(null, false); + long timer = MAX_AUTO_DELETE_TIMER_MS; + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, timer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER), + new BdfEntry(GROUP_KEY_TIMESTAMP, now)); + + expectGetContact(txn); + expectGetContactGroup(); + context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(meta)); }}); autoDeleteManager.setAutoDeleteTimer(txn, contact.getId(), timer); @@ -133,7 +169,10 @@ public class AutoDeleteManagerImplTest extends BrambleMockTestCase { long timer = MAX_AUTO_DELETE_TIMER_MS; BdfDictionary meta = BdfDictionary.of( new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()), - new BdfEntry(GROUP_KEY_AUTO_DELETE_TIMER, timer)); + new BdfEntry(GROUP_KEY_TIMER, timer)); + BdfDictionary newMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMESTAMP, now), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER)); expectGetContact(txn); expectGetContactGroup(); @@ -141,10 +180,12 @@ public class AutoDeleteManagerImplTest extends BrambleMockTestCase { oneOf(clientHelper).getGroupMetadataAsDictionary(txn, contactGroup.getId()); will(returnValue(meta)); + oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(), + newMeta); }}); - assertEquals(timer, - autoDeleteManager.getAutoDeleteTimer(txn, contact.getId())); + assertEquals(timer, autoDeleteManager + .getAutoDeleteTimer(txn, contact.getId(), now)); } @Test @@ -152,6 +193,44 @@ public class AutoDeleteManagerImplTest extends BrambleMockTestCase { Transaction txn = new Transaction(null, false); BdfDictionary meta = BdfDictionary.of( new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + BdfDictionary newMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMESTAMP, now), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER)); + + expectGetContact(txn); + expectGetContactGroup(); + context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(meta)); + oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(), + newMeta); + }}); + + assertEquals(NO_AUTO_DELETE_TIMER, autoDeleteManager + .getAutoDeleteTimer(txn, contact.getId(), now)); + } + + @Test + public void testIgnoresReceivedTimerWithEarlierTimestamp() + throws Exception { + testIgnoresReceivedTimerWithTimestamp(now - 1); + } + + @Test + public void testIgnoresReceivedTimerWithEqualTimestamp() throws Exception { + testIgnoresReceivedTimerWithTimestamp(now); + } + + private void testIgnoresReceivedTimerWithTimestamp(long remoteTimestamp) + throws Exception { + Transaction txn = new Transaction(null, false); + long localTimer = MIN_AUTO_DELETE_TIMER_MS; + long remoteTimer = MAX_AUTO_DELETE_TIMER_MS; + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, localTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER), + new BdfEntry(GROUP_KEY_TIMESTAMP, now)); expectGetContact(txn); expectGetContactGroup(); @@ -161,8 +240,99 @@ public class AutoDeleteManagerImplTest extends BrambleMockTestCase { will(returnValue(meta)); }}); - assertEquals(NO_AUTO_DELETE_TIMER, - autoDeleteManager.getAutoDeleteTimer(txn, contact.getId())); + autoDeleteManager.receiveAutoDeleteTimer(txn, contact.getId(), + remoteTimer, remoteTimestamp); + } + + @Test + public void testMirrorsRemoteTimestampIfNoUnsentChange() throws Exception { + Transaction txn = new Transaction(null, false); + long localTimer = MIN_AUTO_DELETE_TIMER_MS; + long remoteTimer = MAX_AUTO_DELETE_TIMER_MS; + long remoteTimestamp = now + 1; + BdfDictionary oldMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, localTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER), + new BdfEntry(GROUP_KEY_TIMESTAMP, now)); + // The timestamp should be updated and the timer should be mirrored + BdfDictionary newMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMESTAMP, remoteTimestamp), + new BdfEntry(GROUP_KEY_TIMER, remoteTimer)); + + expectGetContact(txn); + expectGetContactGroup(); + context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(oldMeta)); + oneOf(clientHelper).mergeGroupMetadata(txn, + contactGroup.getId(), newMeta); + }}); + + autoDeleteManager.receiveAutoDeleteTimer(txn, contact.getId(), + remoteTimer, remoteTimestamp); + } + + @Test + public void testDoesNotMirrorUnchangedRemoteTimestampIfUnsentChange() + throws Exception { + Transaction txn = new Transaction(null, false); + long localTimer = MIN_AUTO_DELETE_TIMER_MS; + long remoteTimer = MAX_AUTO_DELETE_TIMER_MS; + long remoteTimestamp = now + 1; + BdfDictionary oldMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, localTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, remoteTimer), + new BdfEntry(GROUP_KEY_TIMESTAMP, now)); + // The timestamp should be updated but the timer should not revert + BdfDictionary newMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMESTAMP, remoteTimestamp)); + + expectGetContact(txn); + expectGetContactGroup(); + context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(oldMeta)); + oneOf(clientHelper).mergeGroupMetadata(txn, + contactGroup.getId(), newMeta); + }}); + + autoDeleteManager.receiveAutoDeleteTimer(txn, contact.getId(), + remoteTimer, remoteTimestamp); + } + + @Test + public void testMirrorsChangedRemoteTimestampIfUnsentChange() + throws Exception { + Transaction txn = new Transaction(null, false); + long localTimer = MIN_AUTO_DELETE_TIMER_MS; + long oldRemoteTimer = MAX_AUTO_DELETE_TIMER_MS; + long newRemoteTimer = MAX_AUTO_DELETE_TIMER_MS - 1; + long remoteTimestamp = now + 1; + BdfDictionary oldMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMER, localTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, oldRemoteTimer), + new BdfEntry(GROUP_KEY_TIMESTAMP, now)); + // The timestamp should be updated , the timer should be mirrored and + // the previous timer should be cleared + BdfDictionary newMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_TIMESTAMP, remoteTimestamp), + new BdfEntry(GROUP_KEY_TIMER, newRemoteTimer), + new BdfEntry(GROUP_KEY_PREVIOUS_TIMER, NO_PREVIOUS_TIMER)); + + expectGetContact(txn); + expectGetContactGroup(); + context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(oldMeta)); + oneOf(clientHelper).mergeGroupMetadata(txn, + contactGroup.getId(), newMeta); + }}); + + autoDeleteManager.receiveAutoDeleteTimer(txn, contact.getId(), + newRemoteTimer, remoteTimestamp); } private void expectGetContact(Transaction txn) throws Exception { diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java index 8eb16aa3c..991023c4b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java @@ -256,7 +256,8 @@ abstract class AbstractProtocolEngineTest extends BrambleMockTestCase { void expectGetAutoDeleteTimer() throws Exception { context.checking(new Expectations() {{ - oneOf(autoDeleteManager).getAutoDeleteTimer(txn, contactId); + oneOf(autoDeleteManager).getAutoDeleteTimer(txn, contactId, + localTimestamp); will(returnValue(NO_AUTO_DELETE_TIMER)); }}); }