Delete private messages when their timers expire (needs UI support).

This commit is contained in:
akwizgran
2021-02-25 15:56:29 +00:00
committed by Torsten Grote
parent c89bde08db
commit 240e619248
16 changed files with 710 additions and 145 deletions

View File

@@ -1,11 +1,15 @@
package org.briarproject.briar.messaging;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
@@ -15,17 +19,27 @@ import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.test.BriarIntegrationTest;
import org.briarproject.briar.test.BriarIntegrationTestComponent;
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.cleanup.CleanupManager.BATCH_DELAY_MS;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
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.messaging.MessagingConstants.MISSING_ATTACHMENT_CLEANUP_DURATION_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class AutoDeleteIntegrationTest
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
@@ -39,18 +53,42 @@ public class AutoDeleteIntegrationTest
c0 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t0Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c0);
c1 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t1Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c1);
c2 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t2Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c2);
// Use different times to avoid creating identical messages that are
// treated as redundant copies of the same message (#1907)
try {
long now = System.currentTimeMillis();
c0.getTimeTravel().setCurrentTimeMillis(now);
c1.getTimeTravel().setCurrentTimeMillis(now + 1);
c2.getTimeTravel().setCurrentTimeMillis(now + 2);
} catch (InterruptedException e) {
fail();
}
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
// Run the initial cleanup task that was scheduled at startup
c0.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
c1.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
c2.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
}
@Test
@@ -91,6 +129,8 @@ public class AutoDeleteIntegrationTest
assertEquals(NO_AUTO_DELETE_TIMER, h0.getAutoDeleteTimer());
// Sync the message to 1
sync0To1(1, true);
// Sync the ack to 0
ack1To0(1);
// The message should have been added to 1's view of the conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
@@ -106,6 +146,78 @@ public class AutoDeleteIntegrationTest
getAutoDeleteTimer(c1, contactId0From1));
}
@Test
public void testNonDefaultTimer() throws Exception {
// Set 0's timer
setAutoDeleteTimer(c0, contactId1From0, MIN_AUTO_DELETE_TIMER_MS);
// 0 should be using the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c0, contactId1From0));
// 1 should still be using the default timer
assertEquals(NO_AUTO_DELETE_TIMER,
getAutoDeleteTimer(c1, contactId0From1));
// 0 creates a message with the new timer
MessageId messageId = createMessageWithTimer(c0, contactId1From0);
// The message should have been added to 0's view of the conversation
List<ConversationMessageHeader> headers0 =
getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertEquals(messageId, headers0.get(0).getId());
// The message should have the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
headers0.get(0).getAutoDeleteTimer());
// Sync the message to 1
sync0To1(1, true);
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
// The message should have been added to 1's view of the conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertEquals(messageId, headers1.get(0).getId());
// The message should have the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
headers1.get(0).getAutoDeleteTimer());
// Both peers should be using the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c0, contactId1From0));
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c1, contactId0From1));
// Before 0's timer elapses, both peers should still see the message
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
}
@Test
public void testTimerIsMirrored() throws Exception {
// Set 0's timer
@@ -122,50 +234,450 @@ public class AutoDeleteIntegrationTest
List<ConversationMessageHeader> headers0 =
getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
ConversationMessageHeader h0 = headers0.get(0);
assertEquals(messageId0, h0.getId());
assertEquals(messageId0, headers0.get(0).getId());
// The message should have the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h0.getAutoDeleteTimer());
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
headers0.get(0).getAutoDeleteTimer());
// Sync the message to 1
sync0To1(1, true);
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
// The message should have been added to 1's view of the conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
ConversationMessageHeader h1 = headers1.get(0);
assertEquals(messageId0, h1.getId());
assertEquals(messageId0, headers1.get(0).getId());
// The message should have the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h1.getAutoDeleteTimer());
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
headers1.get(0).getAutoDeleteTimer());
// 0 and 1 should both be using the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c0, contactId1From0));
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c1, contactId0From1));
// Before 0's timer elapses, both peers should still see the message
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId0);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
// 1 creates a message
MessageId messageId1 = createMessageWithTimer(c1, contactId0From1);
// The message should have been added to 1's view of the conversation
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(2, headers1.size());
assertEquals(messageId0, headers1.get(0).getId());
assertEquals(messageId1, headers1.get(1).getId());
assertEquals(1, headers1.size());
assertEquals(messageId1, headers1.get(0).getId());
// The message should have the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
headers1.get(1).getAutoDeleteTimer());
headers1.get(0).getAutoDeleteTimer());
// Sync the message to 0
sync1To0(1, true);
// Sync the ack to 1 - this starts 1's timer
ack0To1(1);
// The message should have been added to 0's view of the conversation
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(2, headers0.size());
assertEquals(messageId0, headers0.get(0).getId());
assertEquals(messageId1, headers0.get(1).getId());
assertEquals(1, headers0.size());
assertEquals(messageId1, headers0.get(0).getId());
// The message should have the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
headers0.get(1).getAutoDeleteTimer());
headers0.get(0).getAutoDeleteTimer());
// 0 and 1 should both be using the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c0, contactId1From0));
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c1, contactId0From1));
// Before 1's timer elapses, both peers should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation but 0 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
// 0 marks the message as read - this starts 0's timer
markMessageRead(c0, contact1From0, messageId1);
// Before 0's timer elapses, 0 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
}
@Test
public void testMessageWithAttachment() throws Exception {
// Set 0's timer
setAutoDeleteTimer(c0, contactId1From0, MIN_AUTO_DELETE_TIMER_MS);
// 0 creates an attachment
AttachmentHeader attachmentHeader =
createAttachment(c0, contactId1From0);
// 0 creates a message with the new timer and the attachment
MessageId messageId = createMessageWithTimer(c0, contactId1From0,
singletonList(attachmentHeader));
// The message should have been added to 0's view of the conversation
List<ConversationMessageHeader> headers0 =
getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertEquals(messageId, headers0.get(0).getId());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
// Sync the message and the attachment to 1
sync0To1(2, true);
// Sync the acks to 0 - this starts 0's timer
ack1To0(2);
// The message should have been added to 1's view of the conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertEquals(messageId, headers1.get(0).getId());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// Before 0's timer elapses, both peers should still see the message
// and both should have the attachment
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
}
@Test
public void testPrivateMessageWithMissingAttachmentIsDeleted()
throws Exception {
// Set 0's timer
setAutoDeleteTimer(c0, contactId1From0, MIN_AUTO_DELETE_TIMER_MS);
// 0 creates an attachment
AttachmentHeader attachmentHeader =
createAttachment(c0, contactId1From0);
// 0 creates a message with the new timer and the attachment
MessageId messageId = createMessageWithTimer(c0, contactId1From0,
singletonList(attachmentHeader));
// The message should have been added to 0's view of the conversation
List<ConversationMessageHeader> headers0 =
getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertEquals(messageId, headers0.get(0).getId());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
// Unshare the attachment so it won't be synced yet
setMessageNotShared(c0, attachmentHeader.getMessageId());
// Sync the message (but not the attachment) to 1
sync0To1(1, true);
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
// The message should have been added to 1's view of the conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertEquals(messageId, headers1.get(0).getId());
// Before 0's timer elapses, both peers should still see the message
// and 0 should still have the attachment (1 hasn't received it)
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
}
@Test
public void testOrphanedAttachmentIsDeleted() throws Exception {
// Set 0's timer
setAutoDeleteTimer(c0, contactId1From0, MIN_AUTO_DELETE_TIMER_MS);
// 0 creates an attachment
AttachmentHeader attachmentHeader =
createAttachment(c0, contactId1From0);
// 0 creates a message with the new timer and the attachment
MessageId messageId = createMessageWithTimer(c0, contactId1From0,
singletonList(attachmentHeader));
// The message should have been added to 0's view of the conversation
List<ConversationMessageHeader> headers0 =
getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertEquals(messageId, headers0.get(0).getId());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
// Unshare the private message so it won't be synced yet
setMessageNotShared(c0, messageId);
// Sync the attachment (but not the message) to 1 - this starts 1's
// orphan cleanup timer
sync0To1(1, true);
// Sync the ack to 0
ack1To0(1);
// The message should not have been added to 1's view of the
// conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// Before 1's timer elapses, both peers should still have the attachment
long timerLatency =
MISSING_ATTACHMENT_CLEANUP_DURATION_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 1's timer has elapsed, 1 should no longer have the attachment
// but 0 should still have it
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// Share the private message and sync it - too late to stop 1's orphan
// cleanup timer
setMessageShared(c0, messageId);
sync0To1(1, true);
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
// The message should have been added to 1's view of the conversation
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// Before 0's timer elapses, both peers should still see the message
// and 0 should still have the attachment (1 has deleted it)
timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
}
@Test
public void testOrphanedAttachmentIsNotDeletedIfPrivateMessageArrives()
throws Exception {
// Set 0's timer
setAutoDeleteTimer(c0, contactId1From0, MIN_AUTO_DELETE_TIMER_MS);
// 0 creates an attachment
AttachmentHeader attachmentHeader =
createAttachment(c0, contactId1From0);
// 0 creates a message with the new timer and the attachment
MessageId messageId = createMessageWithTimer(c0, contactId1From0,
singletonList(attachmentHeader));
// The message should have been added to 0's view of the conversation
List<ConversationMessageHeader> headers0 =
getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertEquals(messageId, headers0.get(0).getId());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
// Unshare the private message so it won't be synced yet
setMessageNotShared(c0, messageId);
// Sync the attachment (but not the message) to 1 - this starts 1's
// orphan cleanup timer
sync0To1(1, true);
// Sync the ack to 0
ack1To0(1);
// The message should not have been added to 1's view of the
// conversation
List<ConversationMessageHeader> headers1 =
getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
// Before 1's timer elapses, both peers should still have the attachment
long timerLatency =
MISSING_ATTACHMENT_CLEANUP_DURATION_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// Share the private message and sync it - just in time to stop 1's
// orphan cleanup timer
setMessageShared(c0, messageId);
sync0To1(1, true);
// The message should have been added to 1's view of the conversation
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 1's timer has elapsed, both peers should still see the message
// and both should still have the attachment
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
// Before 0's timer elapses, both peers should still see the message
// and both should still have the attachment
timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(1, headers0.size());
assertFalse(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(1, headers1.size());
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
headers0 = getMessageHeaders(c0, contactId1From0);
assertEquals(0, headers0.size());
assertTrue(messageIsDeleted(c0, attachmentHeader.getMessageId()));
headers1 = getMessageHeaders(c1, contactId0From1);
assertEquals(0, headers1.size());
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
}
private MessageId createMessageWithoutTimer(
@@ -191,6 +703,12 @@ public class AutoDeleteIntegrationTest
private MessageId createMessageWithTimer(
BriarIntegrationTestComponent component, ContactId contactId)
throws Exception {
return createMessageWithTimer(component, contactId, emptyList());
}
private MessageId createMessageWithTimer(
BriarIntegrationTestComponent component, ContactId contactId,
List<AttachmentHeader> attachmentHeaders) throws Exception {
DatabaseComponent db = component.getDatabaseComponent();
ConversationManager conversationManager =
component.getConversationManager();
@@ -205,12 +723,37 @@ public class AutoDeleteIntegrationTest
long timer = autoDeleteManager
.getAutoDeleteTimer(txn, contactId, timestamp);
PrivateMessage m = factory.createPrivateMessage(groupId, timestamp,
"Hi!", emptyList(), timer);
"Hi!", attachmentHeaders, timer);
messagingManager.addLocalMessage(txn, m);
return m.getMessage().getId();
});
}
private AttachmentHeader createAttachment(
BriarIntegrationTestComponent component, ContactId contactId)
throws Exception {
MessagingManager messagingManager = component.getMessagingManager();
GroupId groupId = messagingManager.getConversationId(contactId);
InputStream in = new ByteArrayInputStream(getRandomBytes(1234));
return messagingManager.addLocalAttachment(groupId,
component.getClock().currentTimeMillis(), "image/jpeg", in);
}
private void setMessageNotShared(BriarIntegrationTestComponent component,
MessageId messageId) throws Exception {
DatabaseComponent db = component.getDatabaseComponent();
db.transaction(false, txn -> db.setMessageNotShared(txn, messageId));
}
private void setMessageShared(BriarIntegrationTestComponent component,
MessageId messageId) throws Exception {
DatabaseComponent db = component.getDatabaseComponent();
db.transaction(false, txn -> db.setMessageShared(txn, messageId));
}
private List<ConversationMessageHeader> getMessageHeaders(
BriarIntegrationTestComponent component, ContactId contactId)
throws Exception {
@@ -230,6 +773,26 @@ public class AutoDeleteIntegrationTest
txn -> autoDeleteManager.getAutoDeleteTimer(txn, contactId));
}
private void markMessageRead(BriarIntegrationTestComponent component,
Contact contact, MessageId messageId) throws DbException {
MessagingManager messagingManager = component.getMessagingManager();
GroupId groupId = messagingManager.getContactGroup(contact).getId();
messagingManager.setReadFlag(groupId, messageId, true);
}
private boolean messageIsDeleted(BriarIntegrationTestComponent component,
MessageId messageId) throws DbException {
DatabaseComponent db = component.getDatabaseComponent();
try {
db.transaction(true, txn -> db.getMessage(txn, messageId));
return false;
} catch (MessageDeletedException e) {
return true;
}
}
@SuppressWarnings({"UseCompareMethod", "Java8ListSort"}) // Animal Sniffer
private List<ConversationMessageHeader> sortHeaders(
Collection<ConversationMessageHeader> in) {

View File

@@ -8,7 +8,6 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -30,10 +29,7 @@ import javax.annotation.Nullable;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS;
@@ -310,56 +306,6 @@ public class MessagingManagerIntegrationTest
}
}
@Test
public void testDeleteSomeAttachment() throws Exception {
// send one message with attachment
AttachmentHeader h = addAttachment(c0);
PrivateMessage m =
sendMessage(c0, c1, getRandomString(42), singletonList(h));
// attachment exists on both devices, state set to PENDING for receiver
db1.transaction(false, txn -> {
db1.getMessage(txn, h.getMessageId());
db1.setMessageState(txn, h.getMessageId(), PENDING);
});
// deleting succeeds for sender
Set<MessageId> toDelete = singleton(m.getMessage().getId());
DeletionResult result0 = db0.transactionWithResult(false, txn ->
messagingManager0.deleteMessages(txn, contactId, toDelete));
assertTrue(result0.allDeleted());
// deleting message fails for receiver,
// because attachment is not yet delivered
DeletionResult result1 = db1.transactionWithResult(false, txn ->
messagingManager1.deleteMessages(txn, contactId, toDelete));
assertFalse(result1.allDeleted());
assertTrue(result1.hasNotFullyDownloaded());
// deliver attachment
db1.transaction(false,
txn -> db1.setMessageState(txn, h.getMessageId(), DELIVERED));
// deleting message and attachment works for sender now
assertTrue(db1.transactionWithResult(false, txn ->
messagingManager1.deleteMessages(txn, contactId, toDelete))
.allDeleted());
// attachment was deleted on both devices
try {
db0.transaction(true, txn -> db0.getMessage(txn, h.getMessageId()));
fail();
} catch (MessageDeletedException e) {
// expected
}
try {
db1.transaction(true, txn -> db1.getMessage(txn, h.getMessageId()));
fail();
} catch (MessageDeletedException e) {
// expected
}
}
@Test
public void testDeletingEmptySet() throws Exception {
assertTrue(db0.transactionWithResult(false, txn ->