Add auto-deletion timer to private messages.

This commit is contained in:
akwizgran
2020-11-19 12:57:07 +00:00
committed by Torsten Grote
parent 629cff20a3
commit dba85debfa
18 changed files with 208 additions and 75 deletions

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.api.autodelete;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
public interface AutoDeleteConstants {
long MIN_AUTO_DELETE_TIMER_MS = MINUTES.toMillis(1);
long MAX_AUTO_DELETE_TIMER_MS = DAYS.toMillis(365);
}

View File

@@ -138,6 +138,7 @@ import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.android.view.AuthorView.setAvatar; import static org.briarproject.briar.android.view.AuthorView.setAvatar;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -268,15 +269,11 @@ public class ConversationActivity extends BriarActivity
ImagePreview imagePreview = findViewById(R.id.imagePreview); ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView, sendController = new TextAttachmentController(textInputView,
imagePreview, this, viewModel); imagePreview, this, viewModel);
viewModel.hasImageSupport().observe(this, new Observer<Boolean>() { observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
@Override if (format != null && format != TEXT) {
public void onChanged(@Nullable Boolean hasSupport) { // TODO: remove cast when removing feature flag
if (hasSupport != null && hasSupport) { ((TextAttachmentController) sendController)
// TODO: remove cast when removing feature flag .setImagesSupported();
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().removeObserver(this);
}
} }
}); });
} else { } else {
@@ -640,8 +637,8 @@ public class ConversationActivity extends BriarActivity
supportFinishAfterTransition(); supportFinishAfterTransition();
} }
} else if (e instanceof ConversationMessageReceivedEvent) { } else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent p = ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent) e; (ConversationMessageReceivedEvent<?>) e;
if (p.getContactId().equals(contactId)) { if (p.getContactId().equals(contactId)) {
LOG.info("Message received, adding"); LOG.info("Message received, adding");
onNewConversationMessage(p.getMessageHeader()); onNewConversationMessage(p.getMessageHeader());

View File

@@ -38,6 +38,7 @@ import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -57,11 +58,14 @@ import static androidx.lifecycle.Transformations.map;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
@NotNullByDefault @NotNullByDefault
public class ConversationViewModel extends DbViewModel public class ConversationViewModel extends DbViewModel
@@ -92,7 +96,7 @@ public class ConversationViewModel extends DbViewModel
private final LiveData<String> contactName = map(contactItem, c -> private final LiveData<String> contactName = map(contactItem, c ->
UiUtils.getContactDisplayName(c.getContact())); UiUtils.getContactDisplayName(c.getContact()));
private final LiveData<GroupId> messagingGroupId; private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<Boolean> imageSupport = private final MutableLiveData<PrivateMessageFormat> privateMessageFormat =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveEvent<Boolean> showImageOnboarding = private final MutableLiveEvent<Boolean> showImageOnboarding =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@@ -241,11 +245,11 @@ public class ConversationViewModel extends DbViewModel
// messagingGroupId is loaded with the contact // messagingGroupId is loaded with the contact
observeForeverOnce(messagingGroupId, groupId -> { observeForeverOnce(messagingGroupId, groupId -> {
requireNonNull(groupId); requireNonNull(groupId);
observeForeverOnce(imageSupport, hasImageSupport -> { // TODO: Use the timer duration that was fetched when checking the
requireNonNull(hasImageSupport); // message format
createMessage(groupId, text, headers, timestamp, observeForeverOnce(privateMessageFormat, format ->
hasImageSupport); createMessage(groupId, text, headers, timestamp,
}); format));
}); });
} }
@@ -275,10 +279,10 @@ public class ConversationViewModel extends DbViewModel
@DatabaseExecutor @DatabaseExecutor
private void checkFeaturesAndOnboarding(ContactId c) throws DbException { private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images are supported // check if images and auto-deletion are supported
boolean imagesSupported = db.transactionWithResult(true, txn -> PrivateMessageFormat format = db.transactionWithResult(true, txn ->
messagingManager.contactSupportsImages(txn, c)); messagingManager.getContactMessageFormat(txn, c));
imageSupport.postValue(imagesSupported); privateMessageFormat.postValue(format);
// check if introductions are supported // check if introductions are supported
Collection<Contact> contacts = contactManager.getContacts(); Collection<Contact> contacts = contactManager.getContacts();
@@ -287,7 +291,7 @@ public class ConversationViewModel extends DbViewModel
// we only show one onboarding dialog at a time // we only show one onboarding dialog at a time
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE); Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (imagesSupported && if (format != TEXT &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) { settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
onOnboardingShown(SHOW_ONBOARDING_IMAGE); onOnboardingShown(SHOW_ONBOARDING_IMAGE);
showImageOnboarding.postEvent(true); showImageOnboarding.postEvent(true);
@@ -308,15 +312,20 @@ public class ConversationViewModel extends DbViewModel
@UiThread @UiThread
private void createMessage(GroupId groupId, @Nullable String text, private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long timestamp, List<AttachmentHeader> headers, long timestamp,
boolean hasImageSupport) { PrivateMessageFormat format) {
// TODO: Move this inside the DB transaction that stores the message
// so we can look up the timer duration (if needed) in the same txn
try { try {
PrivateMessage pm; PrivateMessage pm;
if (hasImageSupport) { if (format == TEXT) {
pm = privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
} else if (format == TEXT_IMAGES) {
pm = privateMessageFactory.createPrivateMessage(groupId, pm = privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers); timestamp, text, headers);
} else { } else {
pm = privateMessageFactory.createLegacyPrivateMessage( pm = privateMessageFactory.createPrivateMessage(groupId,
groupId, timestamp, requireNonNull(text)); timestamp, text, headers, MIN_AUTO_DELETE_TIMER_MS);
} }
storeMessage(pm); storeMessage(pm);
} catch (FormatException e) { } catch (FormatException e) {
@@ -336,7 +345,8 @@ public class ConversationViewModel extends DbViewModel
PrivateMessageHeader h = new PrivateMessageHeader( PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(), message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false, message.getTimestamp(), true, true, false, false,
m.hasText(), m.getAttachmentHeaders()); m.hasText(), m.getAttachmentHeaders(),
m.getAutoDeleteTimer());
// TODO add text to cache when available here // TODO add text to cache when available here
addedHeader.postEvent(h); addedHeader.postEvent(h);
} catch (DbException e) { } catch (DbException e) {
@@ -357,8 +367,8 @@ public class ConversationViewModel extends DbViewModel
return contactName; return contactName;
} }
LiveData<Boolean> hasImageSupport() { LiveData<PrivateMessageFormat> getPrivateMessageFormat() {
return imageSupport; return privateMessageFormat;
} }
LiveEvent<Boolean> showImageOnboarding() { LiveEvent<Boolean> showImageOnboarding() {

View File

@@ -32,7 +32,7 @@ public interface MessagingManager extends ConversationClient {
/** /**
* The current minor version of the messaging client. * The current minor version of the messaging client.
*/ */
int MINOR_VERSION = 2; int MINOR_VERSION = 3;
/** /**
* Stores a local private message. * Stores a local private message.
@@ -70,12 +70,8 @@ public interface MessagingManager extends ConversationClient {
String getMessageText(MessageId m) throws DbException; String getMessageText(MessageId m) throws DbException;
/** /**
* Returns true if the contact with the given {@link ContactId} does support * Returns the private message format supported by the given contact.
* image attachments.
* <p>
* Added: 2019-01-01
*/ */
boolean contactSupportsImages(Transaction txn, ContactId c) PrivateMessageFormat getContactMessageFormat(Transaction txn, ContactId c)
throws DbException; throws DbException;
} }

View File

@@ -9,44 +9,65 @@ import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class PrivateMessage { public class PrivateMessage {
private final Message message; private final Message message;
private final boolean legacyFormat, hasText; private final boolean hasText;
private final List<AttachmentHeader> attachmentHeaders; private final List<AttachmentHeader> attachmentHeaders;
private final long autoDeleteTimer;
private final PrivateMessageFormat format;
/** /**
* Constructor for private messages in the legacy format, which does not * Constructor for private messages in the
* support attachments. * {@link PrivateMessageFormat#TEXT TEXT} format.
*/ */
public PrivateMessage(Message message) { public PrivateMessage(Message message) {
this.message = message; this.message = message;
legacyFormat = true;
hasText = true; hasText = true;
attachmentHeaders = emptyList(); attachmentHeaders = emptyList();
autoDeleteTimer = -1;
format = TEXT;
} }
/** /**
* Constructor for private messages in the current format, which supports * Constructor for private messages in the
* attachments. * {@link PrivateMessageFormat#TEXT_IMAGES TEXT_IMAGES} format.
*/ */
public PrivateMessage(Message message, boolean hasText, public PrivateMessage(Message message, boolean hasText,
List<AttachmentHeader> headers) { List<AttachmentHeader> headers) {
this.message = message; this.message = message;
this.hasText = hasText; this.hasText = hasText;
this.attachmentHeaders = headers; this.attachmentHeaders = headers;
legacyFormat = false; autoDeleteTimer = -1;
format = TEXT_IMAGES;
}
/**
* Constructor for private messages in the
* {@link PrivateMessageFormat#TEXT_IMAGES_AUTO_DELETE TEXT_IMAGES_AUTO_DELETE}
* format.
*/
public PrivateMessage(Message message, boolean hasText,
List<AttachmentHeader> headers, long autoDeleteTimer) {
this.message = message;
this.hasText = hasText;
this.attachmentHeaders = headers;
this.autoDeleteTimer = autoDeleteTimer;
format = TEXT_IMAGES_AUTO_DELETE;
} }
public Message getMessage() { public Message getMessage() {
return message; return message;
} }
public boolean isLegacyFormat() { public PrivateMessageFormat getFormat() {
return legacyFormat; return format;
} }
public boolean hasText() { public boolean hasText() {
@@ -56,4 +77,8 @@ public class PrivateMessage {
public List<AttachmentHeader> getAttachmentHeaders() { public List<AttachmentHeader> getAttachmentHeaders() {
return attachmentHeaders; return attachmentHeaders;
} }
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
} }

View File

@@ -19,4 +19,7 @@ public interface PrivateMessageFactory {
@Nullable String text, List<AttachmentHeader> headers) @Nullable String text, List<AttachmentHeader> headers)
throws FormatException; throws FormatException;
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
@Nullable String text, List<AttachmentHeader> headers,
long autoDeleteTimer) throws FormatException;
} }

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.api.messaging;
public enum PrivateMessageFormat {
/**
* First version of the private message format, which doesn't support
* image attachments or auto-deletion.
*/
TEXT,
/**
* Second version of the private message format, which supports image
* attachments but not auto-deletion. Support for this format was
* added in client version 0.1.
*/
TEXT_IMAGES,
/**
* Third version of the private message format, which supports image
* attachments and auto-deletion. Support for this format was added
* in client version 0.3.
*/
TEXT_IMAGES_AUTO_DELETE
}

View File

@@ -17,13 +17,16 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
private final boolean hasText; private final boolean hasText;
private final List<AttachmentHeader> attachmentHeaders; private final List<AttachmentHeader> attachmentHeaders;
private final long autoDeleteTimer;
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp, public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
boolean hasText, List<AttachmentHeader> headers) { boolean hasText, List<AttachmentHeader> headers,
long autoDeleteTimer) {
super(id, groupId, timestamp, local, read, sent, seen); super(id, groupId, timestamp, local, read, sent, seen);
this.hasText = hasText; this.hasText = hasText;
this.attachmentHeaders = headers; this.attachmentHeaders = headers;
this.autoDeleteTimer = autoDeleteTimer;
} }
public boolean hasText() { public boolean hasText() {
@@ -34,9 +37,12 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
return attachmentHeaders; return attachmentHeaders;
} }
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
@Override @Override
public <T> T accept(ConversationMessageVisitor<T> v) { public <T> T accept(ConversationMessageVisitor<T> v) {
return v.visitPrivateMessageHeader(this); return v.visitPrivateMessageHeader(this);
} }
} }

View File

@@ -11,4 +11,5 @@ interface MessagingConstants {
String MSG_KEY_MSG_TYPE = "messageType"; String MSG_KEY_MSG_TYPE = "messageType";
String MSG_KEY_HAS_TEXT = "hasText"; String MSG_KEY_HAS_TEXT = "hasText";
String MSG_KEY_ATTACHMENT_HEADERS = "attachmentHeaders"; String MSG_KEY_ATTACHMENT_HEADERS = "attachmentHeaders";
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
} }

View File

@@ -36,6 +36,7 @@ import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult; import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent; import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
@@ -59,11 +60,15 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE; import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_DESCRIPTOR_LENGTH; import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_DESCRIPTOR_LENGTH;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT; import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE; import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
import static org.briarproject.briar.messaging.MessagingConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.messaging.MessagingConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_AUTO_DELETE_TIMER;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
@@ -193,9 +198,10 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP); long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
boolean local = meta.getBoolean(MSG_KEY_LOCAL); boolean local = meta.getBoolean(MSG_KEY_LOCAL);
boolean read = meta.getBoolean(MSG_KEY_READ); boolean read = meta.getBoolean(MSG_KEY_READ);
long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER, -1L);
PrivateMessageHeader header = PrivateMessageHeader header =
new PrivateMessageHeader(m.getId(), groupId, timestamp, local, new PrivateMessageHeader(m.getId(), groupId, timestamp, local,
read, false, false, hasText, headers); read, false, false, hasText, headers, timer);
ContactId contactId = getContactId(txn, groupId); ContactId contactId = getContactId(txn, groupId);
PrivateMessageReceivedEvent event = PrivateMessageReceivedEvent event =
new PrivateMessageReceivedEvent(header, contactId); new PrivateMessageReceivedEvent(header, contactId);
@@ -204,8 +210,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
} }
private List<AttachmentHeader> parseAttachmentHeaders(GroupId g, private List<AttachmentHeader> parseAttachmentHeaders(GroupId g,
BdfDictionary meta) BdfDictionary meta) throws FormatException {
throws FormatException {
BdfList attachmentHeaders = meta.getList(MSG_KEY_ATTACHMENT_HEADERS); BdfList attachmentHeaders = meta.getList(MSG_KEY_ATTACHMENT_HEADERS);
int length = attachmentHeaders.size(); int length = attachmentHeaders.size();
List<AttachmentHeader> headers = new ArrayList<>(length); List<AttachmentHeader> headers = new ArrayList<>(length);
@@ -232,7 +237,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
meta.put(MSG_KEY_TIMESTAMP, m.getMessage().getTimestamp()); meta.put(MSG_KEY_TIMESTAMP, m.getMessage().getTimestamp());
meta.put(MSG_KEY_LOCAL, true); meta.put(MSG_KEY_LOCAL, true);
meta.put(MSG_KEY_READ, true); meta.put(MSG_KEY_READ, true);
if (!m.isLegacyFormat()) { if (m.getFormat() != TEXT) {
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE); meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
meta.put(MSG_KEY_HAS_TEXT, m.hasText()); meta.put(MSG_KEY_HAS_TEXT, m.hasText());
BdfList headers = new BdfList(); BdfList headers = new BdfList();
@@ -241,6 +246,10 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
BdfList.of(a.getMessageId(), a.getContentType())); BdfList.of(a.getMessageId(), a.getContentType()));
} }
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers); meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
if (m.getFormat() == TEXT_IMAGES_AUTO_DELETE) {
long timer = m.getAutoDeleteTimer();
if (timer != -1) meta.put(MSG_KEY_AUTO_DELETE_TIMER, timer);
}
} }
// Mark attachments as shared and permanent now we're ready to send // Mark attachments as shared and permanent now we're ready to send
for (AttachmentHeader a : m.getAttachmentHeaders()) { for (AttachmentHeader a : m.getAttachmentHeaders()) {
@@ -353,12 +362,13 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
if (messageType == null) { if (messageType == null) {
headers.add(new PrivateMessageHeader(id, g, timestamp, headers.add(new PrivateMessageHeader(id, g, timestamp,
local, read, s.isSent(), s.isSeen(), true, local, read, s.isSent(), s.isSeen(), true,
emptyList())); emptyList(), -1));
} else { } else {
boolean hasText = meta.getBoolean(MSG_KEY_HAS_TEXT); boolean hasText = meta.getBoolean(MSG_KEY_HAS_TEXT);
long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER, -1L);
headers.add(new PrivateMessageHeader(id, g, timestamp, headers.add(new PrivateMessageHeader(id, g, timestamp,
local, read, s.isSent(), s.isSeen(), hasText, local, read, s.isSent(), s.isSeen(), hasText,
parseAttachmentHeaders(g, meta))); parseAttachmentHeaders(g, meta), timer));
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
@@ -399,12 +409,13 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
} }
@Override @Override
public boolean contactSupportsImages(Transaction txn, ContactId c) public PrivateMessageFormat getContactMessageFormat(Transaction txn,
throws DbException { ContactId c) throws DbException {
int minorVersion = clientVersioningManager int minorVersion = clientVersioningManager
.getClientMinorVersion(txn, c, CLIENT_ID, 0); .getClientMinorVersion(txn, c, CLIENT_ID, 0);
// support was added in 0.1 if (minorVersion >= 3) return TEXT_IMAGES_AUTO_DELETE;
return minorVersion > 0; else if (minorVersion >= 1) return TEXT_IMAGES;
else return TEXT;
} }
@Override @Override

View File

@@ -47,21 +47,42 @@ class PrivateMessageFactoryImpl implements PrivateMessageFactory {
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp, public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
@Nullable String text, List<AttachmentHeader> headers) @Nullable String text, List<AttachmentHeader> headers)
throws FormatException { throws FormatException {
// Validate the arguments validateTextAndAttachmentHeaders(text, headers);
BdfList attachmentList = serialiseAttachmentHeaders(headers);
// Serialise the message
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
Message m = clientHelper.createMessage(groupId, timestamp, body);
return new PrivateMessage(m, text != null, headers, -1);
}
@Override
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
@Nullable String text, List<AttachmentHeader> headers,
long autoDeleteTimer) throws FormatException {
validateTextAndAttachmentHeaders(text, headers);
BdfList attachmentList = serialiseAttachmentHeaders(headers);
// Serialise the message
Long timer = autoDeleteTimer == -1 ? null : autoDeleteTimer;
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList, timer);
Message m = clientHelper.createMessage(groupId, timestamp, body);
return new PrivateMessage(m, text != null, headers, autoDeleteTimer);
}
private void validateTextAndAttachmentHeaders(@Nullable String text,
List<AttachmentHeader> headers) {
if (text == null) { if (text == null) {
if (headers.isEmpty()) throw new IllegalArgumentException(); if (headers.isEmpty()) throw new IllegalArgumentException();
} else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) { } else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
// Serialise the attachment headers }
private BdfList serialiseAttachmentHeaders(List<AttachmentHeader> headers) {
BdfList attachmentList = new BdfList(); BdfList attachmentList = new BdfList();
for (AttachmentHeader a : headers) { for (AttachmentHeader a : headers) {
attachmentList.add( attachmentList.add(
BdfList.of(a.getMessageId(), a.getContentType())); BdfList.of(a.getMessageId(), a.getContentType()));
} }
// Serialise the message return attachmentList;
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
Message m = clientHelper.createMessage(groupId, timestamp, body);
return new PrivateMessage(m, text != null, headers);
} }
} }

View File

@@ -24,6 +24,8 @@ import java.io.InputStream;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.autodelete.AutoDeleteConstants.MAX_AUTO_DELETE_TIMER_MS;
import static org.briarproject.bramble.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@@ -37,6 +39,7 @@ import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT; import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE; import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_AUTO_DELETE_TIMER;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
@@ -114,8 +117,11 @@ class PrivateMessageValidator implements MessageValidator {
private BdfMessageContext validatePrivateMessage(Message m, BdfList body) private BdfMessageContext validatePrivateMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
// Message type, optional private message text, attachment headers // Version 0.1: Message type, optional private message text,
checkSize(body, 3); // attachment headers.
// Version 0.2: Message type, optional private message text,
// attachment headers, optional auto-delete timer.
checkSize(body, 3, 4);
String text = body.getOptionalString(1); String text = body.getOptionalString(1);
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH); checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
BdfList headers = body.getList(2); BdfList headers = body.getList(2);
@@ -130,6 +136,12 @@ class PrivateMessageValidator implements MessageValidator {
String contentType = header.getString(1); String contentType = header.getString(1);
checkLength(contentType, 1, MAX_CONTENT_TYPE_BYTES); checkLength(contentType, 1, MAX_CONTENT_TYPE_BYTES);
} }
Long timer = null;
if (body.size() == 4) timer = body.getOptionalLong(3);
if (timer != null && (timer < MIN_AUTO_DELETE_TIMER_MS ||
timer > MAX_AUTO_DELETE_TIMER_MS)) {
throw new FormatException();
}
// Return the metadata // Return the metadata
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp()); meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
@@ -138,6 +150,7 @@ class PrivateMessageValidator implements MessageValidator {
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE); meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
meta.put(MSG_KEY_HAS_TEXT, text != null); meta.put(MSG_KEY_HAS_TEXT, text != null);
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers); meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
if (timer != null) meta.put(MSG_KEY_AUTO_DELETE_TIMER, timer);
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }

View File

@@ -58,6 +58,7 @@ import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -351,14 +352,18 @@ public class TestDataCreatorImpl implements TestDataCreator {
long timestamp = clock.currentTimeMillis() - num * 60 * 1000; long timestamp = clock.currentTimeMillis() - num * 60 * 1000;
String text = getRandomText(); String text = getRandomText();
boolean local = random.nextBoolean(); boolean local = random.nextBoolean();
createPrivateMessage(contactId, groupId, text, timestamp, local); boolean autoDelete = random.nextBoolean();
createPrivateMessage(contactId, groupId, text, timestamp, local,
autoDelete);
} }
private void createPrivateMessage(ContactId contactId, GroupId groupId, private void createPrivateMessage(ContactId contactId, GroupId groupId,
String text, long timestamp, boolean local) throws DbException { String text, long timestamp, boolean local, boolean autoDelete)
throws DbException {
long timer = autoDelete ? MIN_AUTO_DELETE_TIMER_MS : -1;
try { try {
PrivateMessage m = privateMessageFactory.createPrivateMessage( PrivateMessage m = privateMessageFactory.createPrivateMessage(
groupId, timestamp, text, emptyList()); groupId, timestamp, text, emptyList(), timer);
if (local) { if (local) {
messagingManager.addLocalMessage(m); messagingManager.addLocalMessage(m);
} else { } else {

View File

@@ -19,6 +19,7 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.autodelete.AutoDeleteConstants.MAX_AUTO_DELETE_TIMER_MS;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES; import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
@@ -78,12 +79,12 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
getRandomString(MAX_CONTENT_TYPE_BYTES))); getRandomString(MAX_CONTENT_TYPE_BYTES)));
} }
PrivateMessage message = privateMessageFactory.createPrivateMessage( PrivateMessage message = privateMessageFactory.createPrivateMessage(
groupId, timestamp, text, headers); groupId, timestamp, text, headers, MAX_AUTO_DELETE_TIMER_MS);
// Check the size of the serialised message // Check the size of the serialised message
int length = message.getMessage().getRawLength(); int length = message.getMessage().getRawLength();
assertTrue(length > UniqueId.LENGTH + 8 assertTrue(length > UniqueId.LENGTH + 8
+ MAX_PRIVATE_MESSAGE_TEXT_LENGTH + MAX_ATTACHMENTS_PER_MESSAGE + MAX_PRIVATE_MESSAGE_TEXT_LENGTH + MAX_ATTACHMENTS_PER_MESSAGE
* (UniqueId.LENGTH + MAX_CONTENT_TYPE_BYTES)); * (UniqueId.LENGTH + MAX_CONTENT_TYPE_BYTES) + 4);
assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES); assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
} }

View File

@@ -340,8 +340,9 @@ public class MessagingManagerIntegrationTest
BriarIntegrationTestComponent to, @Nullable String text, BriarIntegrationTestComponent to, @Nullable String text,
List<AttachmentHeader> attachments) throws Exception { List<AttachmentHeader> attachments) throws Exception {
GroupId g = from.getMessagingManager().getConversationId(contactId); GroupId g = from.getMessagingManager().getConversationId(contactId);
// TODO: Add tests for auto-deletion timer
PrivateMessage m = messageFactory.createPrivateMessage(g, PrivateMessage m = messageFactory.createPrivateMessage(g,
clock.currentTimeMillis(), text, attachments); clock.currentTimeMillis(), text, attachments, -1);
from.getMessagingManager().addLocalMessage(m); from.getMessagingManager().addLocalMessage(m);
syncMessage(from, to, contactId, 1 + attachments.size(), true); syncMessage(from, to, contactId, 1 + attachments.size(), true);
return m; return m;

View File

@@ -123,7 +123,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
PrivateMessageFactory privateMessageFactory = PrivateMessageFactory privateMessageFactory =
device.getPrivateMessageFactory(); device.getPrivateMessageFactory();
PrivateMessage message = privateMessageFactory.createPrivateMessage( PrivateMessage message = privateMessageFactory.createPrivateMessage(
groupId, timestamp, "Hi!", singletonList(attachmentHeader)); groupId, timestamp, "Hi!", singletonList(attachmentHeader), -1);
messagingManager.addLocalMessage(message); messagingManager.addLocalMessage(message);
} }

View File

@@ -41,7 +41,8 @@ internal class WebSocketControllerTest : ControllerTest() {
true, true,
true, true,
true, true,
emptyList() emptyList(),
-1
) )
private val event = PrivateMessageReceivedEvent(header, contact.id) private val event = PrivateMessageReceivedEvent(header, contact.id)
private val outputEvent = OutputEvent(EVENT_CONVERSATION_MESSAGE, event.output(text)) private val outputEvent = OutputEvent(EVENT_CONVERSATION_MESSAGE, event.output(text))

View File

@@ -4,7 +4,13 @@ import io.javalin.http.BadRequestResponse
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import io.javalin.plugin.json.JavalinJson.toJson import io.javalin.plugin.json.JavalinJson.toJson
import io.mockk.* import io.mockk.CapturingSlot
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.briar.api.identity.AuthorInfo import org.briarproject.briar.api.identity.AuthorInfo
@@ -62,7 +68,8 @@ internal class MessagingControllerImplTest : ControllerTest() {
true, true,
true, true,
true, true,
emptyList() emptyList(),
-1
) )
private val sessionId = SessionId(getRandomId()) private val sessionId = SessionId(getRandomId())
private val privateMessage = PrivateMessage(message) private val privateMessage = PrivateMessage(message)