Compare commits

...

82 Commits

Author SHA1 Message Date
Sebastian Kürten
8f5c6f76ad Replace a statment lambda with method reference 2021-01-14 12:23:27 +01:00
Sebastian Kürten
927b52a6b6 ConversationSettingsFragment: improve threading logic 2021-01-14 12:21:38 +01:00
Sebastian Kürten
a77d90355b ConversationSettingsActivity: add null-related annotations 2021-01-14 12:17:35 +01:00
Sebastian Kürten
787348042e Use SwitchCompat instead of Switch 2021-01-14 12:12:42 +01:00
Sebastian Kürten
8d2640f459 ConversationSettingsFragemten: use lambdas in two places 2021-01-14 12:06:10 +01:00
Sebastian Kürten
6eb803596c ConversationSettingsFragemten: improve field section 2021-01-14 12:03:59 +01:00
Sebastian Kürten
fd544b2c28 Remove intro text; extend learn more button text 2021-01-08 10:25:33 +01:00
Sebastian Kürten
f9d6bca64e Make learn more dialog content scrollable 2021-01-08 09:59:29 +01:00
Sebastian Kürten
7aea2a99fa Reformat fragment source 2021-01-08 09:59:12 +01:00
Sebastian Kürten
322b8de5f7 Convert learn more button into blue TextView 2021-01-08 09:57:30 +01:00
Sebastian Kürten
620c9188ba Shorten intro text, break dialog text into paragraphs 2021-01-08 09:56:41 +01:00
Sebastian Kürten
c8b3a8221f Add divider, put into ScrollView 2021-01-07 16:16:30 +01:00
Sebastian Kürten
9fd27d7890 Add dialog to appear when user wants to learn more 2021-01-07 15:30:09 +01:00
Sebastian Kürten
63ae41994b Move context menu creation and processing from Activity to Fragment 2021-01-07 14:57:50 +01:00
Sebastian Kürten
7da57a1a1b Move learn-more button to left and make borderless 2021-01-07 10:41:33 +01:00
Sebastian Kürten
ea7e68d731 Delete now-unneeded layouts 2021-01-07 10:22:35 +01:00
Sebastian Kürten
c0989fb631 Make settings layout work using barrier 2021-01-06 21:45:29 +01:00
Sebastian Kürten
11ebaeaaea Start migrating the PreferenceFragment to regular Fragment with ConstrainedLayout 2021-01-06 19:23:26 +01:00
Sebastian Kürten
694ddd949f Set label for ConversationSettingsActivity 2021-01-04 17:47:56 +01:00
Sebastian Kürten
319adb6b8d Add help actions 2020-12-17 17:43:51 +01:00
Sebastian Kürten
10d76b120b Icon color, margins, positioning 2020-12-17 17:33:18 +01:00
Sebastian Kürten
b980307fcf Rename settings activity; remove other menu item 2020-12-17 17:07:28 +01:00
Sebastian Kürten
35c57cfa2c Use bomb drawable, remove hourglass 2020-12-17 16:51:29 +01:00
Sebastian Kürten
baa6d93034 Add explanation and learn more button 2020-12-17 15:56:28 +01:00
Sebastian Kürten
c23d67c765 Improve wording 2020-12-17 10:34:55 +01:00
Sebastian Kürten
86ea384f71 Add preference category, adapt text 2020-12-17 10:04:55 +01:00
Sebastian Kürten
145c1e1c64 Remove timer setting which we won't need for iteration 1 2020-12-17 09:51:08 +01:00
Sebastian Kürten
b48811e9b8 Manipulate disappearing messages settings via settings screen 2020-12-17 09:41:17 +01:00
Sebastian Kürten
87b200f0b5 Add simple draft actvity/fragment for conversation settings 2020-12-14 11:02:49 +01:00
Sebastian Kürten
e313f61b9e Add settings item for settings screen 2020-12-14 11:02:49 +01:00
akwizgran
5ca24c0c10 Merge branch '1859-menu-item-for-disappearing-messages' into '804-self-destructing-messages'
Menu item to enable/disable disappearing messages

See merge request briar/briar!1321
2020-12-11 10:44:24 +00:00
Torsten Grote
a4ea1fd257 Allow setting a self-destruct timer
This is a rough prototype of #1837 meant to make testing the UI easier.
2020-12-10 20:52:08 +01:00
Torsten Grote
630b4ff561 Merge branch '1832-mirror-remote-self-destruct-timer' into '804-self-destructing-messages'
Mirror the contact's changes to the self-destruct timer

See merge request briar/briar!1312
2020-12-04 14:59:15 +00:00
akwizgran
45c205e4ba Use Collections.sort() to satisfy Animal Sniffer. 2020-12-04 12:16:58 +00:00
akwizgran
aff649cb06 Add integration tests for timer mirroring. 2020-12-04 12:10:43 +00:00
akwizgran
501ca326d7 Add method for UI and tests to get current timer. 2020-12-04 12:10:10 +00:00
Torsten Grote
0b2a581f81 Merge branch '1832-use-conversation-timestamp' into '804-self-destructing-messages'
Add integration tests for auto-delete timer

See merge request briar/briar!1315
2020-12-03 18:48:06 +00:00
akwizgran
04cdf27a1c Update integration tests. 2020-12-03 18:00:31 +00:00
akwizgran
6bac5b08ab Don't receive auto-delete timer from remote accept message as introducee. 2020-12-03 17:58:50 +00:00
akwizgran
482c90a45e Hook up incoming messages to the auto-delete manager. 2020-12-03 17:58:50 +00:00
akwizgran
c042b1c6d0 Mirror the remote auto-delete timer. 2020-12-03 17:58:50 +00:00
akwizgran
df43a3d461 Add integration tests for auto-delete timer. 2020-12-03 17:58:10 +00:00
akwizgran
1f73137e52 Merge branch '1832-use-conversation-timestamp' into '804-self-destructing-messages'
Use latest conversation timestamp for all invitation/introduction messages

See merge request briar/briar!1310
2020-12-03 17:32:33 +00:00
Torsten Grote
e662d942f0 Merge branch '804-send-current-minor-version' into '804-self-destructing-messages'
Send current minor version of messaging client to contacts

See merge request briar/briar!1314
2020-12-03 17:24:34 +00:00
akwizgran
6c6c3ab3a8 Forwarded accept messages aren't visible to the introducee. 2020-12-03 17:12:52 +00:00
akwizgran
b8b8894f48 Only use conversation timestamp for messages that will be visible in conversation. 2020-12-03 16:33:55 +00:00
akwizgran
c9ad852aee Send current minor version of messaging client to contacts. 2020-12-03 14:15:12 +00:00
akwizgran
9d0c894fce Merge branch '1838-bomb' into '804-self-destructing-messages'
Show a bomb icon on messages with self-destruct timers

See merge request briar/briar!1313
2020-12-03 14:00:33 +00:00
Torsten Grote
2e3335ef66 Show bomb icon for messages with auto-destruct timer 2020-12-03 10:21:08 -03:00
Torsten Grote
dd216890ed Merge branch '1832-store-self-destruct-timer' into '804-self-destructing-messages'
Store local self-destruct timer duration

See merge request briar/briar!1307
2020-12-02 13:34:30 +00:00
akwizgran
238512e9bf Get timestamp for abort message in same way as other messages. 2020-12-02 11:55:52 +00:00
akwizgran
ae41e1f780 Look up auto-delete timer when creating private group invitation. 2020-12-02 11:30:54 +00:00
akwizgran
2b41700fa7 Use the right timestamp when signing private group invitation. 2020-12-02 11:08:58 +00:00
akwizgran
18f98766db Provide TransactionManager. 2020-12-01 17:38:49 +00:00
akwizgran
7e6871149b Look up conversation timestamp when creating group invitation messages. 2020-12-01 17:21:09 +00:00
akwizgran
524d6a93f1 Move lookup of latest conversation timestamp to core for blog and forum sharing. 2020-12-01 16:21:06 +00:00
akwizgran
67b9ebff8e Move lookup of latest conversation timestamp to core. 2020-12-01 16:02:42 +00:00
akwizgran
d559e821ca Add transactional variant of getGroupCount(). 2020-11-30 09:49:07 +00:00
akwizgran
d829c25717 Check that timer argument is legal before storing. 2020-11-26 13:45:58 +00:00
akwizgran
433e4e79ae Add unit tests for AutoDeleteManagerImpl. 2020-11-26 13:45:41 +00:00
akwizgran
647f179016 Implement AutoDeleteManager. 2020-11-26 13:15:01 +00:00
akwizgran
f57b16e9bf Add dummy implementation of AutoDeleteManager. 2020-11-26 12:16:34 +00:00
akwizgran
2430cc409f Refactor auto-delete code from Bramble to Briar. 2020-11-26 11:20:31 +00:00
akwizgran
8693546170 Merge branch '804-self-destructing-messages-refactoring' into '804-self-destructing-messages'
Factor out some duplicated client code

See merge request briar/briar!1304
2020-11-24 11:03:24 +00:00
akwizgran
7b97bb1f20 Rewrap lines. 2020-11-24 10:51:19 +00:00
Torsten Grote
92d04e0417 Merge branch '1831-self-destruct-timers-private-groups' into '804-self-destructing-messages'
Update private group sharing client to include a self-destruct timer in each message

See merge request briar/briar!1303
2020-11-23 17:52:20 +00:00
akwizgran
bab2b4594d Factor out methods for storing and retrieving contact ID. 2020-11-23 17:15:57 +00:00
akwizgran
029ddddd27 Factor out method for validating auto-delete timers. 2020-11-23 16:42:45 +00:00
akwizgran
3ee75643fc Update comments. 2020-11-23 16:29:06 +00:00
akwizgran
e88de213fb Add unit tests for validating auto-delete timer. 2020-11-23 16:17:36 +00:00
akwizgran
c26bad9f94 Update private group invitation client to include self-destruct timers. 2020-11-23 15:42:39 +00:00
akwizgran
bce1ce0a81 Merge branch '1830-self-destruct-timer-blogs-forums' into '804-self-destructing-messages'
Update blog and forum sharing clients to include self-destruct timers

See merge request briar/briar!1302
2020-11-23 13:56:16 +00:00
akwizgran
1601a85ad1 Merge branch '1829-self-destruct-timer-introductions' into '804-self-destructing-messages'
Update introduction client to include a self-destruct timer in each message

See merge request briar/briar!1300
2020-11-20 17:14:57 +00:00
akwizgran
b6b1bdbf82 Update blog and forum sharing clients to include self-destruct timers. 2020-11-20 17:08:55 +00:00
akwizgran
28ecece34d Update message parsing and encoding to include auto-delete timer. 2020-11-19 17:26:52 +00:00
Torsten Grote
f9e6b3ed3a Merge branch '1828-self-destruct-timer-private-messages' into '804-self-destructing-messages'
Update messaging client to include a self-destruct timer in each message

See merge request briar/briar!1299
2020-11-19 16:57:51 +00:00
akwizgran
28cd086972 Update introduction validator to support auto-delete timers. 2020-11-19 16:10:51 +00:00
akwizgran
3b6b77ccf5 Add constant for NO_AUTO_DELETE_TIMER, address review comments. 2020-11-19 15:58:33 +00:00
akwizgran
dd9c6697b2 Add unit tests for private message validation. 2020-11-19 13:40:51 +00:00
akwizgran
228907543e Fix comments in PrivateMessageValidator. 2020-11-19 13:29:57 +00:00
akwizgran
b477962321 Add integration test for auto-delete timer in private messages. 2020-11-19 13:12:02 +00:00
akwizgran
98b0f64785 Add auto-deletion timer to private messages. 2020-11-19 12:57:07 +00:00
158 changed files with 4949 additions and 1264 deletions

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.client; package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
@@ -119,4 +120,17 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap( Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException; BdfDictionary properties) throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
/**
* Stores the given contact ID in the group metadata of the given contact
* group.
*/
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
throws DbException;
} }

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.client;
public interface ContactGroupConstants {
/**
* Group metadata key for associating a contact ID with a contact group.
*/
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -6,7 +6,9 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault @NotNullByDefault
public class ValidationUtils { public class ValidationUtils {
@@ -64,4 +66,9 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size) if (dictionary != null && dictionary.size() != size)
throw new FormatException(); throw new FormatException();
} }
public static void checkRange(@Nullable Long l, long min, long max)
throws FormatException {
if (l != null && (l < min || l > max)) throw new FormatException();
}
} }

View File

@@ -2,11 +2,13 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -39,6 +41,7 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
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;
@@ -389,4 +392,27 @@ class ClientHelperImpl implements ClientHelper {
return tpMap; return tpMap;
} }
@Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
}
@Override
public void setContactId(Transaction txn, GroupId contactGroupId,
ContactId c) throws DbException {
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
try {
mergeGroupMetadata(txn, contactGroupId, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
} }

View File

@@ -5,6 +5,5 @@ interface ClientVersioningConstants {
// Metadata keys // Metadata keys
String MSG_KEY_UPDATE_VERSION = "version"; String MSG_KEY_UPDATE_VERSION = "version";
String MSG_KEY_LOCAL = "local"; String MSG_KEY_LOCAL = "local";
String GROUP_KEY_CONTACT_ID = "contactId";
} }

View File

@@ -50,7 +50,6 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
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.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -161,13 +160,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
db.addGroup(txn, g); db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary(); clientHelper.setContactId(txn, g.getId(), c.getId());
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
// Create and store the first local update // Create and store the first local update
List<ClientVersion> versions = new ArrayList<>(clients); List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions); Collections.sort(versions);
@@ -229,7 +222,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after = Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates); getVisibilities(newLocalStates, newRemoteStates);
// Call hooks for any visibilities that have changed // Call hooks for any visibilities that have changed
ContactId c = getContactId(txn, m.getGroupId()); ContactId c = clientHelper.getContactId(txn, m.getGroupId());
if (!before.equals(after)) { if (!before.equals(after)) {
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after); callVisibilityHooks(txn, contact, before, after);
@@ -521,17 +514,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
storeUpdate(txn, g, states, 1); storeUpdate(txn, g, states, 1);
} }
private ContactId getContactId(Transaction txn, GroupId g)
throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e);
}
}
private List<ClientState> updateStatesFromRemoteStates( private List<ClientState> updateStatesFromRemoteStates(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) { List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>(); Set<ClientMajorVersion> remoteSet = new HashSet<>();

View File

@@ -38,7 +38,6 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -60,8 +59,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final ClientId clientId = getClientId(); private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis(); private final long now = System.currentTimeMillis();
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
private ClientVersioningManagerImpl createInstance() { private ClientVersioningManagerImpl createInstance() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -123,8 +120,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addGroup(txn, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(), oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
groupMeta); contact.getId());
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(now)); will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now, oneOf(clientHelper).createMessage(contactGroup.getId(), now,
@@ -460,9 +457,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, oldRemoteUpdateId); oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Get contact ID // Get contact ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn, oneOf(clientHelper).getContactId(txn, contactGroup.getId());
contactGroup.getId()); will(returnValue(contact.getId()));
will(returnValue(groupMeta));
// No states or visibilities have changed // No states or visibilities have changed
}}); }});
@@ -492,10 +488,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
// Load the latest local update // Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody)); will(returnValue(oldLocalUpdateBody));
// Get client ID // Get contact ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn, oneOf(clientHelper).getContactId(txn, contactGroup.getId());
contactGroup.getId()); will(returnValue(contact.getId()));
will(returnValue(groupMeta));
// No states or visibilities have changed // No states or visibilities have changed
}}); }});
@@ -546,8 +541,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of( BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true)); new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate); oneOf(clientHelper).toList(newRemoteUpdate);
@@ -577,9 +570,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false); newLocalUpdateMeta, true, false);
// The client's visibility has changed // The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn, oneOf(clientHelper).getContactId(txn, contactGroup.getId());
contactGroup.getId()); will(returnValue(contact.getId()));
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility); oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
@@ -619,8 +611,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of( BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true)); new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate); oneOf(clientHelper).toList(newRemoteUpdate);
@@ -650,9 +640,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false); newLocalUpdateMeta, true, false);
// The client's visibility has changed // The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn, oneOf(clientHelper).getContactId(txn, contactGroup.getId());
contactGroup.getId()); will(returnValue(contact.getId()));
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE); oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);

View File

@@ -141,6 +141,16 @@
android:value="org.briarproject.briar.android.conversation.ConversationActivity" /> android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity> </activity>
<activity
android:name=".android.conversation.ConversationSettingsActivity"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
android:label="@string/disappearing_messages_title"
android:theme="@style/BriarTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity <activity
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity" android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
android:label="@string/groups_create_group_title" android:label="@string/groups_create_group_title"

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
@@ -39,6 +40,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -169,6 +171,10 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager(); AndroidWakeLockManager wakeLockManager();
TransactionManager transactionManager();
AutoDeleteManager autoDeleteManager();
void inject(SignInReminderReceiver briarService); void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService); void inject(BriarService briarService);

View File

@@ -28,6 +28,8 @@ import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment; import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsFragment;
import org.briarproject.briar.android.conversation.ImageActivity; import org.briarproject.briar.android.conversation.ImageActivity;
import org.briarproject.briar.android.conversation.ImageFragment; import org.briarproject.briar.android.conversation.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity; import org.briarproject.briar.android.forum.CreateForumActivity;
@@ -184,6 +186,8 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity); void inject(PendingContactListActivity activity);
void inject(ConversationSettingsActivity activity);
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
@@ -234,4 +238,6 @@ public interface ActivityComponent {
void inject(ImageFragment imageFragment); void inject(ImageFragment imageFragment);
void inject(ConversationSettingsFragment conversationSettingsFragment);
} }

View File

@@ -140,6 +140,8 @@ import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.android.util.UiUtils.observeOnce;
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_IMAGES_AUTO_DELETE;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -273,15 +275,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 != TEXT_ONLY) {
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 {
@@ -384,6 +382,12 @@ public class ConversationActivity extends BriarActivity
// enable alias action if available // enable alias action if available
observeOnce(viewModel.getContact(), this, contact -> observeOnce(viewModel.getContact(), this, contact ->
menu.findItem(R.id.action_set_alias).setEnabled(true)); menu.findItem(R.id.action_set_alias).setEnabled(true));
// show auto-delete timer setting only, if contacts supports it
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
boolean visible = format == TEXT_IMAGES_AUTO_DELETE;
menu.findItem(R.id.action_conversation_settings)
.setVisible(visible);
});
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@@ -405,6 +409,12 @@ public class ConversationActivity extends BriarActivity
AliasDialogFragment.newInstance().show( AliasDialogFragment.newInstance().show(
getSupportFragmentManager(), AliasDialogFragment.TAG); getSupportFragmentManager(), AliasDialogFragment.TAG);
return true; return true;
case R.id.action_conversation_settings:
if (contactId == null) return false;
intent = new Intent(this, ConversationSettingsActivity.class);
intent.putExtra(CONTACT_ID, contactId.getInt());
startActivity(intent);
return true;
case R.id.action_delete_all_messages: case R.id.action_delete_all_messages:
askToDeleteAllMessages(); askToDeleteAllMessages();
return true; return true;
@@ -657,8 +667,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());
@@ -756,18 +766,10 @@ public class ConversationActivity extends BriarActivity
List<AttachmentHeader> attachmentHeaders) { List<AttachmentHeader> attachmentHeaders) {
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty()) if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
throw new AssertionError(); throw new AssertionError();
long timestamp = System.currentTimeMillis(); viewModel.sendMessage(text, attachmentHeaders);
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
viewModel.sendMessage(text, attachmentHeaders, timestamp);
textInputView.clearText(); textInputView.clearText();
} }
private long getMinTimestampForNewMessage() {
// Don't use an earlier timestamp than the newest message
ConversationItem item = adapter.getLastItem();
return item == null ? 0 : item.getTime() + 1;
}
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) { private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
if (h == null) return; if (h == null) return;
addConversationItem(h.accept(visitor)); addConversationItem(h.accept(visitor));
@@ -972,13 +974,11 @@ public class ConversationActivity extends BriarActivity
adapter.notifyItemChanged(position, item); adapter.notifyItemChanged(position, item);
} }
runOnDbThread(() -> { runOnDbThread(() -> {
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try { try {
switch (item.getRequestType()) { switch (item.getRequestType()) {
case INTRODUCTION: case INTRODUCTION:
respondToIntroductionRequest(item.getSessionId(), respondToIntroductionRequest(item.getSessionId(),
accept, timestamp); accept);
break; break;
case FORUM: case FORUM:
respondToForumRequest(item.getSessionId(), accept); respondToForumRequest(item.getSessionId(), accept);
@@ -1054,9 +1054,8 @@ public class ConversationActivity extends BriarActivity
@DatabaseExecutor @DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId, private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException { boolean accept) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, time, introductionManager.respondToIntroduction(contactId, sessionId, accept);
accept);
} }
@DatabaseExecutor @DatabaseExecutor

View File

@@ -22,7 +22,7 @@ abstract class ConversationItem {
protected String text; protected String text;
private final MessageId id; private final MessageId id;
private final GroupId groupId; private final GroupId groupId;
private final long time; private final long time, autoDeleteTimer;
private final boolean isIncoming; private final boolean isIncoming;
private boolean read, sent, seen; private boolean read, sent, seen;
@@ -32,6 +32,7 @@ abstract class ConversationItem {
this.id = h.getId(); this.id = h.getId();
this.groupId = h.getGroupId(); this.groupId = h.getGroupId();
this.time = h.getTimestamp(); this.time = h.getTimestamp();
this.autoDeleteTimer = h.getAutoDeleteTimer();
this.read = h.isRead(); this.read = h.isRead();
this.sent = h.isSent(); this.sent = h.isSent();
this.seen = h.isSeen(); this.seen = h.isSeen();
@@ -68,6 +69,10 @@ abstract class ConversationItem {
return time; return time;
} }
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
/** /**
* Only useful for incoming messages. * Only useful for incoming messages.
*/ */

View File

@@ -12,8 +12,11 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView.ViewHolder; import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.util.StringUtils.trim; import static org.briarproject.bramble.util.StringUtils.trim;
import static org.briarproject.briar.android.util.UiUtils.formatDate; import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -26,6 +29,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
private final OutItemViewHolder outViewHolder; private final OutItemViewHolder outViewHolder;
private final TextView text; private final TextView text;
protected final TextView time; protected final TextView time;
private final View bomb;
@Nullable @Nullable
private String itemKey = null; private String itemKey = null;
@@ -38,6 +42,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
layout = v.findViewById(R.id.layout); layout = v.findViewById(R.id.layout);
text = v.findViewById(R.id.text); text = v.findViewById(R.id.text);
time = v.findViewById(R.id.time); time = v.findViewById(R.id.time);
bomb = v.findViewById(R.id.bomb);
} }
@CallSuper @CallSuper
@@ -52,6 +57,9 @@ abstract class ConversationItemViewHolder extends ViewHolder {
long timestamp = item.getTime(); long timestamp = item.getTime();
time.setText(formatDate(time.getContext(), timestamp)); time.setText(formatDate(time.getContext(), timestamp));
boolean showBomb = item.getAutoDeleteTimer() != NO_AUTO_DELETE_TIMER;
bomb.setVisibility(showBomb ? VISIBLE : GONE);
if (outViewHolder != null) outViewHolder.bind(item); if (outViewHolder != null) outViewHolder.bind(item);
} }

View File

@@ -0,0 +1,80 @@
package org.briarproject.briar.android.conversation;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationSettingsActivity extends BriarActivity implements
BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConversationViewModel viewModel;
private ContactId contactId;
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
Intent i = getIntent();
int id = i.getIntExtra(CONTACT_ID, -1);
if (id == -1) throw new IllegalStateException();
contactId = new ContactId(id);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
setContentView(R.layout.activity_conversation_settings);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class);
}
@Override
public void onResume() {
super.onResume();
// Trigger loading of contact data, noop if data was loaded already.
//
// We can only start loading data *after* we are sure
// the user has signed in. After sign-in, onCreate() isn't run again.
if (signedIn()) viewModel.setContactId(contactId);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,158 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.widget.SwitchCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationSettingsFragment extends BaseFragment {
public static final String TAG =
ConversationSettingsFragment.class.getName();
private static final Logger LOG =
Logger.getLogger(ConversationSettingsFragment.class.getName());
@Inject
ViewModelProvider.Factory viewModelFactory;
@Inject
@DatabaseExecutor
Executor dbExecutor;
@Inject
TransactionManager db;
@Inject
AutoDeleteManager autoDeleteManager;
private ConversationSettingsActivity listener;
private ConversationViewModel viewModel;
private SwitchCompat switchDisappearingMessages;
private volatile boolean disappearingMessages = false;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (ConversationSettingsActivity) context;
listener.getActivityComponent().inject(this);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView =
inflater.inflate(R.layout.fragment_conversation_settings,
container, false);
switchDisappearingMessages =
contentView.findViewById(R.id.switchDisappearingMessages);
switchDisappearingMessages
.setOnCheckedChangeListener((button, value) -> viewModel
.setAutoDeleteTimerEnabled(value));
TextView buttonLearnMore =
contentView.findViewById(R.id.buttonLearnMore);
buttonLearnMore.setOnClickListener(e -> showLearnMoreDialog());
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class);
return contentView;
}
@Override
public void onStart() {
super.onStart();
switchDisappearingMessages.setEnabled(false);
loadSettings();
}
private void loadSettings() {
observeOnce(viewModel.getContact(), this, c -> {
dbExecutor.execute(() -> {
try {
db.transaction(false, txn -> {
long timer = autoDeleteManager
.getAutoDeleteTimer(txn, c.getId());
disappearingMessages = timer != NO_AUTO_DELETE_TIMER;
});
listener.runOnUiThreadUnlessDestroyed(
this::displaySettings);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
});
}
private void displaySettings() {
switchDisappearingMessages.setChecked(disappearingMessages);
switchDisappearingMessages.setEnabled(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_help) {
showLearnMoreDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showLearnMoreDialog() {
ConversationSettingsLearnMoreDialog
dialog = new ConversationSettingsLearnMoreDialog();
dialog.show(requireActivity().getSupportFragmentManager(),
ConversationSettingsLearnMoreDialog.TAG);
}
}

View File

@@ -0,0 +1,44 @@
package org.briarproject.briar.android.conversation;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.briar.R;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
@MethodsNotNullByDefault
public class ConversationSettingsLearnMoreDialog extends DialogFragment {
final static String TAG =
ConversationSettingsLearnMoreDialog.class.getName();
static ConversationSettingsLearnMoreDialog newInstance() {
return new ConversationSettingsLearnMoreDialog();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
FragmentActivity activity = requireActivity();
AlertDialog.Builder builder =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(
R.layout.fragment_conversation_settings_learn_more, null);
builder.setView(view);
builder.setTitle(R.string.disappearing_messages_title);
builder.setNeutralButton(R.string.ok, null);
return builder.create();
}
}

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -28,10 +29,13 @@ import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
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;
@@ -50,6 +54,7 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations; import androidx.lifecycle.Transformations;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
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.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -57,12 +62,15 @@ 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.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@NotNullByDefault @NotNullByDefault
public class ConversationViewModel extends AndroidViewModel public class ConversationViewModel extends AndroidViewModel
implements EventListener, AttachmentManager { implements EventListener, AttachmentManager {
private static Logger LOG = private static final Logger LOG =
getLogger(ConversationViewModel.class.getName()); getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE = private static final String SHOW_ONBOARDING_IMAGE =
@@ -80,6 +88,8 @@ public class ConversationViewModel extends AndroidViewModel
private final PrivateMessageFactory privateMessageFactory; private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever; private final AttachmentRetriever attachmentRetriever;
private final AttachmentCreator attachmentCreator; private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
@Nullable @Nullable
private ContactId contactId = null; private ContactId contactId = null;
@@ -89,7 +99,7 @@ public class ConversationViewModel extends AndroidViewModel
private final LiveData<String> contactName = private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName); Transformations.map(contact, UiUtils::getContactDisplayName);
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<>();
@@ -112,7 +122,9 @@ public class ConversationViewModel extends AndroidViewModel
SettingsManager settingsManager, SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory, PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever, AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator) { AttachmentCreator attachmentCreator,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager) {
super(application); super(application);
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.db = db; this.db = db;
@@ -123,6 +135,8 @@ public class ConversationViewModel extends AndroidViewModel
this.privateMessageFactory = privateMessageFactory; this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever; this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator; this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
messagingGroupId = Transformations messagingGroupId = Transformations
.map(contact, c -> messagingManager.getContactGroup(c).getId()); .map(contact, c -> messagingManager.getContactGroup(c).getId());
contactDeleted.setValue(false); contactDeleted.setValue(false);
@@ -205,16 +219,13 @@ public class ConversationViewModel extends AndroidViewModel
} }
@UiThread @UiThread
void sendMessage(@Nullable String text, void sendMessage(@Nullable String text, List<AttachmentHeader> headers) {
List<AttachmentHeader> headers, long timestamp) {
// 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 -> { observeForeverOnce(privateMessageFormat, format ->
requireNonNull(hasImageSupport); storeMessage(requireNonNull(contactId), groupId, text,
createMessage(groupId, text, headers, timestamp, headers, format));
hasImageSupport);
});
}); });
} }
@@ -244,10 +255,10 @@ public class ConversationViewModel extends AndroidViewModel
@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();
@@ -256,7 +267,7 @@ public class ConversationViewModel extends AndroidViewModel
// 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_ONLY &&
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);
@@ -274,40 +285,67 @@ public class ConversationViewModel extends AndroidViewModel
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} }
@UiThread private PrivateMessage createMessage(Transaction txn, ContactId c,
private void createMessage(GroupId groupId, @Nullable String text, GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long timestamp, List<AttachmentHeader> headers, PrivateMessageFormat format)
boolean hasImageSupport) { throws DbException {
long timestamp =
conversationManager.getTimestampForOutgoingMessage(txn, c);
try { try {
PrivateMessage pm; if (format == TEXT_ONLY) {
if (hasImageSupport) { return privateMessageFactory.createLegacyPrivateMessage(
pm = privateMessageFactory.createPrivateMessage(groupId, groupId, timestamp, requireNonNull(text));
} else if (format == TEXT_IMAGES) {
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers); timestamp, text, headers);
} else { } else {
pm = privateMessageFactory.createLegacyPrivateMessage( long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
groupId, timestamp, requireNonNull(text)); timestamp);
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers, timer);
} }
storeMessage(pm);
} catch (FormatException e) { } catch (FormatException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
@UiThread @UiThread
private void storeMessage(PrivateMessage m) { private void storeMessage(ContactId c, GroupId groupId,
attachmentCreator.onAttachmentsSent(m.getMessage().getId()); @Nullable String text, List<AttachmentHeader> headers,
PrivateMessageFormat format) {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
long start = now(); db.transaction(false, txn -> {
messagingManager.addLocalMessage(m); long start = now();
logDuration(LOG, "Storing message", start); PrivateMessage m = createMessage(txn, c, groupId, text,
Message message = m.getMessage(); headers, format);
PrivateMessageHeader h = new PrivateMessageHeader( messagingManager.addLocalMessage(txn, m);
message.getId(), message.getGroupId(), logDuration(LOG, "Storing message", start);
message.getTimestamp(), true, true, false, false, Message message = m.getMessage();
m.hasText(), m.getAttachmentHeaders()); PrivateMessageHeader h = new PrivateMessageHeader(
// TODO add text to cache when available here message.getId(), message.getGroupId(),
addedHeader.postEvent(h); message.getTimestamp(), true, true, false, false,
m.hasText(), m.getAttachmentHeaders(),
m.getAutoDeleteTimer());
// TODO add text to cache when available here
MessageId id = message.getId();
txn.attach(() -> attachmentCreator.onAttachmentsSent(id));
addedHeader.postEvent(h);
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void setAutoDeleteTimerEnabled(boolean enabled) {
final long timer = enabled ? DAYS.toMillis(7) : NO_AUTO_DELETE_TIMER;
// ContactId is set before menu gets inflated and UI interaction
final ContactId c = requireNonNull(contactId);
dbExecutor.execute(() -> {
try {
db.transaction(false, txn ->
autoDeleteManager.setAutoDeleteTimer(txn, c, timer));
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -330,8 +368,8 @@ public class ConversationViewModel extends AndroidViewModel
return contactName; return contactName;
} }
LiveData<Boolean> hasImageSupport() { LiveData<PrivateMessageFormat> getPrivateMessageFormat() {
return imageSupport; return privateMessageFormat;
} }
LiveEvent<Boolean> showImageOnboarding() { LiveEvent<Boolean> showImageOnboarding() {

View File

@@ -211,8 +211,7 @@ public class IntroductionMessageFragment extends BaseFragment
introductionActivity.runOnDbThread(() -> { introductionActivity.runOnDbThread(() -> {
// actually make the introduction // actually make the introduction
try { try {
long timestamp = System.currentTimeMillis(); introductionManager.makeIntroduction(c1, c2, text);
introductionManager.makeIntroduction(c1, c2, text, timestamp);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
introductionError(); introductionError();

View File

@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -15,6 +17,8 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl; import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
@@ -35,6 +39,8 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@@ -43,9 +49,12 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
implements CreateGroupController { implements CreateGroupController {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(CreateGroupControllerImpl.class.getName()); getLogger(CreateGroupControllerImpl.class.getName());
private final Executor cryptoExecutor; private final Executor cryptoExecutor;
private final TransactionManager db;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final PrivateGroupFactory groupFactory; private final PrivateGroupFactory groupFactory;
@@ -56,16 +65,26 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private final Clock clock; private final Clock clock;
@Inject @Inject
CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor, CreateGroupControllerImpl(
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, TransactionManager db,
IdentityManager identityManager, PrivateGroupFactory groupFactory, AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
LifecycleManager lifecycleManager,
ContactManager contactManager,
IdentityManager identityManager,
PrivateGroupFactory groupFactory,
GroupMessageFactory groupMessageFactory, GroupMessageFactory groupMessageFactory,
PrivateGroupManager groupManager, PrivateGroupManager groupManager,
GroupInvitationFactory groupInvitationFactory, GroupInvitationFactory groupInvitationFactory,
GroupInvitationManager groupInvitationManager, Clock clock) { GroupInvitationManager groupInvitationManager,
Clock clock) {
super(dbExecutor, lifecycleManager, contactManager); super(dbExecutor, lifecycleManager, contactManager);
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.db = db;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.groupFactory = groupFactory; this.groupFactory = groupFactory;
@@ -129,16 +148,14 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) { ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
LocalAuthor localAuthor = identityManager.getLocalAuthor(); db.transaction(true, txn -> {
List<Contact> contacts = new ArrayList<>(); LocalAuthor localAuthor =
for (ContactId c : contactIds) { identityManager.getLocalAuthor(txn);
try { List<InvitationContext> contexts =
contacts.add(contactManager.getContact(c)); createInvitationContexts(txn, contactIds);
} catch (NoSuchContactException e) { txn.attach(() -> signInvitations(g, localAuthor, contexts,
// Continue text, handler));
} });
}
signInvitations(g, localAuthor, contacts, text, handler);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
handler.onException(e); handler.onException(e);
@@ -146,17 +163,32 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
}); });
} }
private List<InvitationContext> createInvitationContexts(Transaction txn,
Collection<ContactId> contactIds) throws DbException {
List<InvitationContext> contexts = new ArrayList<>();
for (ContactId c : contactIds) {
try {
Contact contact = contactManager.getContact(txn, c);
long timestamp = conversationManager
.getTimestampForOutgoingMessage(txn, c);
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
timestamp);
contexts.add(new InvitationContext(contact, timestamp, timer));
} catch (NoSuchContactException e) {
// Continue
}
}
return contexts;
}
private void signInvitations(GroupId g, LocalAuthor localAuthor, private void signInvitations(GroupId g, LocalAuthor localAuthor,
Collection<Contact> contacts, @Nullable String text, List<InvitationContext> contexts, @Nullable String text,
ResultExceptionHandler<Void, DbException> handler) { ResultExceptionHandler<Void, DbException> handler) {
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
long timestamp = clock.currentTimeMillis(); for (InvitationContext ctx : contexts) {
List<InvitationContext> contexts = new ArrayList<>(); ctx.signature = groupInvitationFactory.signInvitation(
for (Contact c : contacts) { ctx.contact, g, ctx.timestamp,
byte[] signature = groupInvitationFactory.signInvitation(c, g, localAuthor.getPrivateKey());
timestamp, localAuthor.getPrivateKey());
contexts.add(new InvitationContext(c.getId(), timestamp,
signature));
} }
sendInvitations(g, contexts, text, handler); sendInvitations(g, contexts, text, handler);
}); });
@@ -167,16 +199,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) { ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
for (InvitationContext context : contexts) { for (InvitationContext ctx : contexts) {
try { try {
groupInvitationManager.sendInvitation(g, groupInvitationManager.sendInvitation(g,
context.contactId, text, context.timestamp, ctx.contact.getId(), text, ctx.timestamp,
context.signature); requireNonNull(ctx.signature),
ctx.autoDeleteTimer);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
// Continue // Continue
} }
} }
//noinspection ConstantConditions
handler.onResult(null); handler.onResult(null);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
@@ -187,15 +219,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private static class InvitationContext { private static class InvitationContext {
private final ContactId contactId; private final Contact contact;
private final long timestamp; private final long timestamp, autoDeleteTimer;
private final byte[] signature; @Nullable
private byte[] signature = null;
private InvitationContext(ContactId contactId, long timestamp, private InvitationContext(Contact contact, long timestamp,
byte[] signature) { long autoDeleteTimer) {
this.contactId = contactId; this.contact = contact;
this.timestamp = timestamp; this.timestamp = timestamp;
this.signature = signature; this.autoDeleteTimer = autoDeleteTimer;
} }
} }
} }

View File

@@ -10,11 +10,9 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl; import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -25,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@@ -33,21 +32,16 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
implements ShareBlogController { implements ShareBlogController {
private final static Logger LOG = private final static Logger LOG =
Logger.getLogger(ShareBlogControllerImpl.class.getName()); getLogger(ShareBlogControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final BlogSharingManager blogSharingManager; private final BlogSharingManager blogSharingManager;
private final Clock clock;
@Inject @Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor, ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
ConversationManager conversationManager, BlogSharingManager blogSharingManager) {
BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager); super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager; this.blogSharingManager = blogSharingManager;
this.clock = clock;
} }
@Override @Override
@@ -62,10 +56,7 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
try { try {
for (ContactId c : contacts) { for (ContactId c : contacts) {
try { try {
long time = Math.max(clock.currentTimeMillis(), blogSharingManager.sendInvitation(g, c, text);
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
blogSharingManager.sendInvitation(g, c, text, time);
} catch (NoSuchContactException | NoSuchGroupException e) { } catch (NoSuchContactException | NoSuchGroupException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -10,10 +10,8 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl; import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import java.util.Collection; import java.util.Collection;
@@ -25,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@@ -33,21 +32,16 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
implements ShareForumController { implements ShareForumController {
private final static Logger LOG = private final static Logger LOG =
Logger.getLogger(ShareForumControllerImpl.class.getName()); getLogger(ShareForumControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final ForumSharingManager forumSharingManager; private final ForumSharingManager forumSharingManager;
private final Clock clock;
@Inject @Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor, ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
ConversationManager conversationManager, ForumSharingManager forumSharingManager) {
ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager); super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager; this.forumSharingManager = forumSharingManager;
this.clock = clock;
} }
@Override @Override
@@ -62,10 +56,7 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
try { try {
for (ContactId c : contacts) { for (ContactId c : contacts) {
try { try {
long time = Math.max(clock.currentTimeMillis(), forumSharingManager.sendInvitation(g, c, text);
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
forumSharingManager.sendInvitation(g, c, text, time);
} catch (NoSuchContactException | NoSuchGroupException e) { } catch (NoSuchContactException | NoSuchGroupException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M11.25,6A3.25,3.25 0 0,1 14.5,2.75A3.25,3.25 0 0,1 17.75,6C17.75,6.42 18.08,6.75 18.5,6.75C18.92,6.75 19.25,6.42 19.25,6V5.25H20.75V6A2.25,2.25 0 0,1 18.5,8.25A2.25,2.25 0 0,1 16.25,6A1.75,1.75 0 0,0 14.5,4.25A1.75,1.75 0 0,0 12.75,6H14V7.29C16.89,8.15 19,10.83 19,14A7,7 0 0,1 12,21A7,7 0 0,1 5,14C5,10.83 7.11,8.15 10,7.29V6H11.25M22,6H24V7H22V6M19,4V2H20V4H19M20.91,4.38L22.33,2.96L23.04,3.67L21.62,5.09L20.91,4.38Z" />
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragment"
android:name="org.briarproject.briar.android.conversation.ConversationSettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="@dimen/margin_large">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageViewBomb"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_bomb"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/colorControlNormal" />
<TextView
android:id="@+id/buttonLearnMore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:clickable="true"
android:focusable="true"
android:text="@string/learn_more"
android:textColor="?android:attr/textColorLink"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageViewBomb" />
<View
android:id="@+id/divider"
style="@style/Divider.Horizontal"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="16dp"
android:background="?android:attr/listDivider"
app:layout_constraintBottom_toTopOf="@+id/barrier1"
app:layout_constraintTop_toBottomOf="@+id/buttonLearnMore" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top" />
<TextView
android:id="@+id/headlineSetting"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/disappearing_messages_title"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/barrier1" />
<TextView
android:id="@+id/textSetting"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/disappearing_messages_summary"
app:layout_constraintBottom_toTopOf="@id/barrier2"
app:layout_constraintEnd_toStartOf="@id/switchDisappearingMessages"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/headlineSetting" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switchDisappearingMessages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/barrier2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textSetting"
app:layout_constraintTop_toBottomOf="@+id/barrier1" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="?dialogPreferredPadding">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/disappearing_messages_explanation_long"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>

View File

@@ -68,6 +68,16 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -67,6 +67,16 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -68,6 +68,17 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:src="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -73,11 +73,20 @@
style="@style/TextMessage.Timestamp" style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="6dp" android:layout_marginEnd="4dp"
android:layout_marginRight="6dp" android:layout_marginRight="4dp"
android:textColor="@color/private_message_date_inverse" android:textColor="@color/private_message_date_inverse"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:src="@drawable/ic_bomb"
tools:ignore="ContentDescription" />
<ImageView <ImageView
android:id="@+id/status" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -48,10 +48,28 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
app:layout_constraintEnd_toEndOf="@+id/text" app:layout_constraintEnd_toStartOf="@+id/bomb"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text" app:layout_constraintTop_toBottomOf="@+id/text"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:src="@drawable/ic_bomb"
app:layout_constraintEnd_toEndOf="@+id/text"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toBottomOf="@+id/text"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@@ -55,14 +55,26 @@
app:layout_constraintTop_toBottomOf="@+id/text" app:layout_constraintTop_toBottomOf="@+id/text"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:src="@drawable/ic_bomb"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toTopOf="@+id/time"
tools:ignore="ContentDescription" />
<ImageView <ImageView
android:id="@+id/status" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium" android:layout_marginStart="4dp"
android:layout_marginLeft="@dimen/margin_medium" android:layout_marginLeft="4dp"
app:layout_constraintBottom_toBottomOf="@+id/time" app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/time" app:layout_constraintStart_toEndOf="@+id/bomb"
app:layout_constraintTop_toTopOf="@+id/time" app:layout_constraintTop_toTopOf="@+id/time"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered" /> tools:src="@drawable/message_delivered" />

View File

@@ -68,10 +68,27 @@
style="@style/TextMessage.Timestamp" style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/bomb"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/acceptButton" app:layout_constraintTop_toBottomOf="@+id/acceptButton"
tools:text="Dec 24, 13:37" /> tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:src="@drawable/ic_bomb"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toBottomOf="@+id/acceptButton"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@@ -1,30 +1,37 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:tools="http://schemas.android.com/tools">
<item <item
android:id="@+id/action_introduction" android:id="@+id/action_introduction"
android:enabled="false"
android:icon="@drawable/introduction_white" android:icon="@drawable/introduction_white"
android:title="@string/introduction_menu_item" android:title="@string/introduction_menu_item"
android:enabled="false" app:showAsAction="never" />
app:showAsAction="never"/>
<item <item
android:id="@+id/action_set_alias" android:id="@+id/action_set_alias"
android:title="@string/set_contact_alias"
android:enabled="false" android:enabled="false"
app:showAsAction="never"/> android:title="@string/set_contact_alias"
app:showAsAction="never" />
<item
android:id="@+id/action_conversation_settings"
android:title="@string/menu_item_disappearing_messages"
android:visible="false"
app:showAsAction="never"
tools:visible="true" />
<item <item
android:id="@+id/action_delete_all_messages" android:id="@+id/action_delete_all_messages"
android:title="@string/delete_all_messages" android:title="@string/delete_all_messages"
app:showAsAction="never"/> app:showAsAction="never" />
<item <item
android:id="@+id/action_social_remove_person" android:id="@+id/action_social_remove_person"
android:icon="@drawable/action_delete_white" android:icon="@drawable/action_delete_white"
android:title="@string/delete_contact" android:title="@string/delete_contact"
app:showAsAction="never"/> app:showAsAction="never" />
</menu> </menu>

View File

@@ -165,6 +165,7 @@
<string name="set_contact_alias">Change contact name</string> <string name="set_contact_alias">Change contact name</string>
<string name="set_contact_alias_hint">Contact name</string> <string name="set_contact_alias_hint">Contact name</string>
<string name="set_alias_button">Change</string> <string name="set_alias_button">Change</string>
<string name="menu_item_disappearing_messages">Disappearing messages</string>
<string name="delete_all_messages">Delete all messages</string> <string name="delete_all_messages">Delete all messages</string>
<string name="dialog_title_delete_all_messages">Confirm Message Deletion</string> <string name="dialog_title_delete_all_messages">Confirm Message Deletion</string>
<string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string> <string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string>
@@ -545,6 +546,20 @@
<string name="choose_ringtone_title">Choose ringtone</string> <string name="choose_ringtone_title">Choose ringtone</string>
<string name="cannot_load_ringtone">Cannot load ringtone</string> <string name="cannot_load_ringtone">Cannot load ringtone</string>
<!-- Conversation Settings -->
<string name="disappearing_messages_title">Disappearing messages</string>
<string name="disappearing_messages_cat_title">Self-destructing messages</string>
<string name="disappearing_messages_explanation_long">You can configure disappearing messages here.
Messages that will disappear are marked with a bomb icon.
\n\nTurning on this setting will make new messages in this conversation automatically disappear 7 days after being received.
This applies to messages you send to your contact as well as messages your contact sends to you.
Your contact can also change this setting for the both of you.
\n\nKeep in mind that recipients can still make analogue copies of the messages you sent.
\n\nIf you change this setting, it will apply to your messages immediately and to messages of
your contact once they receive any message from you after changing the setting.</string>
<string name="learn_more">Learn more about disappearing messages</string>
<string name="disappearing_messages_summary">Make future messages in this conversation automatically disappear 7\u00A0days after being received.</string>
<!-- Settings Feedback --> <!-- Settings Feedback -->
<string name="feedback_settings_title">Feedback</string> <string name="feedback_settings_title">Feedback</string>
<string name="send_feedback">Send feedback</string> <string name="send_feedback">Send feedback</string>

View File

@@ -0,0 +1,23 @@
package org.briarproject.briar.api.autodelete;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
public interface AutoDeleteConstants {
/**
* The minimum valid auto-delete timer duration in milliseconds.
*/
long MIN_AUTO_DELETE_TIMER_MS = MINUTES.toMillis(1);
/**
* The maximum valid auto-delete timer duration in milliseconds.
*/
long MAX_AUTO_DELETE_TIMER_MS = DAYS.toMillis(365);
/**
* Placeholder value indicating that a message has no auto-delete timer.
* This value should not be sent over the wire - send null instead.
*/
long NO_AUTO_DELETE_TIMER = -1;
}

View File

@@ -0,0 +1,56 @@
package org.briarproject.briar.api.autodelete;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
@NotNullByDefault
public interface AutoDeleteManager {
/**
* The unique ID of the auto-delete client.
*/
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.autodelete");
/**
* The current major version of the auto-delete client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the auto-delete client.
*/
int MINOR_VERSION = 0;
/**
* Returns the auto-delete timer duration for the given contact. Use
* {@link #getAutoDeleteTimer(Transaction, ContactId, long)} if the timer
* will be used in an outgoing message.
*/
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;
}

View File

@@ -15,9 +15,9 @@ public class BlogInvitationRequest extends InvitationRequest<Blog> {
public BlogInvitationRequest(MessageId id, GroupId groupId, long time, public BlogInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, Blog blog, @Nullable String text, SessionId sessionId, Blog blog, @Nullable String text,
boolean available, boolean canBeOpened) { boolean available, boolean canBeOpened, long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, blog, super(id, groupId, time, local, read, sent, seen, sessionId, blog,
text, available, canBeOpened); text, available, canBeOpened, autoDeleteTimer);
} }
@Override @Override

View File

@@ -12,9 +12,10 @@ public class BlogInvitationResponse extends InvitationResponse {
public BlogInvitationResponse(MessageId id, GroupId groupId, long time, public BlogInvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accept, GroupId shareableId) { SessionId sessionId, boolean accept, GroupId shareableId,
long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, super(id, groupId, time, local, read, sent, seen, sessionId,
accept, shareableId); accept, shareableId, autoDeleteTimer);
} }
@Override @Override

View File

@@ -18,5 +18,5 @@ public interface BlogSharingManager extends SharingManager<Blog> {
/** /**
* The current minor version of the blog sharing client. * The current minor version of the blog sharing client.
*/ */
int MINOR_VERSION = 0; int MINOR_VERSION = 1;
} }

View File

@@ -43,6 +43,18 @@ public interface ConversationManager {
*/ */
GroupCount getGroupCount(ContactId c) throws DbException; GroupCount getGroupCount(ContactId c) throws DbException;
/**
* Returns the unified group count for all private conversation messages.
*/
GroupCount getGroupCount(Transaction txn, ContactId c) throws DbException;
/**
* Returns a timestamp for an outgoing message, which is later than the
* timestamp of any message in the conversation with the given contact.
*/
long getTimestampForOutgoingMessage(Transaction txn, ContactId c)
throws DbException;
/** /**
* Deletes all messages exchanged with the given contact. * Deletes all messages exchanged with the given contact.
*/ */

View File

@@ -12,18 +12,20 @@ public abstract class ConversationMessageHeader {
private final MessageId id; private final MessageId id;
private final GroupId groupId; private final GroupId groupId;
private final long timestamp; private final long timestamp, autoDeleteTimer;
private final boolean local, sent, seen, read; private final boolean local, read, sent, seen;
public ConversationMessageHeader(MessageId id, GroupId groupId, long timestamp, public ConversationMessageHeader(MessageId id, GroupId groupId,
boolean local, boolean read, boolean sent, boolean seen) { long timestamp, boolean local, boolean read, boolean sent,
boolean seen, long autoDeleteTimer) {
this.id = id; this.id = id;
this.groupId = groupId; this.groupId = groupId;
this.timestamp = timestamp; this.timestamp = timestamp;
this.local = local; this.local = local;
this.read = read;
this.sent = sent; this.sent = sent;
this.seen = seen; this.seen = seen;
this.read = read; this.autoDeleteTimer = autoDeleteTimer;
} }
public MessageId getId() { public MessageId getId() {
@@ -55,4 +57,8 @@ public abstract class ConversationMessageHeader {
} }
public abstract <T> T accept(ConversationMessageVisitor<T> v); public abstract <T> T accept(ConversationMessageVisitor<T> v);
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
} }

View File

@@ -20,11 +20,12 @@ public abstract class ConversationRequest<N extends Nameable>
private final String text; private final String text;
private final boolean answered; private final boolean answered;
public ConversationRequest(MessageId messageId, GroupId groupId, long time, public ConversationRequest(MessageId messageId, GroupId groupId,
boolean local, boolean read, boolean sent, boolean seen, long timestamp, boolean local, boolean read, boolean sent,
SessionId sessionId, N nameable, @Nullable String text, boolean seen, SessionId sessionId, N nameable,
boolean answered) { @Nullable String text, boolean answered, long autoDeleteTimer) {
super(messageId, groupId, time, local, read, sent, seen); super(messageId, groupId, timestamp, local, read, sent, seen,
autoDeleteTimer);
this.sessionId = sessionId; this.sessionId = sessionId;
this.nameable = nameable; this.nameable = nameable;
this.text = text; this.text = text;

View File

@@ -16,8 +16,8 @@ public abstract class ConversationResponse extends ConversationMessageHeader {
public ConversationResponse(MessageId id, GroupId groupId, long time, public ConversationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accepted) { SessionId sessionId, boolean accepted, long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen); super(id, groupId, time, local, read, sent, seen, autoDeleteTimer);
this.sessionId = sessionId; this.sessionId = sessionId;
this.accepted = accepted; this.accepted = accepted;
} }

View File

@@ -17,9 +17,9 @@ public class ForumInvitationRequest extends InvitationRequest<Forum> {
public ForumInvitationRequest(MessageId id, GroupId groupId, long time, public ForumInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, Forum forum, @Nullable String text, SessionId sessionId, Forum forum, @Nullable String text,
boolean available, boolean canBeOpened) { boolean available, boolean canBeOpened, long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, forum, super(id, groupId, time, local, read, sent, seen, sessionId, forum,
text, available, canBeOpened); text, available, canBeOpened, autoDeleteTimer);
} }
@Override @Override

View File

@@ -15,9 +15,10 @@ public class ForumInvitationResponse extends InvitationResponse {
public ForumInvitationResponse(MessageId id, GroupId groupId, long time, public ForumInvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accept, GroupId shareableId) { SessionId sessionId, boolean accept, GroupId shareableId,
long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, super(id, groupId, time, local, read, sent, seen, sessionId,
accept, shareableId); accept, shareableId, autoDeleteTimer);
} }
@Override @Override

View File

@@ -18,5 +18,5 @@ public interface ForumSharingManager extends SharingManager<Forum> {
/** /**
* The current minor version of the forum sharing client. * The current minor version of the forum sharing client.
*/ */
int MINOR_VERSION = 0; int MINOR_VERSION = 1;
} }

View File

@@ -31,18 +31,18 @@ public interface IntroductionManager extends ConversationClient {
/** /**
* The current minor version of the introduction client. * The current minor version of the introduction client.
*/ */
int MINOR_VERSION = 0; int MINOR_VERSION = 1;
/** /**
* Sends two initial introduction messages. * Sends two initial introduction messages.
*/ */
void makeIntroduction(Contact c1, Contact c2, @Nullable String text, void makeIntroduction(Contact c1, Contact c2, @Nullable String text)
long timestamp) throws DbException; throws DbException;
/** /**
* Responds to an introduction. * Responds to an introduction.
*/ */
void respondToIntroduction(ContactId contactId, SessionId sessionId, void respondToIntroduction(ContactId contactId, SessionId sessionId,
long timestamp, boolean accept) throws DbException; boolean accept) throws DbException;
} }

View File

@@ -18,12 +18,12 @@ public class IntroductionRequest extends ConversationRequest<Author> {
private final AuthorInfo authorInfo; private final AuthorInfo authorInfo;
public IntroductionRequest(MessageId messageId, GroupId groupId, public IntroductionRequest(MessageId messageId, GroupId groupId, long time,
long time, boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, Author author, @Nullable String text, SessionId sessionId, Author author, @Nullable String text,
boolean answered, AuthorInfo authorInfo) { boolean answered, AuthorInfo authorInfo, long autoDeleteTimer) {
super(messageId, groupId, time, local, read, sent, seen, sessionId, super(messageId, groupId, time, local, read, sent, seen, sessionId,
author, text, answered); author, text, answered, autoDeleteTimer);
this.authorInfo = authorInfo; this.authorInfo = authorInfo;
} }

View File

@@ -25,9 +25,10 @@ public class IntroductionResponse extends ConversationResponse {
public IntroductionResponse(MessageId messageId, GroupId groupId, long time, public IntroductionResponse(MessageId messageId, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accepted, Author author, SessionId sessionId, boolean accepted, Author author,
AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed) { AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed,
long autoDeleteTimer) {
super(messageId, groupId, time, local, read, sent, seen, sessionId, super(messageId, groupId, time, local, read, sent, seen, sessionId,
accepted); accepted, autoDeleteTimer);
this.introducedAuthor = author; this.introducedAuthor = author;
this.introducedAuthorInfo = introducedAuthorInfo; this.introducedAuthorInfo = introducedAuthorInfo;
this.ourRole = role; this.ourRole = role;

View File

@@ -30,13 +30,18 @@ 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.
*/ */
void addLocalMessage(PrivateMessage m) throws DbException; void addLocalMessage(PrivateMessage m) throws DbException;
/**
* Stores a local private message.
*/
void addLocalMessage(Transaction txn, PrivateMessage m) throws DbException;
/** /**
* Stores a local attachment message. * Stores a local attachment message.
* *
@@ -77,12 +82,8 @@ public interface MessagingManager extends ConversationClient {
Attachment getAttachment(AttachmentHeader h) throws DbException; Attachment getAttachment(AttachmentHeader h) 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.
*
* Added: 2019-01-01
*/ */
boolean contactSupportsImages(Transaction txn, ContactId c) PrivateMessageFormat getContactMessageFormat(Transaction txn, ContactId c)
throws DbException; throws DbException;
} }

View File

@@ -8,44 +8,66 @@ 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.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@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_ONLY TEXT_ONLY} 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 = NO_AUTO_DELETE_TIMER;
format = TEXT_ONLY;
} }
/** /**
* 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 = NO_AUTO_DELETE_TIMER;
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() {
@@ -55,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

@@ -11,11 +11,29 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface PrivateMessageFactory { public interface PrivateMessageFactory {
/**
* Creates a private message in the
* {@link PrivateMessageFormat#TEXT_ONLY TEXT_ONLY} format.
*/
PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp, PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp,
String text) throws FormatException; String text) throws FormatException;
/**
* Creates a private message in the
* {@link PrivateMessageFormat#TEXT_IMAGES TEXT_IMAGES} format. This format
* requires the contact to support client version 0.1 or higher.
*/
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp, PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
@Nullable String text, List<AttachmentHeader> headers) @Nullable String text, List<AttachmentHeader> headers)
throws FormatException; throws FormatException;
/**
* Creates a private message in the
* {@link PrivateMessageFormat#TEXT_IMAGES_AUTO_DELETE TEXT_IMAGES_AUTO_DELETE}
* format. This format requires the contact to support client version 0.3
* or higher.
*/
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_ONLY,
/**
* 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

@@ -19,8 +19,9 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
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,
super(id, groupId, timestamp, local, read, sent, seen); long autoDeleteTimer) {
super(id, groupId, timestamp, local, read, sent, seen, autoDeleteTimer);
this.hasText = hasText; this.hasText = hasText;
this.attachmentHeaders = headers; this.attachmentHeaders = headers;
} }
@@ -37,5 +38,4 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
public <T> T accept(ConversationMessageVisitor<T> v) { public <T> T accept(ConversationMessageVisitor<T> v) {
return v.visitPrivateMessageHeader(this); return v.visitPrivateMessageHeader(this);
} }
} }

View File

@@ -32,7 +32,7 @@ public interface GroupInvitationManager extends ConversationClient {
/** /**
* The current minor version of the private group invitation client. * The current minor version of the private group invitation client.
*/ */
int MINOR_VERSION = 0; int MINOR_VERSION = 1;
/** /**
* Sends an invitation to share the given private group with the given * Sends an invitation to share the given private group with the given
@@ -43,7 +43,8 @@ public interface GroupInvitationManager extends ConversationClient {
* pending. * pending.
*/ */
void sendInvitation(GroupId g, ContactId c, @Nullable String text, void sendInvitation(GroupId g, ContactId c, @Nullable String text,
long timestamp, byte[] signature) throws DbException; long timestamp, byte[] signature, long autoDeleteTimer)
throws DbException;
/** /**
* Responds to a pending private group invitation from the given contact. * Responds to a pending private group invitation from the given contact.

View File

@@ -18,9 +18,10 @@ public class GroupInvitationRequest extends InvitationRequest<PrivateGroup> {
public GroupInvitationRequest(MessageId id, GroupId groupId, long time, public GroupInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, PrivateGroup shareable, SessionId sessionId, PrivateGroup shareable,
@Nullable String text, boolean available, boolean canBeOpened) { @Nullable String text, boolean available, boolean canBeOpened,
long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, shareable, super(id, groupId, time, local, read, sent, seen, sessionId, shareable,
text, available, canBeOpened); text, available, canBeOpened, autoDeleteTimer);
} }
@Override @Override

View File

@@ -15,9 +15,10 @@ public class GroupInvitationResponse extends InvitationResponse {
public GroupInvitationResponse(MessageId id, GroupId groupId, long time, public GroupInvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accept, GroupId shareableId) { SessionId sessionId, boolean accept, GroupId shareableId,
long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, super(id, groupId, time, local, read, sent, seen, sessionId,
accept, shareableId); accept, shareableId, autoDeleteTimer);
} }
@Override @Override

View File

@@ -15,9 +15,9 @@ public abstract class InvitationRequest<S extends Shareable> extends
public InvitationRequest(MessageId messageId, GroupId groupId, long time, public InvitationRequest(MessageId messageId, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, S object, @Nullable String text, SessionId sessionId, S object, @Nullable String text,
boolean available, boolean canBeOpened) { boolean available, boolean canBeOpened, long autoDeleteTimer) {
super(messageId, groupId, time, local, read, sent, seen, sessionId, super(messageId, groupId, time, local, read, sent, seen, sessionId,
object, text, !available); object, text, !available, autoDeleteTimer);
this.canBeOpened = canBeOpened; this.canBeOpened = canBeOpened;
} }

View File

@@ -11,8 +11,10 @@ public abstract class InvitationResponse extends ConversationResponse {
public InvitationResponse(MessageId id, GroupId groupId, long time, public InvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accepted, GroupId shareableId) { SessionId sessionId, boolean accepted, GroupId shareableId,
super(id, groupId, time, local, read, sent, seen, sessionId, accepted); long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, accepted,
autoDeleteTimer);
this.shareableId = shareableId; this.shareableId = shareableId;
} }

View File

@@ -21,7 +21,7 @@ public interface SharingManager<S extends Shareable>
* including optional text. * including optional text.
*/ */
void sendInvitation(GroupId shareableId, ContactId contactId, void sendInvitation(GroupId shareableId, ContactId contactId,
@Nullable String text, long timestamp) throws DbException; @Nullable String text) throws DbException;
/** /**
* Responds to a pending group invitation * Responds to a pending group invitation

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar; package org.briarproject.briar;
import org.briarproject.briar.autodelete.AutoDeleteModule;
import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.blog.BlogModule;
import org.briarproject.briar.feed.FeedModule; import org.briarproject.briar.feed.FeedModule;
import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.forum.ForumModule;
@@ -11,6 +12,8 @@ import org.briarproject.briar.sharing.SharingModule;
public interface BriarCoreEagerSingletons { public interface BriarCoreEagerSingletons {
void inject(AutoDeleteModule.EagerSingletons init);
void inject(BlogModule.EagerSingletons init); void inject(BlogModule.EagerSingletons init);
void inject(FeedModule.EagerSingletons init); void inject(FeedModule.EagerSingletons init);
@@ -30,6 +33,7 @@ public interface BriarCoreEagerSingletons {
class Helper { class Helper {
public static void injectEagerSingletons(BriarCoreEagerSingletons c) { public static void injectEagerSingletons(BriarCoreEagerSingletons c) {
c.inject(new AutoDeleteModule.EagerSingletons());
c.inject(new BlogModule.EagerSingletons()); c.inject(new BlogModule.EagerSingletons());
c.inject(new FeedModule.EagerSingletons()); c.inject(new FeedModule.EagerSingletons());
c.inject(new ForumModule.EagerSingletons()); c.inject(new ForumModule.EagerSingletons());

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar; package org.briarproject.briar;
import org.briarproject.briar.autodelete.AutoDeleteModule;
import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.blog.BlogModule;
import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.client.BriarClientModule;
import org.briarproject.briar.feed.DnsModule; import org.briarproject.briar.feed.DnsModule;
@@ -15,6 +16,7 @@ import org.briarproject.briar.test.TestModule;
import dagger.Module; import dagger.Module;
@Module(includes = { @Module(includes = {
AutoDeleteModule.class,
BlogModule.class, BlogModule.class,
BriarClientModule.class, BriarClientModule.class,
FeedModule.class, FeedModule.class,

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.autodelete;
interface AutoDeleteConstants {
/**
* 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;
}

View File

@@ -0,0 +1,188 @@
package org.briarproject.briar.autodelete;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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_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;
private final Group localGroup;
@Inject
AutoDeleteManagerImpl(
DatabaseComponent db,
ClientHelper clientHelper,
GroupFactory groupFactory,
ContactGroupFactory contactGroupFactory) {
this.db = db;
this.clientHelper = clientHelper;
this.groupFactory = groupFactory;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
}
@Override
public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
Group g = getGroup(c);
db.addGroup(txn, g);
clientHelper.setContactId(txn, g.getId(), c.getId());
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getGroup(c));
}
@Override
public long getAutoDeleteTimer(Transaction txn, ContactId c)
throws DbException {
try {
Group g = getGroup(db.getContact(txn, c));
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g.getId());
return meta.getLong(GROUP_KEY_TIMER, NO_AUTO_DELETE_TIMER);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
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());
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);
}
}
@Override
public void setAutoDeleteTimer(Transaction txn, ContactId c, long timer)
throws DbException {
validateTimer(timer);
try {
Group g = getGroup(db.getContact(txn, c));
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 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);
}
}
private Group getGroup(Contact c) {
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();
}
}
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.briar.autodelete;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AutoDeleteModule {
public static class EagerSingletons {
@Inject
AutoDeleteManager autoDeleteManager;
}
@Provides
@Singleton
AutoDeleteManager provideAutoDeleteManager(
LifecycleManager lifecycleManager, ContactManager contactManager,
AutoDeleteManagerImpl autoDeleteManager) {
lifecycleManager.registerOpenDatabaseHook(autoDeleteManager);
contactManager.registerContactHook(autoDeleteManager);
// Don't need to register with the client versioning manager as this
// client's groups aren't shared with contacts
return autoDeleteManager;
}
}

View File

@@ -8,6 +8,8 @@ import org.briarproject.briar.api.client.SessionId;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AbortMessage extends AbstractIntroductionMessage { class AbortMessage extends AbstractIntroductionMessage {
@@ -16,7 +18,8 @@ class AbortMessage extends AbstractIntroductionMessage {
protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp, protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId) { @Nullable MessageId previousMessageId, SessionId sessionId) {
super(messageId, groupId, timestamp, previousMessageId); super(messageId, groupId, timestamp, previousMessageId,
NO_AUTO_DELETE_TIMER);
this.sessionId = sessionId; this.sessionId = sessionId;
} }

View File

@@ -16,13 +16,16 @@ abstract class AbstractIntroductionMessage {
private final long timestamp; private final long timestamp;
@Nullable @Nullable
private final MessageId previousMessageId; private final MessageId previousMessageId;
private final long autoDeleteTimer;
AbstractIntroductionMessage(MessageId messageId, GroupId groupId, AbstractIntroductionMessage(MessageId messageId, GroupId groupId,
long timestamp, @Nullable MessageId previousMessageId) { long timestamp, @Nullable MessageId previousMessageId,
long autoDeleteTimer) {
this.messageId = messageId; this.messageId = messageId;
this.groupId = groupId; this.groupId = groupId;
this.timestamp = timestamp; this.timestamp = timestamp;
this.previousMessageId = previousMessageId; this.previousMessageId = previousMessageId;
this.autoDeleteTimer = autoDeleteTimer;
} }
MessageId getMessageId() { MessageId getMessageId() {
@@ -42,4 +45,7 @@ abstract class AbstractIntroductionMessage {
return previousMessageId; return previousMessageId;
} }
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
@@ -17,11 +18,15 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
@@ -30,6 +35,9 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION;
import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
@@ -39,7 +47,7 @@ import static org.briarproject.briar.introduction.MessageType.REQUEST;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
abstract class AbstractProtocolEngine<S extends Session> abstract class AbstractProtocolEngine<S extends Session<?>>
implements ProtocolEngine<S> { implements ProtocolEngine<S> {
protected final DatabaseComponent db; protected final DatabaseComponent db;
@@ -50,6 +58,9 @@ abstract class AbstractProtocolEngine<S extends Session>
protected final IdentityManager identityManager; protected final IdentityManager identityManager;
protected final MessageParser messageParser; protected final MessageParser messageParser;
protected final MessageEncoder messageEncoder; protected final MessageEncoder messageEncoder;
protected final ClientVersioningManager clientVersioningManager;
protected final AutoDeleteManager autoDeleteManager;
protected final ConversationManager conversationManager;
protected final Clock clock; protected final Clock clock;
AbstractProtocolEngine( AbstractProtocolEngine(
@@ -61,6 +72,9 @@ abstract class AbstractProtocolEngine<S extends Session>
IdentityManager identityManager, IdentityManager identityManager,
MessageParser messageParser, MessageParser messageParser,
MessageEncoder messageEncoder, MessageEncoder messageEncoder,
ClientVersioningManager clientVersioningManager,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) { Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
@@ -70,16 +84,29 @@ abstract class AbstractProtocolEngine<S extends Session>
this.identityManager = identityManager; this.identityManager = identityManager;
this.messageParser = messageParser; this.messageParser = messageParser;
this.messageEncoder = messageEncoder; this.messageEncoder = messageEncoder;
this.clientVersioningManager = clientVersioningManager;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
this.clock = clock; this.clock = clock;
} }
Message sendRequestMessage(Transaction txn, PeerSession s, Message sendRequestMessage(Transaction txn, PeerSession s,
long timestamp, Author author, @Nullable String text) long timestamp, Author author, @Nullable String text)
throws DbException { throws DbException {
Message m = messageEncoder Message m;
.encodeRequestMessage(s.getContactGroupId(), timestamp, ContactId c = getContactId(txn, s.getContactGroupId());
s.getLastLocalMessageId(), author, text); if (contactSupportsAutoDeletion(txn, c)) {
sendMessage(txn, REQUEST, s.getSessionId(), m, true); 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);
} else {
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), author, text);
sendMessage(txn, REQUEST, s.getSessionId(), m, true,
NO_AUTO_DELETE_TIMER);
}
return m; return m;
} }
@@ -87,21 +114,43 @@ abstract class AbstractProtocolEngine<S extends Session>
PublicKey ephemeralPublicKey, long acceptTimestamp, PublicKey ephemeralPublicKey, long acceptTimestamp,
Map<TransportId, TransportProperties> transportProperties, Map<TransportId, TransportProperties> transportProperties,
boolean visible) throws DbException { boolean visible) throws DbException {
Message m = messageEncoder Message m;
.encodeAcceptMessage(s.getContactGroupId(), timestamp, ContactId c = getContactId(txn, s.getContactGroupId());
s.getLastLocalMessageId(), s.getSessionId(), if (contactSupportsAutoDeletion(txn, c)) {
ephemeralPublicKey, acceptTimestamp, long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
transportProperties); timestamp);
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible); m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
ephemeralPublicKey, acceptTimestamp, transportProperties,
timer);
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible, timer);
} else {
m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
ephemeralPublicKey, acceptTimestamp, transportProperties);
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible,
NO_AUTO_DELETE_TIMER);
}
return m; return m;
} }
Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp, Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp,
boolean visible) throws DbException { boolean visible) throws DbException {
Message m = messageEncoder Message m;
.encodeDeclineMessage(s.getContactGroupId(), timestamp, ContactId c = getContactId(txn, s.getContactGroupId());
s.getLastLocalMessageId(), s.getSessionId()); if (contactSupportsAutoDeletion(txn, c)) {
sendMessage(txn, DECLINE, s.getSessionId(), m, visible); long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
timestamp);
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
timer);
sendMessage(txn, DECLINE, s.getSessionId(), m, visible, timer);
} else {
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId());
sendMessage(txn, DECLINE, s.getSessionId(), m, visible,
NO_AUTO_DELETE_TIMER);
}
return m; return m;
} }
@@ -111,7 +160,8 @@ abstract class AbstractProtocolEngine<S extends Session>
.encodeAuthMessage(s.getContactGroupId(), timestamp, .encodeAuthMessage(s.getContactGroupId(), timestamp,
s.getLastLocalMessageId(), s.getSessionId(), mac, s.getLastLocalMessageId(), s.getSessionId(), mac,
signature); signature);
sendMessage(txn, AUTH, s.getSessionId(), m, false); sendMessage(txn, AUTH, s.getSessionId(), m, false,
NO_AUTO_DELETE_TIMER);
return m; return m;
} }
@@ -120,7 +170,8 @@ abstract class AbstractProtocolEngine<S extends Session>
Message m = messageEncoder Message m = messageEncoder
.encodeActivateMessage(s.getContactGroupId(), timestamp, .encodeActivateMessage(s.getContactGroupId(), timestamp,
s.getLastLocalMessageId(), s.getSessionId(), mac); s.getLastLocalMessageId(), s.getSessionId(), mac);
sendMessage(txn, ACTIVATE, s.getSessionId(), m, false); sendMessage(txn, ACTIVATE, s.getSessionId(), m, false,
NO_AUTO_DELETE_TIMER);
return m; return m;
} }
@@ -129,16 +180,17 @@ abstract class AbstractProtocolEngine<S extends Session>
Message m = messageEncoder Message m = messageEncoder
.encodeAbortMessage(s.getContactGroupId(), timestamp, .encodeAbortMessage(s.getContactGroupId(), timestamp,
s.getLastLocalMessageId(), s.getSessionId()); s.getLastLocalMessageId(), s.getSessionId());
sendMessage(txn, ABORT, s.getSessionId(), m, false); sendMessage(txn, ABORT, s.getSessionId(), m, false,
NO_AUTO_DELETE_TIMER);
return m; return m;
} }
private void sendMessage(Transaction txn, MessageType type, private void sendMessage(Transaction txn, MessageType type,
SessionId sessionId, Message m, boolean visibleInConversation) SessionId sessionId, Message m, boolean visibleInConversation,
throws DbException { long autoDeleteTimer) throws DbException {
BdfDictionary meta = messageEncoder BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
.encodeMetadata(type, sessionId, m.getTimestamp(), true, true, m.getTimestamp(), true, true, visibleInConversation,
visibleInConversation); autoDeleteTimer);
try { try {
clientHelper.addLocalMessage(txn, m, meta, true, false); clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) { } catch (FormatException e) {
@@ -146,9 +198,10 @@ abstract class AbstractProtocolEngine<S extends Session>
} }
} }
void broadcastIntroductionResponseReceivedEvent(Transaction txn, Session s, void broadcastIntroductionResponseReceivedEvent(Transaction txn,
AuthorId sender, Author otherAuthor, AbstractIntroductionMessage m, Session<?> s, AuthorId sender, Author otherAuthor,
boolean canSucceed) throws DbException { AbstractIntroductionMessage m, boolean canSucceed)
throws DbException {
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
Contact c = contactManager.getContact(txn, sender, localAuthorId); Contact c = contactManager.getContact(txn, sender, localAuthorId);
AuthorInfo otherAuthorInfo = AuthorInfo otherAuthorInfo =
@@ -157,7 +210,8 @@ abstract class AbstractProtocolEngine<S extends Session>
new IntroductionResponse(m.getMessageId(), m.getGroupId(), new IntroductionResponse(m.getMessageId(), m.getGroupId(),
m.getTimestamp(), false, false, false, false, m.getTimestamp(), false, false, false, false,
s.getSessionId(), m instanceof AcceptMessage, s.getSessionId(), m instanceof AcceptMessage,
otherAuthor, otherAuthorInfo, s.getRole(), canSucceed); otherAuthor, otherAuthorInfo, s.getRole(), canSucceed,
m.getAutoDeleteTimer());
IntroductionResponseReceivedEvent e = IntroductionResponseReceivedEvent e =
new IntroductionResponseReceivedEvent(response, c.getId()); new IntroductionResponseReceivedEvent(response, c.getId());
txn.attach(e); txn.attach(e);
@@ -180,14 +234,33 @@ abstract class AbstractProtocolEngine<S extends Session>
return !dependency.equals(lastRemoteMessageId); return !dependency.equals(lastRemoteMessageId);
} }
long getLocalTimestamp(long localTimestamp, long requestTimestamp) { long getTimestampForOutgoingMessage(Transaction txn, GroupId contactGroupId)
return Math.max( throws DbException {
clock.currentTimeMillis(), ContactId c = getContactId(txn, contactGroupId);
Math.max( return conversationManager.getTimestampForOutgoingMessage(txn, c);
localTimestamp,
requestTimestamp
) + 1
);
} }
void receiveAutoDeleteTimer(Transaction txn, AbstractIntroductionMessage m)
throws DbException {
ContactId c = getContactId(txn, m.getGroupId());
autoDeleteManager.receiveAutoDeleteTimer(txn, c, m.getAutoDeleteTimer(),
m.getTimestamp());
}
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
return clientHelper.getContactId(txn, contactGroupId);
} catch (FormatException e) {
throw new DbException(e);
}
}
private boolean contactSupportsAutoDeletion(Transaction txn, ContactId c)
throws DbException {
int minorVersion = clientVersioningManager.getClientMinorVersion(txn, c,
CLIENT_ID, MAJOR_VERSION);
// Auto-delete was added in client version 0.1
return minorVersion >= 1;
}
} }

View File

@@ -26,8 +26,10 @@ class AcceptMessage extends AbstractIntroductionMessage {
long timestamp, @Nullable MessageId previousMessageId, long timestamp, @Nullable MessageId previousMessageId,
SessionId sessionId, PublicKey ephemeralPublicKey, SessionId sessionId, PublicKey ephemeralPublicKey,
long acceptTimestamp, long acceptTimestamp,
Map<TransportId, TransportProperties> transportProperties) { Map<TransportId, TransportProperties> transportProperties,
super(messageId, groupId, timestamp, previousMessageId); long autoDeleteTimer) {
super(messageId, groupId, timestamp, previousMessageId,
autoDeleteTimer);
this.sessionId = sessionId; this.sessionId = sessionId;
this.ephemeralPublicKey = ephemeralPublicKey; this.ephemeralPublicKey = ephemeralPublicKey;
this.acceptTimestamp = acceptTimestamp; this.acceptTimestamp = acceptTimestamp;

View File

@@ -7,6 +7,8 @@ import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class ActivateMessage extends AbstractIntroductionMessage { class ActivateMessage extends AbstractIntroductionMessage {
@@ -17,7 +19,8 @@ class ActivateMessage extends AbstractIntroductionMessage {
protected ActivateMessage(MessageId messageId, GroupId groupId, protected ActivateMessage(MessageId messageId, GroupId groupId,
long timestamp, MessageId previousMessageId, SessionId sessionId, long timestamp, MessageId previousMessageId, SessionId sessionId,
byte[] mac) { byte[] mac) {
super(messageId, groupId, timestamp, previousMessageId); super(messageId, groupId, timestamp, previousMessageId,
NO_AUTO_DELETE_TIMER);
this.sessionId = sessionId; this.sessionId = sessionId;
this.mac = mac; this.mac = mac;
} }

View File

@@ -7,6 +7,8 @@ import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AuthMessage extends AbstractIntroductionMessage { class AuthMessage extends AbstractIntroductionMessage {
@@ -17,7 +19,8 @@ class AuthMessage extends AbstractIntroductionMessage {
protected AuthMessage(MessageId messageId, GroupId groupId, protected AuthMessage(MessageId messageId, GroupId groupId,
long timestamp, MessageId previousMessageId, SessionId sessionId, long timestamp, MessageId previousMessageId, SessionId sessionId,
byte[] mac, byte[] signature) { byte[] mac, byte[] signature) {
super(messageId, groupId, timestamp, previousMessageId); super(messageId, groupId, timestamp, previousMessageId,
NO_AUTO_DELETE_TIMER);
this.sessionId = sessionId; this.sessionId = sessionId;
this.mac = mac; this.mac = mac;
this.signature = signature; this.signature = signature;

View File

@@ -16,8 +16,9 @@ class DeclineMessage extends AbstractIntroductionMessage {
protected DeclineMessage(MessageId messageId, GroupId groupId, protected DeclineMessage(MessageId messageId, GroupId groupId,
long timestamp, @Nullable MessageId previousMessageId, long timestamp, @Nullable MessageId previousMessageId,
SessionId sessionId) { SessionId sessionId, long autoDeleteTimer) {
super(messageId, groupId, timestamp, previousMessageId); super(messageId, groupId, timestamp, previousMessageId,
autoDeleteTimer);
this.sessionId = sessionId; this.sessionId = sessionId;
} }

View File

@@ -26,9 +26,12 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
@@ -41,6 +44,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.lang.Math.max;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
@@ -73,13 +77,17 @@ class IntroduceeProtocolEngine
IdentityManager identityManager, IdentityManager identityManager,
MessageParser messageParser, MessageParser messageParser,
MessageEncoder messageEncoder, MessageEncoder messageEncoder,
Clock clock,
IntroductionCrypto crypto, IntroductionCrypto crypto,
KeyManager keyManager, KeyManager keyManager,
TransportPropertyManager transportPropertyManager) { TransportPropertyManager transportPropertyManager,
ClientVersioningManager clientVersioningManager,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) {
super(db, clientHelper, contactManager, contactGroupFactory, super(db, clientHelper, contactManager, contactGroupFactory,
messageTracker, identityManager, messageParser, messageEncoder, messageTracker, identityManager, messageParser, messageEncoder,
clock); clientVersioningManager, autoDeleteManager,
conversationManager, clock);
this.crypto = crypto; this.crypto = crypto;
this.keyManager = keyManager; this.keyManager = keyManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
@@ -87,18 +95,18 @@ class IntroduceeProtocolEngine
@Override @Override
public IntroduceeSession onRequestAction(Transaction txn, public IntroduceeSession onRequestAction(Transaction txn,
IntroduceeSession session, @Nullable String text, long timestamp) { IntroduceeSession session, @Nullable String text) {
throw new UnsupportedOperationException(); // Invalid in this role throw new UnsupportedOperationException(); // Invalid in this role
} }
@Override @Override
public IntroduceeSession onAcceptAction(Transaction txn, public IntroduceeSession onAcceptAction(Transaction txn,
IntroduceeSession session, long timestamp) throws DbException { IntroduceeSession session) throws DbException {
switch (session.getState()) { switch (session.getState()) {
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case REMOTE_DECLINED: case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
return onLocalAccept(txn, session, timestamp); return onLocalAccept(txn, session);
case START: case START:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
@@ -112,12 +120,12 @@ class IntroduceeProtocolEngine
@Override @Override
public IntroduceeSession onDeclineAction(Transaction txn, public IntroduceeSession onDeclineAction(Transaction txn,
IntroduceeSession session, long timestamp) throws DbException { IntroduceeSession session) throws DbException {
switch (session.getState()) { switch (session.getState()) {
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case REMOTE_DECLINED: case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
return onLocalDecline(txn, session, timestamp); return onLocalDecline(txn, session);
case START: case START:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
@@ -249,6 +257,9 @@ class IntroduceeProtocolEngine
messageTracker messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Broadcast IntroductionRequestReceivedEvent // Broadcast IntroductionRequestReceivedEvent
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(), Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
@@ -258,7 +269,7 @@ class IntroduceeProtocolEngine
IntroductionRequest request = new IntroductionRequest(m.getMessageId(), IntroductionRequest request = new IntroductionRequest(m.getMessageId(),
m.getGroupId(), m.getTimestamp(), false, false, false, false, m.getGroupId(), m.getTimestamp(), false, false, false, false,
s.getSessionId(), m.getAuthor(), m.getText(), false, s.getSessionId(), m.getAuthor(), m.getText(), false,
authorInfo); authorInfo, m.getAutoDeleteTimer());
IntroductionRequestReceivedEvent e = IntroductionRequestReceivedEvent e =
new IntroductionRequestReceivedEvent(request, c.getId()); new IntroductionRequestReceivedEvent(request, c.getId());
txn.attach(e); txn.attach(e);
@@ -268,7 +279,7 @@ class IntroduceeProtocolEngine
} }
private IntroduceeSession onLocalAccept(Transaction txn, private IntroduceeSession onLocalAccept(Transaction txn,
IntroduceeSession s, long timestamp) throws DbException { IntroduceeSession s) throws DbException {
// Mark the request message unavailable to answer // Mark the request message unavailable to answer
markRequestsUnavailableToAnswer(txn, s); markRequestsUnavailableToAnswer(txn, s);
@@ -279,8 +290,8 @@ class IntroduceeProtocolEngine
Map<TransportId, TransportProperties> transportProperties = Map<TransportId, TransportProperties> transportProperties =
transportPropertyManager.getLocalProperties(txn); transportPropertyManager.getLocalProperties(txn);
// Send a ACCEPT message // Send an ACCEPT message
long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s)); long localTimestamp = getTimestampForVisibleMessage(txn, s);
Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey, Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey,
localTimestamp, transportProperties, true); localTimestamp, transportProperties, true);
// Track the message // Track the message
@@ -305,12 +316,12 @@ class IntroduceeProtocolEngine
} }
private IntroduceeSession onLocalDecline(Transaction txn, private IntroduceeSession onLocalDecline(Transaction txn,
IntroduceeSession s, long timestamp) throws DbException { IntroduceeSession s) throws DbException {
// Mark the request message unavailable to answer // Mark the request message unavailable to answer
markRequestsUnavailableToAnswer(txn, s); markRequestsUnavailableToAnswer(txn, s);
// Send a DECLINE message // Send a DECLINE message
long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s)); long localTimestamp = getTimestampForVisibleMessage(txn, s);
Message sent = sendDeclineMessage(txn, s, localTimestamp, true); Message sent = sendDeclineMessage(txn, s, localTimestamp, true);
// Track the message // Track the message
@@ -324,8 +335,7 @@ class IntroduceeProtocolEngine
} }
private IntroduceeSession onRemoteAccept(Transaction txn, private IntroduceeSession onRemoteAccept(Transaction txn,
IntroduceeSession s, AcceptMessage m) IntroduceeSession s, AcceptMessage m) throws DbException {
throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s);
@@ -362,6 +372,9 @@ class IntroduceeProtocolEngine
messageTracker messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Broadcast IntroductionResponseReceivedEvent // Broadcast IntroductionResponseReceivedEvent
broadcastIntroductionResponseReceivedEvent(txn, s, broadcastIntroductionResponseReceivedEvent(txn, s,
s.getIntroducer().getId(), s.getRemote().author, m, false); s.getIntroducer().getId(), s.getRemote().author, m, false);
@@ -408,8 +421,8 @@ class IntroduceeProtocolEngine
return abort(txn, s); return abort(txn, s);
} }
if (s.getState() != AWAIT_AUTH) throw new AssertionError(); if (s.getState() != AWAIT_AUTH) throw new AssertionError();
Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac, long localTimestamp = getTimestampForInvisibleMessage(s);
signature); Message sent = sendAuthMessage(txn, s, localTimestamp, mac, signature);
return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey, return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey,
aliceMacKey, bobMacKey); aliceMacKey, bobMacKey);
} }
@@ -460,7 +473,8 @@ class IntroduceeProtocolEngine
// send ACTIVATE message with a MAC // send ACTIVATE message with a MAC
byte[] mac = crypto.activateMac(s); byte[] mac = crypto.activateMac(s);
Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s), mac); long localTimestamp = getTimestampForInvisibleMessage(s);
Message sent = sendActivateMessage(txn, s, localTimestamp, mac);
// Move to AWAIT_ACTIVATE state and clear key material from session // Move to AWAIT_ACTIVATE state and clear key material from session
return IntroduceeSession.awaitActivate(s, m, sent, keys); return IntroduceeSession.awaitActivate(s, m, sent, keys);
@@ -511,7 +525,8 @@ class IntroduceeProtocolEngine
markRequestsUnavailableToAnswer(txn, s); markRequestsUnavailableToAnswer(txn, s);
// Send an ABORT message // Send an ABORT message
Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); long localTimestamp = getTimestampForInvisibleMessage(s);
Message sent = sendAbortMessage(txn, s, localTimestamp);
// Broadcast abort event for testing // Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId())); txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
@@ -526,9 +541,34 @@ class IntroduceeProtocolEngine
return isInvalidDependency(s.getLastRemoteMessageId(), dependency); return isInvalidDependency(s.getLastRemoteMessageId(), dependency);
} }
private long getLocalTimestamp(IntroduceeSession s) { /**
return getLocalTimestamp(s.getLocalTimestamp(), * Returns a timestamp for a visible outgoing message. The timestamp is
s.getRequestTimestamp()); * later than the timestamp of any message sent or received so far in the
* conversation, and later than the {@link
* #getSessionTimestamp(IntroduceeSession) session timestamp}.
*/
private long getTimestampForVisibleMessage(Transaction txn,
IntroduceeSession s) throws DbException {
long conversationTimestamp =
getTimestampForOutgoingMessage(txn, s.getContactGroupId());
return max(conversationTimestamp, getSessionTimestamp(s) + 1);
}
/**
* Returns a timestamp for an invisible outgoing message. The timestamp is
* later than the {@link #getSessionTimestamp(IntroduceeSession) session
* timestamp}.
*/
private long getTimestampForInvisibleMessage(IntroduceeSession s) {
return max(clock.currentTimeMillis(), getSessionTimestamp(s) + 1);
}
/**
* Returns the latest timestamp of any message sent so far in the session,
* and any request message received so far in the session.
*/
private long getSessionTimestamp(IntroduceeSession s) {
return max(s.getLocalTimestamp(), s.getRequestTimestamp());
} }
private void addSessionId(Transaction txn, MessageId m, SessionId sessionId) private void addSessionId(Transaction txn, MessageId m, SessionId sessionId)

View File

@@ -13,8 +13,11 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.introduction.IntroducerSession.Introducee; import org.briarproject.briar.introduction.IntroducerSession.Introducee;
@@ -22,6 +25,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.lang.Math.max;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
@@ -50,19 +54,23 @@ class IntroducerProtocolEngine
IdentityManager identityManager, IdentityManager identityManager,
MessageParser messageParser, MessageParser messageParser,
MessageEncoder messageEncoder, MessageEncoder messageEncoder,
ClientVersioningManager clientVersioningManager,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) { Clock clock) {
super(db, clientHelper, contactManager, contactGroupFactory, super(db, clientHelper, contactManager, contactGroupFactory,
messageTracker, identityManager, messageParser, messageEncoder, messageTracker, identityManager, messageParser, messageEncoder,
clock); clientVersioningManager, autoDeleteManager,
conversationManager, clock);
} }
@Override @Override
public IntroducerSession onRequestAction(Transaction txn, public IntroducerSession onRequestAction(Transaction txn,
IntroducerSession s, @Nullable String text, long timestamp) IntroducerSession s, @Nullable String text)
throws DbException { throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
return onLocalRequest(txn, s, text, timestamp); return onLocalRequest(txn, s, text);
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_A:
case AWAIT_RESPONSE_B: case AWAIT_RESPONSE_B:
@@ -82,37 +90,24 @@ class IntroducerProtocolEngine
@Override @Override
public IntroducerSession onAcceptAction(Transaction txn, public IntroducerSession onAcceptAction(Transaction txn,
IntroducerSession s, long timestamp) { IntroducerSession s) {
throw new UnsupportedOperationException(); // Invalid in this role throw new UnsupportedOperationException(); // Invalid in this role
} }
@Override @Override
public IntroducerSession onDeclineAction(Transaction txn, public IntroducerSession onDeclineAction(Transaction txn,
IntroducerSession s, long timestamp) { IntroducerSession s) {
throw new UnsupportedOperationException(); // Invalid in this role throw new UnsupportedOperationException(); // Invalid in this role
} }
IntroducerSession onIntroduceeRemoved(Transaction txn, IntroducerSession onIntroduceeRemoved(Transaction txn,
Introducee remainingIntroducee, IntroducerSession session) Introducee remainingIntroducee, IntroducerSession session)
throws DbException { throws DbException {
// abort session // abort session with remaining introducee
IntroducerSession s = abort(txn, session); IntroducerSession s = abort(txn, session, remainingIntroducee);
// reset information for introducee that was removed
Introducee introduceeA, introduceeB;
if (remainingIntroducee.author.equals(s.getIntroduceeA().author)) {
introduceeA = s.getIntroduceeA();
introduceeB =
new Introducee(s.getSessionId(), s.getIntroduceeB().groupId,
s.getIntroduceeB().author);
} else if (remainingIntroducee.author
.equals(s.getIntroduceeB().author)) {
introduceeA =
new Introducee(s.getSessionId(), s.getIntroduceeA().groupId,
s.getIntroduceeA().author);
introduceeB = s.getIntroduceeB();
} else throw new DbException();
return new IntroducerSession(s.getSessionId(), s.getState(), return new IntroducerSession(s.getSessionId(), s.getState(),
s.getRequestTimestamp(), introduceeA, introduceeB); s.getRequestTimestamp(), s.getIntroduceeA(),
s.getIntroduceeB());
} }
@Override @Override
@@ -222,13 +217,13 @@ class IntroducerProtocolEngine
} }
private IntroducerSession onLocalRequest(Transaction txn, private IntroducerSession onLocalRequest(Transaction txn,
IntroducerSession s, @Nullable String text, long timestamp) IntroducerSession s, @Nullable String text) throws DbException {
throws DbException {
// Send REQUEST messages // Send REQUEST messages
long maxIntroduceeTimestamp = long timestampA =
Math.max(getLocalTimestamp(s, s.getIntroduceeA()), getTimestampForVisibleMessage(txn, s, s.getIntroduceeA());
getLocalTimestamp(s, s.getIntroduceeB())); long timestampB =
long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp); getTimestampForVisibleMessage(txn, s, s.getIntroduceeB());
long localTimestamp = max(timestampA, timestampB);
Message sentA = sendRequestMessage(txn, s.getIntroduceeA(), Message sentA = sendRequestMessage(txn, s.getIntroduceeA(),
localTimestamp, s.getIntroduceeB().author, text); localTimestamp, s.getIntroduceeB().author, text);
Message sentB = sendRequestMessage(txn, s.getIntroduceeB(), Message sentB = sendRequestMessage(txn, s.getIntroduceeB(),
@@ -265,14 +260,16 @@ class IntroducerProtocolEngine
// Track the incoming message // Track the incoming message
messageTracker messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward ACCEPT message // Forward ACCEPT message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); // The forwarded message will not be visible to the introducee
Message sent = long localTimestamp = getTimestampForInvisibleMessage(s, i);
sendAcceptMessage(txn, i, timestamp, m.getEphemeralPublicKey(), Message sent = sendAcceptMessage(txn, i, localTimestamp,
m.getAcceptTimestamp(), m.getTransportProperties(), m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
false); m.getTransportProperties(), false);
// Create the next state // Create the next state
IntroducerState state = AWAIT_AUTHS; IntroducerState state = AWAIT_AUTHS;
@@ -326,10 +323,14 @@ class IntroducerProtocolEngine
// Track the incoming message // Track the incoming message
messageTracker messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward ACCEPT message // Forward ACCEPT message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
Message sent = sendAcceptMessage(txn, i, getLocalTimestamp(s, i), // The forwarded message will not be visible to the introducee
long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendAcceptMessage(txn, i, localTimestamp,
m.getEphemeralPublicKey(), m.getAcceptTimestamp(), m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
m.getTransportProperties(), false); m.getTransportProperties(), false);
@@ -377,11 +378,14 @@ class IntroducerProtocolEngine
// Track the incoming message // Track the incoming message
messageTracker messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward DECLINE message // Forward DECLINE message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); // The forwarded message will be visible to the introducee
Message sent = sendDeclineMessage(txn, i, timestamp, false); long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
// Create the next state // Create the next state
IntroducerState state = START; IntroducerState state = START;
@@ -429,11 +433,14 @@ class IntroducerProtocolEngine
// Track the incoming message // Track the incoming message
messageTracker messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward DECLINE message // Forward DECLINE message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); // The forwarded message will be visible to the introducee
Message sent = sendDeclineMessage(txn, i, timestamp, false); long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
Introducee introduceeA, introduceeB; Introducee introduceeA, introduceeB;
Author sender, other; Author sender, other;
@@ -473,8 +480,8 @@ class IntroducerProtocolEngine
// Forward AUTH message // Forward AUTH message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendAuthMessage(txn, i, timestamp, m.getMac(), Message sent = sendAuthMessage(txn, i, localTimestamp, m.getMac(),
m.getSignature()); m.getSignature());
// Move to the next state // Move to the next state
@@ -509,8 +516,8 @@ class IntroducerProtocolEngine
// Forward ACTIVATE message // Forward ACTIVATE message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendActivateMessage(txn, i, timestamp, m.getMac()); Message sent = sendActivateMessage(txn, i, localTimestamp, m.getMac());
// Move to the next state // Move to the next state
IntroducerState state = START; IntroducerState state = START;
@@ -532,8 +539,8 @@ class IntroducerProtocolEngine
IntroducerSession s, AbortMessage m) throws DbException { IntroducerSession s, AbortMessage m) throws DbException {
// Forward ABORT message // Forward ABORT message
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendAbortMessage(txn, i, timestamp); Message sent = sendAbortMessage(txn, i, localTimestamp);
// Broadcast abort event for testing // Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId())); txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
@@ -551,15 +558,45 @@ class IntroducerProtocolEngine
s.getRequestTimestamp(), introduceeA, introduceeB); s.getRequestTimestamp(), introduceeA, introduceeB);
} }
private IntroducerSession abort(Transaction txn, private IntroducerSession abort(Transaction txn, IntroducerSession s,
IntroducerSession s) throws DbException { Introducee remainingIntroducee) throws DbException {
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Send an ABORT message to the remaining introducee
long localTimestamp =
getTimestampForInvisibleMessage(s, remainingIntroducee);
Message sent =
sendAbortMessage(txn, remainingIntroducee, localTimestamp);
// Reset the session back to initial state
Introducee introduceeA = s.getIntroduceeA();
Introducee introduceeB = s.getIntroduceeB();
if (remainingIntroducee.author.equals(introduceeA.author)) {
introduceeA = new Introducee(introduceeA, sent);
introduceeB = new Introducee(s.getSessionId(), introduceeB.groupId,
introduceeB.author);
} else if (remainingIntroducee.author.equals(introduceeB.author)) {
introduceeA = new Introducee(s.getSessionId(), introduceeA.groupId,
introduceeA.author);
introduceeB = new Introducee(introduceeB, sent);
} else {
throw new DbException();
}
return new IntroducerSession(s.getSessionId(), START,
s.getRequestTimestamp(), introduceeA, introduceeB);
}
private IntroducerSession abort(Transaction txn, IntroducerSession s)
throws DbException {
// Broadcast abort event for testing // Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId())); txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Send an ABORT message to both introducees // Send an ABORT message to both introducees
long timestampA = getLocalTimestamp(s, s.getIntroduceeA()); long timestampA =
getTimestampForInvisibleMessage(s, s.getIntroduceeA());
Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA); Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA);
long timestampB = getLocalTimestamp(s, s.getIntroduceeB()); long timestampB =
getTimestampForInvisibleMessage(s, s.getIntroduceeB());
Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB); Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB);
// Reset the session back to initial state // Reset the session back to initial state
Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA); Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA);
@@ -589,9 +626,33 @@ class IntroducerProtocolEngine
return isInvalidDependency(expected, dependency); return isInvalidDependency(expected, dependency);
} }
private long getLocalTimestamp(IntroducerSession s, PeerSession p) { /**
return getLocalTimestamp(p.getLocalTimestamp(), * Returns a timestamp for a visible outgoing message. The timestamp is
s.getRequestTimestamp()); * later than the timestamp of any message sent or received so far in the
* conversation, and later than the {@link
* #getSessionTimestamp(IntroducerSession, PeerSession) session timestamp}.
*/
private long getTimestampForVisibleMessage(Transaction txn,
IntroducerSession s, PeerSession p) throws DbException {
long conversationTimestamp =
getTimestampForOutgoingMessage(txn, p.getContactGroupId());
return max(conversationTimestamp, getSessionTimestamp(s, p) + 1);
} }
/**
* Returns a timestamp for an invisible outgoing message. The timestamp is
* later than the {@link #getSessionTimestamp(IntroducerSession, PeerSession)
* session timestamp}.
*/
private long getTimestampForInvisibleMessage(IntroducerSession s,
PeerSession p) {
return max(clock.currentTimeMillis(), getSessionTimestamp(s, p) + 1);
}
/**
* Returns the latest timestamp of any message sent so far in the session.
*/
private long getSessionTimestamp(IntroducerSession s, PeerSession p) {
return max(p.getLocalTimestamp(), s.getRequestTimestamp());
}
} }

View File

@@ -2,9 +2,6 @@ package org.briarproject.briar.introduction;
interface IntroductionConstants { interface IntroductionConstants {
// Group metadata keys
String GROUP_KEY_CONTACT_ID = "contactId";
// Message metadata keys // Message metadata keys
String MSG_KEY_MESSAGE_TYPE = "messageType"; String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_SESSION_ID = "sessionId"; String MSG_KEY_SESSION_ID = "sessionId";
@@ -12,6 +9,7 @@ interface IntroductionConstants {
String MSG_KEY_LOCAL = "local"; String MSG_KEY_LOCAL = "local";
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi"; String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
// Session Keys // Session Keys
String SESSION_KEY_SESSION_ID = "sessionId"; String SESSION_KEY_SESSION_ID = "sessionId";

View File

@@ -59,7 +59,6 @@ import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_DECLINE
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED; import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED; import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
import static org.briarproject.briar.introduction.IntroducerState.START; import static org.briarproject.briar.introduction.IntroducerState.START;
import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
@@ -136,13 +135,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
c.getId(), CLIENT_ID, MAJOR_VERSION); c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client); db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary(); clientHelper.setContactId(txn, g.getId(), c.getId());
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
} }
@Override @Override
@@ -183,7 +176,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
StoredSession ss = getSession(txn, sessionId); StoredSession ss = getSession(txn, sessionId);
// Handle the message // Handle the message
Session session; Session<?> session;
MessageId storageId; MessageId storageId;
if (ss == null) { if (ss == null) {
if (meta.getMessageType() != REQUEST) throw new FormatException(); if (meta.getMessageType() != REQUEST) throw new FormatException();
@@ -211,7 +204,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
private IntroduceeSession createNewIntroduceeSession(Transaction txn, private IntroduceeSession createNewIntroduceeSession(Transaction txn,
Message m, BdfList body) throws DbException, FormatException { Message m, BdfList body) throws DbException, FormatException {
ContactId introducerId = getContactId(txn, m.getGroupId()); ContactId introducerId = clientHelper.getContactId(txn, m.getGroupId());
Author introducer = db.getContact(txn, introducerId).getAuthor(); Author introducer = db.getContact(txn, introducerId).getAuthor();
Author local = identityManager.getLocalAuthor(txn); Author local = identityManager.getLocalAuthor(txn);
Author remote = messageParser.parseRequestMessage(m, body).getAuthor(); Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
@@ -223,7 +216,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
remote); remote);
} }
private <S extends Session> S handleMessage(Transaction txn, Message m, private <S extends Session<?>> S handleMessage(Transaction txn, Message m,
BdfList body, MessageType type, S session, ProtocolEngine<S> engine) BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
throws DbException, FormatException { throws DbException, FormatException {
if (type == REQUEST) { if (type == REQUEST) {
@@ -263,13 +256,6 @@ class IntroductionManagerImpl extends ConversationClientImpl
results.values().iterator().next()); results.values().iterator().next());
} }
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
}
private MessageId createStorageId(Transaction txn) throws DbException { private MessageId createStorageId(Transaction txn) throws DbException {
Message m = clientHelper Message m = clientHelper
.createMessageForStoringMetadata(localGroup.getId()); .createMessageForStoringMetadata(localGroup.getId());
@@ -278,7 +264,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
private void storeSession(Transaction txn, MessageId storageId, private void storeSession(Transaction txn, MessageId storageId,
Session session) throws DbException { Session<?> session) throws DbException {
BdfDictionary d; BdfDictionary d;
if (session.getRole() == INTRODUCER) { if (session.getRole() == INTRODUCER) {
d = sessionEncoder d = sessionEncoder
@@ -325,8 +311,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
@Override @Override
public void makeIntroduction(Contact c1, Contact c2, @Nullable String text, public void makeIntroduction(Contact c1, Contact c2, @Nullable String text)
long timestamp) throws DbException { throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
// Look up the session, if there is one // Look up the session, if there is one
@@ -358,8 +344,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
storageId = ss.storageId; storageId = ss.storageId;
} }
// Handle the request action // Handle the request action
session = introducerEngine session = introducerEngine.onRequestAction(txn, session, text);
.onRequestAction(txn, session, text, timestamp);
// Store the updated session // Store the updated session
storeSession(txn, storageId, session); storeSession(txn, storageId, session);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -372,7 +357,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
@Override @Override
public void respondToIntroduction(ContactId contactId, SessionId sessionId, public void respondToIntroduction(ContactId contactId, SessionId sessionId,
long timestamp, boolean accept) throws DbException { boolean accept) throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
// Look up the session // Look up the session
@@ -390,11 +375,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
.parseIntroduceeSession(contactGroupId, ss.bdfSession); .parseIntroduceeSession(contactGroupId, ss.bdfSession);
// Handle the join or leave action // Handle the join or leave action
if (accept) { if (accept) {
session = introduceeEngine session = introduceeEngine.onAcceptAction(txn, session);
.onAcceptAction(txn, session, timestamp);
} else { } else {
session = introduceeEngine session = introduceeEngine.onDeclineAction(txn, session);
.onDeclineAction(txn, session, timestamp);
} }
// Store the updated session // Store the updated session
storeSession(txn, ss.storageId, session); storeSession(txn, ss.storageId, session);
@@ -461,7 +444,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(), return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(),
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(), meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
sessionId, author, text, !meta.isAvailableToAnswer(), sessionId, author, text, !meta.isAvailableToAnswer(),
authorInfo); authorInfo, rm.getAutoDeleteTimer());
} }
private IntroductionResponse parseInvitationResponse(Transaction txn, private IntroductionResponse parseInvitationResponse(Transaction txn,
@@ -499,7 +482,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(), return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(), meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
sessionId, accept, author, authorInfo, role, canSucceed); sessionId, accept, author, authorInfo, role, canSucceed,
meta.getAutoDeleteTimer());
} }
private void removeSessionWithIntroducer(Transaction txn, private void removeSessionWithIntroducer(Transaction txn,
@@ -665,7 +649,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
try { try {
StoredSession ss = getSession(txn, sessionId); StoredSession ss = getSession(txn, sessionId);
if (ss == null) throw new AssertionError(); if (ss == null) throw new AssertionError();
Session s; Session<?> s;
Role role = sessionParser.getRole(ss.bdfSession); Role role = sessionParser.getRole(ss.bdfSession);
if (role == INTRODUCER) { if (role == INTRODUCER) {
s = sessionParser.parseIntroducerSession(ss.bdfSession); s = sessionParser.parseIntroducerSession(ss.bdfSession);

View File

@@ -23,10 +23,12 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_
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.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction.MessageType.AUTH; import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
@Immutable @Immutable
@@ -52,13 +54,14 @@ class IntroductionValidator extends BdfMessageValidator {
return validateRequestMessage(m, body); return validateRequestMessage(m, body);
case ACCEPT: case ACCEPT:
return validateAcceptMessage(m, body); return validateAcceptMessage(m, body);
case DECLINE:
return validateDeclineMessage(type, m, body);
case AUTH: case AUTH:
return validateAuthMessage(m, body); return validateAuthMessage(m, body);
case ACTIVATE: case ACTIVATE:
return validateActivateMessage(m, body); return validateActivateMessage(m, body);
case DECLINE:
case ABORT: case ABORT:
return validateOtherMessage(type, m, body); return validateAbortMessage(type, m, body);
default: default:
throw new FormatException(); throw new FormatException();
} }
@@ -66,7 +69,11 @@ class IntroductionValidator extends BdfMessageValidator {
private BdfMessageContext validateRequestMessage(Message m, BdfList body) private BdfMessageContext validateRequestMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
checkSize(body, 4); // Client version 0.0: Message type, optional previous message ID,
// author, optional text.
// Client version 0.1: Message type, optional previous message ID,
// author, optional text, optional auto-delete timer.
checkSize(body, 4, 5);
byte[] previousMessageId = body.getOptionalRaw(1); byte[] previousMessageId = body.getOptionalRaw(1);
checkLength(previousMessageId, UniqueId.LENGTH); checkLength(previousMessageId, UniqueId.LENGTH);
@@ -77,8 +84,13 @@ class IntroductionValidator extends BdfMessageValidator {
String text = body.getOptionalString(3); String text = body.getOptionalString(3);
checkLength(text, 1, MAX_INTRODUCTION_TEXT_LENGTH); checkLength(text, 1, MAX_INTRODUCTION_TEXT_LENGTH);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 5) {
timer = validateAutoDeleteTimer(body.getOptionalLong(4));
}
BdfDictionary meta = BdfDictionary meta =
messageEncoder.encodeRequestMetadata(m.getTimestamp()); messageEncoder.encodeRequestMetadata(m.getTimestamp(), timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -89,7 +101,12 @@ class IntroductionValidator extends BdfMessageValidator {
private BdfMessageContext validateAcceptMessage(Message m, BdfList body) private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
checkSize(body, 6); // Client version 0.0: Message type, session ID, optional previous
// message ID, ephemeral public key, timestamp, transport properties.
// Client version 0.1: Message type, session ID, optional previous
// message ID, ephemeral public key, timestamp, transport properties,
// optional auto-delete timer.
checkSize(body, 6, 7);
byte[] sessionIdBytes = body.getRaw(1); byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH); checkLength(sessionIdBytes, UniqueId.LENGTH);
@@ -109,9 +126,44 @@ class IntroductionValidator extends BdfMessageValidator {
clientHelper clientHelper
.parseAndValidateTransportPropertiesMap(transportProperties); .parseAndValidateTransportPropertiesMap(transportProperties);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 7) {
timer = validateAutoDeleteTimer(body.getOptionalLong(6));
}
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId,
m.getTimestamp(), false, false, false); m.getTimestamp(), false, false, false, timer);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta, singletonList(dependency));
}
}
private BdfMessageContext validateDeclineMessage(MessageType type,
Message m, BdfList body) throws FormatException {
// Client version 0.0: Message type, session ID, optional previous
// message ID.
// Client version 0.1: Message type, session ID, optional previous
// message ID, optional auto-delete timer.
checkSize(body, 3, 4);
byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
byte[] previousMessageId = body.getOptionalRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 4) {
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
}
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
m.getTimestamp(), false, false, false, timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -138,7 +190,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(AUTH, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(AUTH, sessionId,
m.getTimestamp(), false, false, false); m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
MessageId dependency = new MessageId(previousMessageId); MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta, singletonList(dependency)); return new BdfMessageContext(meta, singletonList(dependency));
} }
@@ -158,7 +210,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId,
m.getTimestamp(), false, false, false); m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -167,7 +219,7 @@ class IntroductionValidator extends BdfMessageValidator {
} }
} }
private BdfMessageContext validateOtherMessage(MessageType type, private BdfMessageContext validateAbortMessage(MessageType type,
Message m, BdfList body) throws FormatException { Message m, BdfList body) throws FormatException {
checkSize(body, 3); checkSize(body, 3);
@@ -179,7 +231,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
m.getTimestamp(), false, false, false); m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -187,5 +239,4 @@ class IntroductionValidator extends BdfMessageValidator {
return new BdfMessageContext(meta, singletonList(dependency)); return new BdfMessageContext(meta, singletonList(dependency));
} }
} }
} }

View File

@@ -18,11 +18,12 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
interface MessageEncoder { interface MessageEncoder {
BdfDictionary encodeRequestMetadata(long timestamp); BdfDictionary encodeRequestMetadata(long timestamp,
long autoDeleteTimer);
BdfDictionary encodeMetadata(MessageType type, BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp, boolean local, @Nullable SessionId sessionId, long timestamp, boolean local,
boolean read, boolean visible); boolean read, boolean visible, long autoDeleteTimer);
void addSessionId(BdfDictionary meta, SessionId sessionId); void addSessionId(BdfDictionary meta, SessionId sessionId);
@@ -30,18 +31,53 @@ interface MessageEncoder {
void setAvailableToAnswer(BdfDictionary meta, boolean available); void setAvailableToAnswer(BdfDictionary meta, boolean available);
/**
* Encodes a request message without an auto-delete timer.
*/
Message encodeRequestMessage(GroupId contactGroupId, long timestamp, Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, Author author, @Nullable MessageId previousMessageId, Author author,
@Nullable String text); @Nullable String text);
/**
* Encodes a request message with an optional auto-delete timer. This
* requires the contact to support client version 0.1 or higher.
*/
Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, Author author,
@Nullable String text, long autoDeleteTimer);
/**
* Encodes an accept message without an auto-delete timer.
*/
Message encodeAcceptMessage(GroupId contactGroupId, long timestamp, Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId, @Nullable MessageId previousMessageId, SessionId sessionId,
PublicKey ephemeralPublicKey, long acceptTimestamp, PublicKey ephemeralPublicKey, long acceptTimestamp,
Map<TransportId, TransportProperties> transportProperties); Map<TransportId, TransportProperties> transportProperties);
/**
* Encodes an accept message with an optional auto-delete timer. This
* requires the contact to support client version 0.1 or higher.
*/
Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId,
PublicKey ephemeralPublicKey, long acceptTimestamp,
Map<TransportId, TransportProperties> transportProperties,
long autoDeleteTimer);
/**
* Encodes a decline message without an auto-delete timer.
*/
Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId); @Nullable MessageId previousMessageId, SessionId sessionId);
/**
* Encodes a decline message with an optional auto-delete timer. This
* requires the contact to support client version 0.1 or higher.
*/
Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId,
long autoDeleteTimer);
Message encodeAuthMessage(GroupId contactGroupId, long timestamp, Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId, @Nullable MessageId previousMessageId, SessionId sessionId,
byte[] mac, byte[] signature); byte[] mac, byte[] signature);

View File

@@ -20,7 +20,9 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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.introduction.IntroductionConstants.MSG_KEY_AUTO_DELETE_TIMER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
@@ -48,9 +50,10 @@ class MessageEncoderImpl implements MessageEncoder {
} }
@Override @Override
public BdfDictionary encodeRequestMetadata(long timestamp) { public BdfDictionary encodeRequestMetadata(long timestamp,
BdfDictionary meta = long autoDeleteTimer) {
encodeMetadata(REQUEST, null, timestamp, false, false, false); BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp,
false, false, false, autoDeleteTimer);
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false); meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
return meta; return meta;
} }
@@ -58,7 +61,7 @@ class MessageEncoderImpl implements MessageEncoder {
@Override @Override
public BdfDictionary encodeMetadata(MessageType type, public BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp, boolean local, @Nullable SessionId sessionId, long timestamp, boolean local,
boolean read, boolean visible) { boolean read, boolean visible, long autoDeleteTimer) {
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue()); meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
if (sessionId != null) if (sessionId != null)
@@ -69,6 +72,9 @@ class MessageEncoderImpl implements MessageEncoder {
meta.put(MSG_KEY_LOCAL, local); meta.put(MSG_KEY_LOCAL, local);
meta.put(MSG_KEY_READ, read); meta.put(MSG_KEY_READ, read);
meta.put(MSG_KEY_VISIBLE_IN_UI, visible); meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
if (autoDeleteTimer != NO_AUTO_DELETE_TIMER) {
meta.put(MSG_KEY_AUTO_DELETE_TIMER, autoDeleteTimer);
}
return meta; return meta;
} }
@@ -103,6 +109,23 @@ class MessageEncoderImpl implements MessageEncoder {
return createMessage(contactGroupId, timestamp, body); return createMessage(contactGroupId, timestamp, body);
} }
@Override
public Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, Author author,
@Nullable String text, long autoDeleteTimer) {
if (text != null && text.isEmpty()) {
throw new IllegalArgumentException();
}
BdfList body = BdfList.of(
REQUEST.getValue(),
previousMessageId,
clientHelper.toList(author),
text,
encodeTimer(autoDeleteTimer)
);
return createMessage(contactGroupId, timestamp, body);
}
@Override @Override
public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp, public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId, @Nullable MessageId previousMessageId, SessionId sessionId,
@@ -119,11 +142,46 @@ class MessageEncoderImpl implements MessageEncoder {
return createMessage(contactGroupId, timestamp, body); return createMessage(contactGroupId, timestamp, body);
} }
@Override
public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId,
PublicKey ephemeralPublicKey, long acceptTimestamp,
Map<TransportId, TransportProperties> transportProperties,
long autoDeleteTimer) {
BdfList body = BdfList.of(
ACCEPT.getValue(),
sessionId,
previousMessageId,
ephemeralPublicKey.getEncoded(),
acceptTimestamp,
clientHelper.toDictionary(transportProperties),
encodeTimer(autoDeleteTimer)
);
return createMessage(contactGroupId, timestamp, body);
}
@Override @Override
public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId) { @Nullable MessageId previousMessageId, SessionId sessionId) {
return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp, BdfList body = BdfList.of(
previousMessageId); DECLINE.getValue(),
sessionId,
previousMessageId
);
return createMessage(contactGroupId, timestamp, body);
}
@Override
public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId,
long autoDeleteTimer) {
BdfList body = BdfList.of(
DECLINE.getValue(),
sessionId,
previousMessageId,
encodeTimer(autoDeleteTimer)
);
return createMessage(contactGroupId, timestamp, body);
} }
@Override @Override
@@ -156,15 +214,8 @@ class MessageEncoderImpl implements MessageEncoder {
@Override @Override
public Message encodeAbortMessage(GroupId contactGroupId, long timestamp, public Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId) { @Nullable MessageId previousMessageId, SessionId sessionId) {
return encodeMessage(ABORT, contactGroupId, sessionId, timestamp,
previousMessageId);
}
private Message encodeMessage(MessageType type, GroupId contactGroupId,
SessionId sessionId, long timestamp,
@Nullable MessageId previousMessageId) {
BdfList body = BdfList.of( BdfList body = BdfList.of(
type.getValue(), ABORT.getValue(),
sessionId, sessionId,
previousMessageId previousMessageId
); );
@@ -181,4 +232,8 @@ class MessageEncoderImpl implements MessageEncoder {
} }
} }
@Nullable
private Long encodeTimer(long autoDeleteTimer) {
return autoDeleteTimer == NO_AUTO_DELETE_TIMER ? null : autoDeleteTimer;
}
} }

View File

@@ -13,12 +13,12 @@ class MessageMetadata {
private final MessageType type; private final MessageType type;
@Nullable @Nullable
private final SessionId sessionId; private final SessionId sessionId;
private final long timestamp; private final long timestamp, autoDeleteTimer;
private final boolean local, read, visible, available; private final boolean local, read, visible, available;
MessageMetadata(MessageType type, @Nullable SessionId sessionId, MessageMetadata(MessageType type, @Nullable SessionId sessionId,
long timestamp, boolean local, boolean read, boolean visible, long timestamp, boolean local, boolean read, boolean visible,
boolean available) { boolean available, long autoDeleteTimer) {
this.type = type; this.type = type;
this.sessionId = sessionId; this.sessionId = sessionId;
this.timestamp = timestamp; this.timestamp = timestamp;
@@ -26,6 +26,7 @@ class MessageMetadata {
this.read = read; this.read = read;
this.visible = visible; this.visible = visible;
this.available = available; this.available = available;
this.autoDeleteTimer = autoDeleteTimer;
} }
MessageType getMessageType() { MessageType getMessageType() {
@@ -57,4 +58,7 @@ class MessageMetadata {
return available; return available;
} }
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
} }

View File

@@ -19,7 +19,9 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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.introduction.IntroductionConstants.MSG_KEY_AUTO_DELETE_TIMER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
@@ -65,8 +67,9 @@ class MessageParserImpl implements MessageParser {
boolean read = d.getBoolean(MSG_KEY_READ); boolean read = d.getBoolean(MSG_KEY_READ);
boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI); boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false); boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
long timer = d.getLong(MSG_KEY_AUTO_DELETE_TIMER, NO_AUTO_DELETE_TIMER);
return new MessageMetadata(type, sessionId, timestamp, local, read, return new MessageMetadata(type, sessionId, timestamp, local, read,
visible, available); visible, available, timer);
} }
@Override @Override
@@ -77,8 +80,10 @@ class MessageParserImpl implements MessageParser {
new MessageId(previousMsgBytes)); new MessageId(previousMsgBytes));
Author author = clientHelper.parseAndValidateAuthor(body.getList(2)); Author author = clientHelper.parseAndValidateAuthor(body.getList(2));
String text = body.getOptionalString(3); String text = body.getOptionalString(3);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 5) timer = body.getLong(4, NO_AUTO_DELETE_TIMER);
return new RequestMessage(m.getId(), m.getGroupId(), return new RequestMessage(m.getId(), m.getGroupId(),
m.getTimestamp(), previousMessageId, author, text); m.getTimestamp(), previousMessageId, author, text, timer);
} }
@Override @Override
@@ -92,9 +97,11 @@ class MessageParserImpl implements MessageParser {
long acceptTimestamp = body.getLong(4); long acceptTimestamp = body.getLong(4);
Map<TransportId, TransportProperties> transportProperties = clientHelper Map<TransportId, TransportProperties> transportProperties = clientHelper
.parseAndValidateTransportPropertiesMap(body.getDictionary(5)); .parseAndValidateTransportPropertiesMap(body.getDictionary(5));
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 7) timer = body.getLong(6, NO_AUTO_DELETE_TIMER);
return new AcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(), return new AcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
previousMessageId, sessionId, ephemeralPublicKey, previousMessageId, sessionId, ephemeralPublicKey,
acceptTimestamp, transportProperties); acceptTimestamp, transportProperties, timer);
} }
@Override @Override
@@ -104,8 +111,10 @@ class MessageParserImpl implements MessageParser {
byte[] previousMsgBytes = body.getOptionalRaw(2); byte[] previousMsgBytes = body.getOptionalRaw(2);
MessageId previousMessageId = (previousMsgBytes == null ? null : MessageId previousMessageId = (previousMsgBytes == null ? null :
new MessageId(previousMsgBytes)); new MessageId(previousMsgBytes));
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 4) timer = body.getLong(3, NO_AUTO_DELETE_TIMER);
return new DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(), return new DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
previousMessageId, sessionId); previousMessageId, sessionId, timer);
} }
@Override @Override

View File

@@ -8,16 +8,14 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
interface ProtocolEngine<S extends Session> { interface ProtocolEngine<S extends Session<?>> {
S onRequestAction(Transaction txn, S session, @Nullable String text, S onRequestAction(Transaction txn, S session, @Nullable String text)
long timestamp) throws DbException;
S onAcceptAction(Transaction txn, S session, long timestamp)
throws DbException; throws DbException;
S onDeclineAction(Transaction txn, S session, long timestamp) S onAcceptAction(Transaction txn, S session) throws DbException;
throws DbException;
S onDeclineAction(Transaction txn, S session) throws DbException;
S onRequestMessage(Transaction txn, S session, RequestMessage m) S onRequestMessage(Transaction txn, S session, RequestMessage m)
throws DbException, FormatException; throws DbException, FormatException;

View File

@@ -18,8 +18,9 @@ class RequestMessage extends AbstractIntroductionMessage {
RequestMessage(MessageId messageId, GroupId groupId, long timestamp, RequestMessage(MessageId messageId, GroupId groupId, long timestamp,
@Nullable MessageId previousMessageId, Author author, @Nullable MessageId previousMessageId, Author author,
@Nullable String text) { @Nullable String text, long autoDeleteTimer) {
super(messageId, groupId, timestamp, previousMessageId); super(messageId, groupId, timestamp, previousMessageId,
autoDeleteTimer);
this.author = author; this.author = author;
this.text = text; this.text = text;
} }

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
@@ -20,16 +21,20 @@ import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.lang.Math.max;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ConversationManagerImpl implements ConversationManager { class ConversationManagerImpl implements ConversationManager {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Clock clock;
private final Set<ConversationClient> clients; private final Set<ConversationClient> clients;
@Inject @Inject
ConversationManagerImpl(DatabaseComponent db) { ConversationManagerImpl(DatabaseComponent db, Clock clock) {
this.db = db; this.db = db;
this.clock = clock;
clients = new CopyOnWriteArraySet<>(); clients = new CopyOnWriteArraySet<>();
} }
@@ -57,24 +62,33 @@ class ConversationManagerImpl implements ConversationManager {
@Override @Override
public GroupCount getGroupCount(ContactId contactId) throws DbException { public GroupCount getGroupCount(ContactId contactId) throws DbException {
return db.transactionWithResult(true, txn ->
getGroupCount(txn, contactId));
}
@Override
public GroupCount getGroupCount(Transaction txn, ContactId contactId)
throws DbException {
int msgCount = 0, unreadCount = 0; int msgCount = 0, unreadCount = 0;
long latestTime = 0; long latestTime = 0;
Transaction txn = db.startTransaction(true); for (ConversationClient client : clients) {
try { GroupCount count = client.getGroupCount(txn, contactId);
for (ConversationClient client : clients) { msgCount += count.getMsgCount();
GroupCount count = client.getGroupCount(txn, contactId); unreadCount += count.getUnreadCount();
msgCount += count.getMsgCount(); if (count.getLatestMsgTime() > latestTime)
unreadCount += count.getUnreadCount(); latestTime = count.getLatestMsgTime();
if (count.getLatestMsgTime() > latestTime)
latestTime = count.getLatestMsgTime();
}
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
} }
return new GroupCount(msgCount, unreadCount, latestTime); return new GroupCount(msgCount, unreadCount, latestTime);
} }
@Override
public long getTimestampForOutgoingMessage(Transaction txn, ContactId c)
throws DbException {
long now = clock.currentTimeMillis();
GroupCount gc = getGroupCount(txn, c);
return max(now, gc.getLatestMsgTime() + 1);
}
@Override @Override
public DeletionResult deleteAllMessages(ContactId c) throws DbException { public DeletionResult deleteAllMessages(ContactId c) throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn -> {
@@ -87,8 +101,8 @@ class ConversationManagerImpl implements ConversationManager {
} }
@Override @Override
public DeletionResult deleteMessages(ContactId c, Collection<MessageId> toDelete) public DeletionResult deleteMessages(ContactId c,
throws DbException { Collection<MessageId> toDelete) throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn -> {
DeletionResult result = new DeletionResult(); DeletionResult result = new DeletionResult();
for (ConversationClient client : clients) { for (ConversationClient client : clients) {

View File

@@ -2,9 +2,6 @@ package org.briarproject.briar.messaging;
interface MessagingConstants { interface MessagingConstants {
// Metadata keys for groups
String GROUP_KEY_CONTACT_ID = "contactId";
// Metadata keys for messages // Metadata keys for messages
String MSG_KEY_TIMESTAMP = "timestamp"; String MSG_KEY_TIMESTAMP = "timestamp";
String MSG_KEY_LOCAL = "local"; String MSG_KEY_LOCAL = "local";
@@ -13,4 +10,5 @@ interface MessagingConstants {
String MSG_KEY_DESCRIPTOR_LENGTH = "descriptorLength"; String MSG_KEY_DESCRIPTOR_LENGTH = "descriptorLength";
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

@@ -27,6 +27,7 @@ import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient; import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
@@ -38,6 +39,7 @@ import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.InvalidAttachmentException; import org.briarproject.briar.api.messaging.InvalidAttachmentException;
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;
@@ -60,11 +62,15 @@ import static java.util.Collections.emptyList;
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.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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.api.messaging.PrivateMessageFormat.TEXT_ONLY;
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.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_CONTENT_TYPE; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
@@ -84,18 +90,24 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
private final MessageTracker messageTracker; private final MessageTracker messageTracker;
private final ClientVersioningManager clientVersioningManager; private final ClientVersioningManager clientVersioningManager;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
private final AutoDeleteManager autoDeleteManager;
@Inject @Inject
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, MessagingManagerImpl(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MessageTracker messageTracker, MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory) { MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory,
AutoDeleteManager autoDeleteManager) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.messageTracker = messageTracker; this.messageTracker = messageTracker;
this.clientVersioningManager = clientVersioningManager; this.clientVersioningManager = clientVersioningManager;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.autoDeleteManager = autoDeleteManager;
} }
@Override @Override
@@ -133,13 +145,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
c.getId(), CLIENT_ID, MAJOR_VERSION); c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client); db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary d = new BdfDictionary(); clientHelper.setContactId(txn, g.getId(), c.getId());
d.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
} catch (FormatException e) {
throw new AssertionError(e);
}
// Initialize the group count with current time // Initialize the group count with current time
messageTracker.initializeGroupCount(txn, g.getId()); messageTracker.initializeGroupCount(txn, g.getId());
} }
@@ -196,14 +202,18 @@ 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,
NO_AUTO_DELETE_TIMER);
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);
txn.attach(event); txn.attach(event);
messageTracker.trackIncomingMessage(txn, m); messageTracker.trackIncomingMessage(txn, m);
autoDeleteManager.receiveAutoDeleteTimer(txn, contactId, timer,
timestamp);
} }
private List<AttachmentHeader> parseAttachmentHeaders(BdfDictionary meta) private List<AttachmentHeader> parseAttachmentHeaders(BdfDictionary meta)
@@ -228,13 +238,18 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
@Override @Override
public void addLocalMessage(PrivateMessage m) throws DbException { public void addLocalMessage(PrivateMessage m) throws DbException {
Transaction txn = db.startTransaction(false); db.transaction(false, txn -> addLocalMessage(txn, m));
}
@Override
public void addLocalMessage(Transaction txn, PrivateMessage m)
throws DbException {
try { try {
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
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_ONLY) {
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();
@@ -243,6 +258,12 @@ 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 != NO_AUTO_DELETE_TIMER) {
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()) {
@@ -252,11 +273,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true, clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
false); false);
messageTracker.trackOutgoingMessage(txn, m.getMessage()); messageTracker.trackOutgoingMessage(txn, m.getMessage());
db.commitTransaction(txn);
} catch (FormatException e) { } catch (FormatException e) {
throw new AssertionError(e); throw new AssertionError(e);
} finally {
db.endTransaction(txn);
} }
} }
@@ -355,12 +373,14 @@ 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(), NO_AUTO_DELETE_TIMER));
} 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,
NO_AUTO_DELETE_TIMER);
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(meta))); parseAttachmentHeaders(meta), timer));
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
@@ -422,12 +442,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_ONLY;
} }
@Override @Override

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.messaging; package org.briarproject.briar.messaging;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
@@ -20,6 +19,7 @@ import dagger.Provides;
import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID; import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID;
import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION; import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION;
import static org.briarproject.briar.api.messaging.MessagingManager.MINOR_VERSION;
@Module @Module
public class MessagingModule { public class MessagingModule {
@@ -57,17 +57,14 @@ public class MessagingModule {
ContactManager contactManager, ValidationManager validationManager, ContactManager contactManager, ValidationManager validationManager,
ConversationManager conversationManager, ConversationManager conversationManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
FeatureFlags featureFlags, MessagingManagerImpl messagingManager) { MessagingManagerImpl messagingManager) {
lifecycleManager.registerOpenDatabaseHook(messagingManager); lifecycleManager.registerOpenDatabaseHook(messagingManager);
contactManager.registerContactHook(messagingManager); contactManager.registerContactHook(messagingManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
messagingManager); messagingManager);
conversationManager.registerConversationClient(messagingManager); conversationManager.registerConversationClient(messagingManager);
// Advertise the current or previous minor version depending on the
// feature flag
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 2 : 0;
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
minorVersion, messagingManager); MINOR_VERSION, messagingManager);
return messagingManager; return messagingManager;
} }

View File

@@ -17,6 +17,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong; import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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.messaging.MessageTypes.PRIVATE_MESSAGE; import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
@@ -47,21 +48,43 @@ 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);
if (text == null) { BdfList attachmentList = serialiseAttachmentHeaders(headers);
if (headers.isEmpty()) throw new IllegalArgumentException();
} else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) {
throw new IllegalArgumentException();
}
// Serialise the attachment headers
BdfList attachmentList = new BdfList();
for (AttachmentHeader a : headers) {
attachmentList.add(
BdfList.of(a.getMessageId(), a.getContentType()));
}
// Serialise the message // Serialise the message
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList); BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
Message m = clientHelper.createMessage(groupId, timestamp, body); Message m = clientHelper.createMessage(groupId, timestamp, body);
return new PrivateMessage(m, text != null, headers); return new PrivateMessage(m, text != null, headers);
} }
@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 == NO_AUTO_DELETE_TIMER ?
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 (headers.isEmpty()) throw new IllegalArgumentException();
} else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) {
throw new IllegalArgumentException();
}
}
private BdfList serialiseAttachmentHeaders(List<AttachmentHeader> headers) {
BdfList attachmentList = new BdfList();
for (AttachmentHeader a : headers) {
attachmentList.add(
BdfList.of(a.getMessageId(), a.getContentType()));
}
return attachmentList;
}
} }

View File

@@ -27,6 +27,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_L
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;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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_CONTENT_TYPE_BYTES; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_BYTES;
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;
@@ -34,12 +35,14 @@ 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_CONTENT_TYPE; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
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;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP; import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -99,7 +102,7 @@ class PrivateMessageValidator implements MessageValidator {
private BdfMessageContext validateLegacyPrivateMessage(Message m, private BdfMessageContext validateLegacyPrivateMessage(Message m,
BdfList body) throws FormatException { BdfList body) throws FormatException {
// Private message text // Client version 0.0: Private message text
checkSize(body, 1); checkSize(body, 1);
String text = body.getString(0); String text = body.getString(0);
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH); checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
@@ -113,8 +116,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 // Client version 0.1 to 0.2: Message type, optional private message
checkSize(body, 3); // text, attachment headers.
// Client version 0.3: 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);
@@ -129,6 +135,10 @@ 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 = NO_AUTO_DELETE_TIMER;
if (body.size() == 4) {
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
}
// 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());
@@ -137,6 +147,9 @@ 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 != NO_AUTO_DELETE_TIMER) {
meta.put(MSG_KEY_AUTO_DELETE_TIMER, timer);
}
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }

View File

@@ -17,21 +17,23 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; import static java.lang.Math.max;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN; import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
@@ -39,7 +41,7 @@ import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
abstract class AbstractProtocolEngine<S extends Session> abstract class AbstractProtocolEngine<S extends Session<?>>
implements ProtocolEngine<S> { implements ProtocolEngine<S> {
protected final DatabaseComponent db; protected final DatabaseComponent db;
@@ -53,15 +55,23 @@ abstract class AbstractProtocolEngine<S extends Session>
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final MessageParser messageParser; private final MessageParser messageParser;
private final MessageEncoder messageEncoder; private final MessageEncoder messageEncoder;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
private final Clock clock; private final Clock clock;
AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, AbstractProtocolEngine(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
PrivateGroupManager privateGroupManager, PrivateGroupManager privateGroupManager,
PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
GroupMessageFactory groupMessageFactory, GroupMessageFactory groupMessageFactory,
IdentityManager identityManager, MessageParser messageParser, IdentityManager identityManager,
MessageEncoder messageEncoder, MessageTracker messageTracker, MessageParser messageParser,
MessageEncoder messageEncoder,
MessageTracker messageTracker,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) { Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
@@ -73,16 +83,11 @@ abstract class AbstractProtocolEngine<S extends Session>
this.messageParser = messageParser; this.messageParser = messageParser;
this.messageEncoder = messageEncoder; this.messageEncoder = messageEncoder;
this.messageTracker = messageTracker; this.messageTracker = messageTracker;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
this.clock = clock; this.clock = clock;
} }
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
}
boolean isSubscribedPrivateGroup(Transaction txn, GroupId g) boolean isSubscribedPrivateGroup(Transaction txn, GroupId g)
throws DbException { throws DbException {
if (!db.containsGroup(txn, g)) return false; if (!db.containsGroup(txn, g)) return false;
@@ -90,6 +95,7 @@ abstract class AbstractProtocolEngine<S extends Session>
return group.getClientId().equals(PrivateGroupManager.CLIENT_ID); return group.getClientId().equals(PrivateGroupManager.CLIENT_ID);
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
boolean isValidDependency(S session, @Nullable MessageId dependency) { boolean isValidDependency(S session, @Nullable MessageId dependency) {
MessageId expected = session.getLastRemoteMessageId(); MessageId expected = session.getLastRemoteMessageId();
if (dependency == null) return expected == null; if (dependency == null) return expected == null;
@@ -99,54 +105,108 @@ abstract class AbstractProtocolEngine<S extends Session>
void setPrivateGroupVisibility(Transaction txn, S session, void setPrivateGroupVisibility(Transaction txn, S session,
Visibility preferred) throws DbException, FormatException { Visibility preferred) throws DbException, FormatException {
// Apply min of preferred visibility and client's visibility // Apply min of preferred visibility and client's visibility
ContactId contactId = getContactId(txn, session.getContactGroupId()); ContactId contactId =
clientHelper.getContactId(txn, session.getContactGroupId());
Visibility client = clientVersioningManager.getClientVisibility(txn, Visibility client = clientVersioningManager.getClientVisibility(txn,
contactId, CLIENT_ID, MAJOR_VERSION); contactId, PrivateGroupManager.CLIENT_ID,
PrivateGroupManager.MAJOR_VERSION);
Visibility min = Visibility.min(preferred, client); Visibility min = Visibility.min(preferred, client);
db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), min); db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), min);
} }
Message sendInviteMessage(Transaction txn, S session, Message sendInviteMessage(Transaction txn, S s,
@Nullable String text, long timestamp, byte[] signature) @Nullable String text, long timestamp, byte[] signature,
throws DbException { long timer) throws DbException {
Group g = db.getGroup(txn, session.getPrivateGroupId()); Group g = db.getGroup(txn, s.getPrivateGroupId());
PrivateGroup privateGroup; PrivateGroup privateGroup;
try { try {
privateGroup = privateGroupFactory.parsePrivateGroup(g); privateGroup = privateGroupFactory.parsePrivateGroup(g);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); // Invalid group descriptor throw new DbException(e); // Invalid group descriptor
} }
Message m = messageEncoder.encodeInviteMessage( Message m;
session.getContactGroupId(), privateGroup.getId(), ContactId c = getContactId(txn, s.getContactGroupId());
timestamp, privateGroup.getName(), privateGroup.getCreator(), if (contactSupportsAutoDeletion(txn, c)) {
privateGroup.getSalt(), text, signature); m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
sendMessage(txn, m, INVITE, privateGroup.getId(), true); privateGroup.getId(), timestamp, privateGroup.getName(),
privateGroup.getCreator(), privateGroup.getSalt(), text,
signature, timer);
sendMessage(txn, m, INVITE, privateGroup.getId(), true, timer);
} else {
m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
privateGroup.getId(), timestamp, privateGroup.getName(),
privateGroup.getCreator(), privateGroup.getSalt(), text,
signature);
sendMessage(txn, m, INVITE, privateGroup.getId(), true,
NO_AUTO_DELETE_TIMER);
}
return m; return m;
} }
Message sendJoinMessage(Transaction txn, S session, boolean visibleInUi) Message sendJoinMessage(Transaction txn, S s, boolean visibleInUi)
throws DbException { throws DbException {
Message m = messageEncoder.encodeJoinMessage( Message m;
session.getContactGroupId(), session.getPrivateGroupId(), long localTimestamp = visibleInUi
getLocalTimestamp(session), session.getLastLocalMessageId()); ? getTimestampForVisibleMessage(txn, s)
sendMessage(txn, m, JOIN, session.getPrivateGroupId(), visibleInUi); : getTimestampForInvisibleMessage(s);
ContactId c = getContactId(txn, s.getContactGroupId());
if (contactSupportsAutoDeletion(txn, c)) {
// Set auto-delete timer if manually accepting an invitation
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);
sendMessage(txn, m, JOIN, s.getPrivateGroupId(), visibleInUi,
timer);
} else {
m = messageEncoder.encodeJoinMessage(s.getContactGroupId(),
s.getPrivateGroupId(), localTimestamp,
s.getLastLocalMessageId());
sendMessage(txn, m, JOIN, s.getPrivateGroupId(), visibleInUi,
NO_AUTO_DELETE_TIMER);
}
return m; return m;
} }
Message sendLeaveMessage(Transaction txn, S session, boolean visibleInUi) Message sendLeaveMessage(Transaction txn, S s, boolean visibleInUi)
throws DbException { throws DbException {
Message m = messageEncoder.encodeLeaveMessage( Message m;
session.getContactGroupId(), session.getPrivateGroupId(), long localTimestamp = visibleInUi
getLocalTimestamp(session), session.getLastLocalMessageId()); ? getTimestampForVisibleMessage(txn, s)
sendMessage(txn, m, LEAVE, session.getPrivateGroupId(), visibleInUi); : getTimestampForInvisibleMessage(s);
ContactId c = getContactId(txn, s.getContactGroupId());
if (contactSupportsAutoDeletion(txn, c)) {
// Set auto-delete timer if manually accepting an invitation
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);
sendMessage(txn, m, LEAVE, s.getPrivateGroupId(), visibleInUi,
timer);
} else {
m = messageEncoder.encodeLeaveMessage(s.getContactGroupId(),
s.getPrivateGroupId(), localTimestamp,
s.getLastLocalMessageId());
sendMessage(txn, m, LEAVE, s.getPrivateGroupId(), visibleInUi,
NO_AUTO_DELETE_TIMER);
}
return m; return m;
} }
Message sendAbortMessage(Transaction txn, S session) throws DbException { Message sendAbortMessage(Transaction txn, S session) throws DbException {
Message m = messageEncoder.encodeAbortMessage( Message m = messageEncoder.encodeAbortMessage(
session.getContactGroupId(), session.getPrivateGroupId(), session.getContactGroupId(), session.getPrivateGroupId(),
getLocalTimestamp(session)); getTimestampForInvisibleMessage(session));
sendMessage(txn, m, ABORT, session.getPrivateGroupId(), false); sendMessage(txn, m, ABORT, session.getPrivateGroupId(), false,
NO_AUTO_DELETE_TIMER);
return m; return m;
} }
@@ -200,7 +260,7 @@ abstract class AbstractProtocolEngine<S extends Session>
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup( PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
invite.getGroupName(), invite.getCreator(), invite.getSalt()); invite.getGroupName(), invite.getCreator(), invite.getSalt());
long timestamp = long timestamp =
Math.max(clock.currentTimeMillis(), invite.getTimestamp() + 1); max(clock.currentTimeMillis(), invite.getTimestamp() + 1);
// TODO: Create the join message on the crypto executor // TODO: Create the join message on the crypto executor
LocalAuthor member = identityManager.getLocalAuthor(txn); LocalAuthor member = identityManager.getLocalAuthor(txn);
GroupMessage joinMessage = groupMessageFactory.createJoinMessage( GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
@@ -210,18 +270,49 @@ abstract class AbstractProtocolEngine<S extends Session>
.addPrivateGroup(txn, privateGroup, joinMessage, false); .addPrivateGroup(txn, privateGroup, joinMessage, false);
} }
long getLocalTimestamp(S session) { /**
return Math.max(clock.currentTimeMillis(), * Returns a timestamp for a visible outgoing message. The timestamp is
Math.max(session.getLocalTimestamp(), * later than the timestamp of any message sent or received so far in the
session.getInviteTimestamp()) + 1); * conversation, and later than the {@link #getSessionTimestamp(Session)
* session timestamp}.
*/
long getTimestampForVisibleMessage(Transaction txn, S s)
throws DbException {
ContactId c = getContactId(txn, s.getContactGroupId());
long conversationTimestamp =
conversationManager.getTimestampForOutgoingMessage(txn, c);
return max(conversationTimestamp, getSessionTimestamp(s) + 1);
}
/**
* Returns a timestamp for an invisible outgoing message. The timestamp is
* later than the {@link #getSessionTimestamp(Session) session timestamp}.
*/
long getTimestampForInvisibleMessage(S s) {
return max(clock.currentTimeMillis(), getSessionTimestamp(s) + 1);
}
/**
* Returns the latest timestamp of any message sent so far in the session,
* and any invite message sent or received so far in the session.
*/
private long getSessionTimestamp(S s) {
return max(s.getLocalTimestamp(), s.getInviteTimestamp());
}
void receiveAutoDeleteTimer(Transaction txn,
DeletableGroupInvitationMessage m) throws DbException {
ContactId c = getContactId(txn, m.getContactGroupId());
autoDeleteManager.receiveAutoDeleteTimer(txn, c, m.getAutoDeleteTimer(),
m.getTimestamp());
} }
private void sendMessage(Transaction txn, Message m, MessageType type, private void sendMessage(Transaction txn, Message m, MessageType type,
GroupId privateGroupId, boolean visibleInConversation) GroupId privateGroupId, boolean visibleInConversation,
throws DbException { long autoDeleteTimer) throws DbException {
BdfDictionary meta = messageEncoder BdfDictionary meta = messageEncoder.encodeMetadata(type,
.encodeMetadata(type, privateGroupId, m.getTimestamp(), true, privateGroupId, m.getTimestamp(), true, true,
true, visibleInConversation, false, false); visibleInConversation, false, false, autoDeleteTimer);
try { try {
clientHelper.addLocalMessage(txn, m, meta, true, false); clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) { } catch (FormatException e) {
@@ -229,4 +320,21 @@ abstract class AbstractProtocolEngine<S extends Session>
} }
} }
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
return clientHelper.getContactId(txn, contactGroupId);
} catch (FormatException e) {
throw new DbException(e);
}
}
private boolean contactSupportsAutoDeletion(Transaction txn, ContactId c)
throws DbException {
int minorVersion = clientVersioningManager.getClientMinorVersion(txn, c,
GroupInvitationManager.CLIENT_ID,
GroupInvitationManager.MAJOR_VERSION);
// Auto-delete was added in client version 0.1
return minorVersion >= 1;
}
} }

View File

@@ -11,9 +11,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
@@ -23,6 +25,7 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRespons
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.lang.Math.max;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED; import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED;
@@ -36,26 +39,34 @@ import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
@NotNullByDefault @NotNullByDefault
class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> { class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
CreatorProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, CreatorProtocolEngine(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
PrivateGroupManager privateGroupManager, PrivateGroupManager privateGroupManager,
PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
GroupMessageFactory groupMessageFactory, GroupMessageFactory groupMessageFactory,
IdentityManager identityManager, MessageParser messageParser, IdentityManager identityManager,
MessageEncoder messageEncoder, MessageTracker messageTracker, MessageParser messageParser,
MessageEncoder messageEncoder,
MessageTracker messageTracker,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) { Clock clock) {
super(db, clientHelper, clientVersioningManager, privateGroupManager, super(db, clientHelper, clientVersioningManager, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager, privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock); messageParser, messageEncoder, messageTracker,
autoDeleteManager, conversationManager, clock);
} }
@Override @Override
public CreatorSession onInviteAction(Transaction txn, CreatorSession s, public CreatorSession onInviteAction(Transaction txn, CreatorSession s,
@Nullable String text, long timestamp, byte[] signature) @Nullable String text, long timestamp, byte[] signature,
throws DbException { long autoDeleteTimer) throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
return onLocalInvite(txn, s, text, timestamp, signature); return onLocalInvite(txn, s, text, timestamp, signature,
autoDeleteTimer);
case INVITED: case INVITED:
case JOINED: case JOINED:
case LEFT: case LEFT:
@@ -145,14 +156,16 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
} }
private CreatorSession onLocalInvite(Transaction txn, CreatorSession s, private CreatorSession onLocalInvite(Transaction txn, CreatorSession s,
@Nullable String text, long timestamp, byte[] signature) @Nullable String text, long timestamp, byte[] signature,
throws DbException { long autoDeleteTimer) throws DbException {
// Send an INVITE message // Send an INVITE message
Message sent = sendInviteMessage(txn, s, text, timestamp, signature); Message sent = sendInviteMessage(txn, s, text, timestamp, signature,
autoDeleteTimer);
// Track the message // Track the message
messageTracker.trackOutgoingMessage(txn, sent); messageTracker.trackOutgoingMessage(txn, sent);
// Move to the INVITED state // Move to the INVITED state
long localTimestamp = Math.max(timestamp, getLocalTimestamp(s)); long localTimestamp =
max(timestamp, getTimestampForVisibleMessage(txn, s));
return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(), return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), localTimestamp, sent.getId(), s.getLastRemoteMessageId(), localTimestamp,
timestamp, INVITED); timestamp, INVITED);
@@ -188,10 +201,13 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
// Track the message // Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(), messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false); m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Share the private group with the contact // Share the private group with the contact
setPrivateGroupVisibility(txn, s, SHARED); setPrivateGroupVisibility(txn, s, SHARED);
// Broadcast an event // Broadcast an event
ContactId contactId = getContactId(txn, m.getContactGroupId()); ContactId contactId =
clientHelper.getContactId(txn, m.getContactGroupId());
txn.attach(new GroupInvitationResponseReceivedEvent( txn.attach(new GroupInvitationResponseReceivedEvent(
createInvitationResponse(m, true), contactId)); createInvitationResponse(m, true), contactId));
// Move to the JOINED state // Move to the JOINED state
@@ -212,8 +228,11 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
// Track the message // Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(), messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false); m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Broadcast an event // Broadcast an event
ContactId contactId = getContactId(txn, m.getContactGroupId()); ContactId contactId =
clientHelper.getContactId(txn, m.getContactGroupId());
txn.attach(new GroupInvitationResponseReceivedEvent( txn.attach(new GroupInvitationResponseReceivedEvent(
createInvitationResponse(m, false), contactId)); createInvitationResponse(m, false), contactId));
// Move to the START state // Move to the START state
@@ -253,10 +272,10 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
} }
private GroupInvitationResponse createInvitationResponse( private GroupInvitationResponse createInvitationResponse(
GroupInvitationMessage m, boolean accept) { DeletableGroupInvitationMessage m, boolean accept) {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes()); SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationResponse(m.getId(), m.getContactGroupId(), return new GroupInvitationResponse(m.getId(), m.getContactGroupId(),
m.getTimestamp(), false, false, false, false, sessionId, m.getTimestamp(), false, false, false, false, sessionId,
accept, m.getPrivateGroupId()); accept, m.getPrivateGroupId(), m.getAutoDeleteTimer());
} }
} }

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
abstract class DeletableGroupInvitationMessage extends GroupInvitationMessage {
private final long autoDeleteTimer;
DeletableGroupInvitationMessage(MessageId id, GroupId contactGroupId,
GroupId privateGroupId, long timestamp, long autoDeleteTimer) {
super(id, contactGroupId, privateGroupId, timestamp);
this.autoDeleteTimer = autoDeleteTimer;
}
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
}

View File

@@ -2,9 +2,6 @@ package org.briarproject.briar.privategroup.invitation;
interface GroupInvitationConstants { interface GroupInvitationConstants {
// Group metadata keys
String GROUP_KEY_CONTACT_ID = "contactId";
// Message metadata keys // Message metadata keys
String MSG_KEY_MESSAGE_TYPE = "messageType"; String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId"; String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId";
@@ -13,6 +10,7 @@ interface GroupInvitationConstants {
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi"; String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted";
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
// Session keys // Session keys
String SESSION_KEY_IS_SESSION = "isSession"; String SESSION_KEY_IS_SESSION = "isSession";

View File

@@ -53,7 +53,6 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.START; import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN; import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
@@ -123,13 +122,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
c.getId(), CLIENT_ID, MAJOR_VERSION); c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client); db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary(); clientHelper.setContactId(txn, g.getId(), c.getId());
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
// If the contact belongs to any private groups, create a peer session // If the contact belongs to any private groups, create a peer session
for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID, for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID,
PrivateGroupManager.MAJOR_VERSION)) { PrivateGroupManager.MAJOR_VERSION)) {
@@ -159,7 +152,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
SessionId sessionId = getSessionId(meta.getPrivateGroupId()); SessionId sessionId = getSessionId(meta.getPrivateGroupId());
StoredSession ss = getSession(txn, m.getGroupId(), sessionId); StoredSession ss = getSession(txn, m.getGroupId(), sessionId);
// Handle the message // Handle the message
Session session; Session<?> session;
MessageId storageId; MessageId storageId;
if (ss == null) { if (ss == null) {
session = handleFirstMessage(txn, m, body, meta); session = handleFirstMessage(txn, m, body, meta);
@@ -189,8 +182,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
results.values().iterator().next()); results.values().iterator().next());
} }
private Session handleFirstMessage(Transaction txn, Message m, BdfList body, private Session<?> handleFirstMessage(Transaction txn, Message m,
MessageMetadata meta) throws DbException, FormatException { BdfList body, MessageMetadata meta)
throws DbException, FormatException {
GroupId privateGroupId = meta.getPrivateGroupId(); GroupId privateGroupId = meta.getPrivateGroupId();
MessageType type = meta.getMessageType(); MessageType type = meta.getMessageType();
if (type == INVITE) { if (type == INVITE) {
@@ -206,7 +200,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
} }
private Session handleMessage(Transaction txn, Message m, BdfList body, private Session<?> handleMessage(Transaction txn, Message m, BdfList body,
MessageMetadata meta, BdfDictionary bdfSession) MessageMetadata meta, BdfDictionary bdfSession)
throws DbException, FormatException { throws DbException, FormatException {
MessageType type = meta.getMessageType(); MessageType type = meta.getMessageType();
@@ -228,7 +222,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
} }
private <S extends Session> S handleMessage(Transaction txn, Message m, private <S extends Session<?>> S handleMessage(Transaction txn, Message m,
BdfList body, MessageType type, S session, ProtocolEngine<S> engine) BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
throws DbException, FormatException { throws DbException, FormatException {
if (type == INVITE) { if (type == INVITE) {
@@ -256,15 +250,15 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
private void storeSession(Transaction txn, MessageId storageId, private void storeSession(Transaction txn, MessageId storageId,
Session session) throws DbException, FormatException { Session<?> session) throws DbException, FormatException {
BdfDictionary d = sessionEncoder.encodeSession(session); BdfDictionary d = sessionEncoder.encodeSession(session);
clientHelper.mergeMessageMetadata(txn, storageId, d); clientHelper.mergeMessageMetadata(txn, storageId, d);
} }
@Override @Override
public void sendInvitation(GroupId privateGroupId, ContactId c, public void sendInvitation(GroupId privateGroupId, ContactId c,
@Nullable String text, long timestamp, byte[] signature) @Nullable String text, long timestamp, byte[] signature,
throws DbException { long autoDeleteTimer) throws DbException {
SessionId sessionId = getSessionId(privateGroupId); SessionId sessionId = getSessionId(privateGroupId);
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
@@ -287,7 +281,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
// Handle the invite action // Handle the invite action
session = creatorEngine.onInviteAction(txn, session, text, session = creatorEngine.onInviteAction(txn, session, text,
timestamp, signature); timestamp, signature, autoDeleteTimer);
// Store the updated session // Store the updated session
storeSession(txn, storageId, session); storeSession(txn, storageId, session);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -354,7 +348,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
} }
private <S extends Session> S handleAction(Transaction txn, private <S extends Session<?>> S handleAction(Transaction txn,
LocalAction type, S session, ProtocolEngine<S> engine) LocalAction type, S session, ProtocolEngine<S> engine)
throws DbException { throws DbException {
if (type == LocalAction.INVITE) { if (type == LocalAction.INVITE) {
@@ -420,7 +414,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return new GroupInvitationRequest(m, contactGroupId, return new GroupInvitationRequest(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), meta.isRead(), meta.getTimestamp(), meta.isLocal(), meta.isRead(),
status.isSent(), status.isSeen(), sessionId, pg, status.isSent(), status.isSeen(), sessionId, pg,
invite.getText(), meta.isAvailableToAnswer(), canBeOpened); invite.getText(), meta.isAvailableToAnswer(), canBeOpened,
invite.getAutoDeleteTimer());
} }
private GroupInvitationResponse parseInvitationResponse( private GroupInvitationResponse parseInvitationResponse(
@@ -430,7 +425,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return new GroupInvitationResponse(m, contactGroupId, return new GroupInvitationResponse(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), meta.isRead(), meta.getTimestamp(), meta.isLocal(), meta.isRead(),
status.isSent(), status.isSeen(), sessionId, accept, status.isSent(), status.isSeen(), sessionId, accept,
meta.getPrivateGroupId()); meta.getPrivateGroupId(), meta.getAutoDeleteTimer());
} }
@Override @Override
@@ -508,7 +503,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
SessionId sessionId = getSessionId(privateGroupId); SessionId sessionId = getSessionId(privateGroupId);
StoredSession ss = getSession(txn, contactGroupId, sessionId); StoredSession ss = getSession(txn, contactGroupId, sessionId);
// Create or parse the session // Create or parse the session
Session session; Session<?> session;
MessageId storageId; MessageId storageId;
if (ss == null) { if (ss == null) {
// If there's no session the contact must be a peer, // If there's no session the contact must be a peer,
@@ -543,7 +538,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
StoredSession ss = getSession(txn, contactGroupId, sessionId); StoredSession ss = getSession(txn, contactGroupId, sessionId);
if (ss == null) continue; // No session for this contact if (ss == null) continue; // No session for this contact
// Handle the action // Handle the action
Session session = handleAction(txn, LocalAction.LEAVE, Session<?> session = handleAction(txn, LocalAction.LEAVE,
contactGroupId, ss.bdfSession); contactGroupId, ss.bdfSession);
// Store the updated session // Store the updated session
storeSession(txn, ss.storageId, session); storeSession(txn, ss.storageId, session);
@@ -553,7 +548,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
} }
private Session handleAction(Transaction txn, LocalAction a, private Session<?> handleAction(Transaction txn, LocalAction a,
GroupId contactGroupId, BdfDictionary bdfSession) GroupId contactGroupId, BdfDictionary bdfSession)
throws DbException, FormatException { throws DbException, FormatException {
Role role = sessionParser.getRole(bdfSession); Role role = sessionParser.getRole(bdfSession);
@@ -613,7 +608,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
.getMessageMetadataAsDictionary(txn, contactGroupId, query); .getMessageMetadataAsDictionary(txn, contactGroupId, query);
Map<GroupId, Visibility> m = new HashMap<>(); Map<GroupId, Visibility> m = new HashMap<>();
for (BdfDictionary d : results.values()) { for (BdfDictionary d : results.values()) {
Session s = sessionParser.parseSession(contactGroupId, d); Session<?> s = sessionParser.parseSession(contactGroupId, d);
m.put(s.getPrivateGroupId(), s.getState().getVisibility()); m.put(s.getPrivateGroupId(), s.getState().getVisibility());
} }
return m; return m;
@@ -644,7 +639,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
Map<GroupId, DeletableSession> sessions = new HashMap<>(); Map<GroupId, DeletableSession> sessions = new HashMap<>();
for (BdfDictionary d : metadata.values()) { for (BdfDictionary d : metadata.values()) {
if (!sessionParser.isSession(d)) continue; if (!sessionParser.isSession(d)) continue;
Session session; Session<?> session;
try { try {
session = sessionParser.parseSession(g, d); session = sessionParser.parseSession(g, d);
} catch (FormatException e) { } catch (FormatException e) {
@@ -673,7 +668,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
getSessionId(messageMetadata.getPrivateGroupId()); getSessionId(messageMetadata.getPrivateGroupId());
StoredSession ss = getSession(txn1, g, sessionId); StoredSession ss = getSession(txn1, g, sessionId);
if (ss == null) throw new DbException(); if (ss == null) throw new DbException();
Session session = sessionParser Session<?> session = sessionParser
.parseSession(g, metadata.get(ss.storageId)); .parseSession(g, metadata.get(ss.storageId));
sessions.put(session.getPrivateGroupId(), sessions.put(session.getPrivateGroupId(),
new DeletableSession(session.getState())); new DeletableSession(session.getState()));

View File

@@ -26,6 +26,7 @@ import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_TEXT_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_TEXT_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
@@ -34,6 +35,7 @@ import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN; import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE; import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -71,8 +73,11 @@ class GroupInvitationValidator extends BdfMessageValidator {
private BdfMessageContext validateInviteMessage(Message m, BdfList body) private BdfMessageContext validateInviteMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
// Message type, creator, group name, salt, optional text, signature // Client version 0.0: Message type, creator, group name, salt,
checkSize(body, 6); // optional text, signature.
// Client version 0.1: Message type, creator, group name, salt,
// optional text, signature, optional auto-delete timer.
checkSize(body, 6, 7);
BdfList creatorList = body.getList(1); BdfList creatorList = body.getList(1);
String groupName = body.getString(2); String groupName = body.getString(2);
checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH); checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH);
@@ -82,6 +87,10 @@ class GroupInvitationValidator extends BdfMessageValidator {
checkLength(text, 1, MAX_GROUP_INVITATION_TEXT_LENGTH); checkLength(text, 1, MAX_GROUP_INVITATION_TEXT_LENGTH);
byte[] signature = body.getRaw(5); byte[] signature = body.getRaw(5);
checkLength(signature, 1, MAX_SIGNATURE_LENGTH); checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 7) {
timer = validateAutoDeleteTimer(body.getOptionalLong(6));
}
// Validate the creator and create the private group // Validate the creator and create the private group
Author creator = clientHelper.parseAndValidateAuthor(creatorList); Author creator = clientHelper.parseAndValidateAuthor(creatorList);
@@ -102,20 +111,29 @@ class GroupInvitationValidator extends BdfMessageValidator {
// Create the metadata // Create the metadata
BdfDictionary meta = messageEncoder.encodeMetadata(INVITE, BdfDictionary meta = messageEncoder.encodeMetadata(INVITE,
privateGroup.getId(), m.getTimestamp(), false, false, false, privateGroup.getId(), m.getTimestamp(), false, false, false,
false, false); false, false, timer);
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }
private BdfMessageContext validateJoinMessage(Message m, BdfList body) private BdfMessageContext validateJoinMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
checkSize(body, 3); // Client version 0.0: Message type, private group ID, optional
// previous message ID.
// Client version 0.1: Message type, private group ID, optional
// previous message ID, optional auto-delete timer.
checkSize(body, 3, 4);
byte[] privateGroupId = body.getRaw(1); byte[] privateGroupId = body.getRaw(1);
checkLength(privateGroupId, UniqueId.LENGTH); checkLength(privateGroupId, UniqueId.LENGTH);
byte[] previousMessageId = body.getOptionalRaw(2); byte[] previousMessageId = body.getOptionalRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH); checkLength(previousMessageId, UniqueId.LENGTH);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 4) {
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
}
BdfDictionary meta = messageEncoder.encodeMetadata(JOIN, BdfDictionary meta = messageEncoder.encodeMetadata(JOIN,
new GroupId(privateGroupId), m.getTimestamp(), false, false, new GroupId(privateGroupId), m.getTimestamp(), false, false,
false, false, false); false, false, false, timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -127,14 +145,23 @@ class GroupInvitationValidator extends BdfMessageValidator {
private BdfMessageContext validateLeaveMessage(Message m, BdfList body) private BdfMessageContext validateLeaveMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
checkSize(body, 3); // Client version 0.0: Message type, private group ID, optional
// previous message ID.
// Client version 0.1: Message type, private group ID, optional
// previous message ID, optional auto-delete timer.
checkSize(body, 3, 4);
byte[] privateGroupId = body.getRaw(1); byte[] privateGroupId = body.getRaw(1);
checkLength(privateGroupId, UniqueId.LENGTH); checkLength(privateGroupId, UniqueId.LENGTH);
byte[] previousMessageId = body.getOptionalRaw(2); byte[] previousMessageId = body.getOptionalRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH); checkLength(previousMessageId, UniqueId.LENGTH);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 4) {
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
}
BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE, BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE,
new GroupId(privateGroupId), m.getTimestamp(), false, false, new GroupId(privateGroupId), m.getTimestamp(), false, false,
false, false, false); false, false, false, timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -151,7 +178,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
checkLength(privateGroupId, UniqueId.LENGTH); checkLength(privateGroupId, UniqueId.LENGTH);
BdfDictionary meta = messageEncoder.encodeMetadata(ABORT, BdfDictionary meta = messageEncoder.encodeMetadata(ABORT,
new GroupId(privateGroupId), m.getTimestamp(), false, false, new GroupId(privateGroupId), m.getTimestamp(), false, false,
false, false, false); false, false, false, NO_AUTO_DELETE_TIMER);
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }
} }

View File

@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class InviteMessage extends GroupInvitationMessage { class InviteMessage extends DeletableGroupInvitationMessage {
private final String groupName; private final String groupName;
private final Author creator; private final Author creator;
@@ -20,8 +20,8 @@ class InviteMessage extends GroupInvitationMessage {
InviteMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId, InviteMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
long timestamp, String groupName, Author creator, byte[] salt, long timestamp, String groupName, Author creator, byte[] salt,
@Nullable String text, byte[] signature) { @Nullable String text, byte[] signature, long autoDeleteTimer) {
super(id, contactGroupId, privateGroupId, timestamp); super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
this.groupName = groupName; this.groupName = groupName;
this.creator = creator; this.creator = creator;
this.salt = salt; this.salt = salt;

View File

@@ -13,9 +13,11 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
@@ -41,22 +43,30 @@ import static org.briarproject.briar.privategroup.invitation.InviteeState.START;
@NotNullByDefault @NotNullByDefault
class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
InviteeProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, InviteeProtocolEngine(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
PrivateGroupManager privateGroupManager, PrivateGroupManager privateGroupManager,
PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
GroupMessageFactory groupMessageFactory, GroupMessageFactory groupMessageFactory,
IdentityManager identityManager, MessageParser messageParser, IdentityManager identityManager,
MessageEncoder messageEncoder, MessageTracker messageTracker, MessageParser messageParser,
MessageEncoder messageEncoder,
MessageTracker messageTracker,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) { Clock clock) {
super(db, clientHelper, clientVersioningManager, privateGroupManager, super(db, clientHelper, clientVersioningManager, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager, privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock); messageParser, messageEncoder, messageTracker,
autoDeleteManager, conversationManager, clock);
} }
@Override @Override
public InviteeSession onInviteAction(Transaction txn, InviteeSession s, public InviteeSession onInviteAction(Transaction txn, InviteeSession s,
@Nullable String text, long timestamp, byte[] signature) { @Nullable String text, long timestamp, byte[] signature,
long autoDeleteTimer) {
throw new UnsupportedOperationException(); // Invalid in this role throw new UnsupportedOperationException(); // Invalid in this role
} }
@@ -230,7 +240,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
// The timestamp must be higher than the last invite message, if any // The timestamp must be higher than the last invite message, if any
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s); if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// Check that the contact is the creator // Check that the contact is the creator
ContactId contactId = getContactId(txn, s.getContactGroupId()); ContactId contactId =
clientHelper.getContactId(txn, s.getContactGroupId());
Author contact = db.getContact(txn, contactId).getAuthor(); Author contact = db.getContact(txn, contactId).getAuthor();
if (!contact.getId().equals(m.getCreator().getId())) if (!contact.getId().equals(m.getCreator().getId()))
return abort(txn, s); return abort(txn, s);
@@ -240,6 +251,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
// Track the message // Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(), messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false); m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Broadcast an event // Broadcast an event
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup( PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
m.getGroupName(), m.getCreator(), m.getSalt()); m.getGroupName(), m.getCreator(), m.getSalt());
@@ -330,7 +343,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes()); SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationRequest(m.getId(), m.getContactGroupId(), return new GroupInvitationRequest(m.getId(), m.getContactGroupId(),
m.getTimestamp(), false, false, false, false, sessionId, pg, m.getTimestamp(), false, false, false, false, sessionId, pg,
m.getText(), true, false); m.getText(), true, false, m.getAutoDeleteTimer());
} }
} }

View File

@@ -9,14 +9,15 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class JoinMessage extends GroupInvitationMessage { class JoinMessage extends DeletableGroupInvitationMessage {
@Nullable @Nullable
private final MessageId previousMessageId; private final MessageId previousMessageId;
JoinMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId, JoinMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId) { long timestamp, @Nullable MessageId previousMessageId,
super(id, contactGroupId, privateGroupId, timestamp); long autoDeleteTimer) {
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
this.previousMessageId = previousMessageId; this.previousMessageId = previousMessageId;
} }

View File

@@ -9,14 +9,15 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class LeaveMessage extends GroupInvitationMessage { class LeaveMessage extends DeletableGroupInvitationMessage {
@Nullable @Nullable
private final MessageId previousMessageId; private final MessageId previousMessageId;
LeaveMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId, LeaveMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId) { long timestamp, @Nullable MessageId previousMessageId,
super(id, contactGroupId, privateGroupId, timestamp); long autoDeleteTimer) {
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
this.previousMessageId = previousMessageId; this.previousMessageId = previousMessageId;
} }

View File

@@ -14,7 +14,7 @@ interface MessageEncoder {
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId, BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
long timestamp, boolean local, boolean read, boolean visible, long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted); boolean available, boolean accepted, long autoDeleteTimer);
void setVisibleInUi(BdfDictionary meta, boolean visible); void setVisibleInUi(BdfDictionary meta, boolean visible);
@@ -22,16 +22,49 @@ interface MessageEncoder {
void setInvitationAccepted(BdfDictionary meta, boolean accepted); void setInvitationAccepted(BdfDictionary meta, boolean accepted);
/**
* Encodes an invite message without an auto-delete timer.
*/
Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId, Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, String groupName, Author creator, byte[] salt, long timestamp, String groupName, Author creator, byte[] salt,
@Nullable String text, byte[] signature); @Nullable String text, byte[] signature);
/**
* Encodes an invite message with an optional auto-delete timer. This
* requires the contact to support client version 0.1 or higher.
*/
Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, String groupName, Author creator, byte[] salt,
@Nullable String text, byte[] signature, long autoDeleteTimer);
/**
* Encodes a join message without an auto-delete timer.
*/
Message encodeJoinMessage(GroupId contactGroupId, GroupId privateGroupId, Message encodeJoinMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId); long timestamp, @Nullable MessageId previousMessageId);
/**
* Encodes a join message with an optional auto-delete timer. This
* requires the contact to support client version 0.1 or higher.
*/
Message encodeJoinMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId,
long autoDeleteTimer);
/**
* Encodes a leave message without an auto-delete timer.
*/
Message encodeLeaveMessage(GroupId contactGroupId, GroupId privateGroupId, Message encodeLeaveMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId); long timestamp, @Nullable MessageId previousMessageId);
/**
* Encodes a leave message with an optional auto-delete timer. This
* requires the contact to support client version 0.1 or higher.
*/
Message encodeLeaveMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId,
long autoDeleteTimer);
Message encodeAbortMessage(GroupId contactGroupId, GroupId privateGroupId, Message encodeAbortMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp); long timestamp);
} }

Some files were not shown because too many files have changed in this diff Show More