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;
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.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
@@ -119,4 +120,17 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
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 javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ValidationUtils {
@@ -64,4 +66,9 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size)
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.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
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.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -39,6 +41,7 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable;
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.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -389,4 +392,27 @@ class ClientHelperImpl implements ClientHelper {
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
String MSG_KEY_UPDATE_VERSION = "version";
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.SHARED;
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_UPDATE_VERSION;
@@ -161,13 +160,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
clientHelper.setContactId(txn, g.getId(), c.getId());
// Create and store the first local update
List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions);
@@ -229,7 +222,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates);
// 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)) {
Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after);
@@ -521,17 +514,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
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(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
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.getMessage;
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_UPDATE_VERSION;
import static org.junit.Assert.assertEquals;
@@ -60,8 +59,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis();
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() {
context.checking(new Expectations() {{
@@ -123,8 +120,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED);
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
groupMeta);
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId());
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
@@ -460,9 +457,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Get contact ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
// No states or visibilities have changed
}});
@@ -492,10 +488,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Get client ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
// No states or visibilities have changed
}});
@@ -546,8 +541,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -577,9 +570,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
@@ -619,8 +611,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -650,9 +640,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);

View File

@@ -141,6 +141,16 @@
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</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
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
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.PasswordStrengthEstimator;
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.identity.IdentityManager;
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.LockManager;
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.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -169,6 +171,10 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager();
TransactionManager transactionManager();
AutoDeleteManager autoDeleteManager();
void inject(SignInReminderReceiver 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.conversation.AliasDialogFragment;
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.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity;
@@ -184,6 +186,8 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity);
void inject(ConversationSettingsActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -234,4 +238,6 @@ public interface ActivityComponent {
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.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.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -273,15 +275,11 @@ public class ConversationActivity extends BriarActivity
ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView,
imagePreview, this, viewModel);
viewModel.hasImageSupport().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean hasSupport) {
if (hasSupport != null && hasSupport) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().removeObserver(this);
}
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
if (format != TEXT_ONLY) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
}
});
} else {
@@ -384,6 +382,12 @@ public class ConversationActivity extends BriarActivity
// enable alias action if available
observeOnce(viewModel.getContact(), this, contact ->
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);
}
@@ -405,6 +409,12 @@ public class ConversationActivity extends BriarActivity
AliasDialogFragment.newInstance().show(
getSupportFragmentManager(), AliasDialogFragment.TAG);
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:
askToDeleteAllMessages();
return true;
@@ -657,8 +667,8 @@ public class ConversationActivity extends BriarActivity
supportFinishAfterTransition();
}
} else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
if (p.getContactId().equals(contactId)) {
LOG.info("Message received, adding");
onNewConversationMessage(p.getMessageHeader());
@@ -756,18 +766,10 @@ public class ConversationActivity extends BriarActivity
List<AttachmentHeader> attachmentHeaders) {
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
throw new AssertionError();
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
viewModel.sendMessage(text, attachmentHeaders, timestamp);
viewModel.sendMessage(text, attachmentHeaders);
textInputView.clearText();
}
private long getMinTimestampForNewMessage() {
// Don't use an earlier timestamp than the newest message
ConversationItem item = adapter.getLastItem();
return item == null ? 0 : item.getTime() + 1;
}
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
if (h == null) return;
addConversationItem(h.accept(visitor));
@@ -972,13 +974,11 @@ public class ConversationActivity extends BriarActivity
adapter.notifyItemChanged(position, item);
}
runOnDbThread(() -> {
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try {
switch (item.getRequestType()) {
case INTRODUCTION:
respondToIntroductionRequest(item.getSessionId(),
accept, timestamp);
accept);
break;
case FORUM:
respondToForumRequest(item.getSessionId(), accept);
@@ -1054,9 +1054,8 @@ public class ConversationActivity extends BriarActivity
@DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, time,
accept);
boolean accept) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, accept);
}
@DatabaseExecutor

View File

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

View File

@@ -12,8 +12,11 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintLayout;
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.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread
@NotNullByDefault
@@ -26,6 +29,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
private final OutItemViewHolder outViewHolder;
private final TextView text;
protected final TextView time;
private final View bomb;
@Nullable
private String itemKey = null;
@@ -38,6 +42,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
layout = v.findViewById(R.id.layout);
text = v.findViewById(R.id.text);
time = v.findViewById(R.id.time);
bomb = v.findViewById(R.id.bomb);
}
@CallSuper
@@ -52,6 +57,9 @@ abstract class ConversationItemViewHolder extends ViewHolder {
long timestamp = item.getTime();
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);
}

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.DbException;
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.event.Event;
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.viewmodel.LiveEvent;
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.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
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.event.AttachmentReceivedEvent;
@@ -50,6 +54,7 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
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.Logger.getLogger;
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.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
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
public class ConversationViewModel extends AndroidViewModel
implements EventListener, AttachmentManager {
private static Logger LOG =
private static final Logger LOG =
getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
@@ -80,6 +88,8 @@ public class ConversationViewModel extends AndroidViewModel
private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever;
private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
@Nullable
private ContactId contactId = null;
@@ -89,7 +99,7 @@ public class ConversationViewModel extends AndroidViewModel
private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<Boolean> imageSupport =
private final MutableLiveData<PrivateMessageFormat> privateMessageFormat =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> showImageOnboarding =
new MutableLiveEvent<>();
@@ -112,7 +122,9 @@ public class ConversationViewModel extends AndroidViewModel
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator) {
AttachmentCreator attachmentCreator,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager) {
super(application);
this.dbExecutor = dbExecutor;
this.db = db;
@@ -123,6 +135,8 @@ public class ConversationViewModel extends AndroidViewModel
this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
messagingGroupId = Transformations
.map(contact, c -> messagingManager.getContactGroup(c).getId());
contactDeleted.setValue(false);
@@ -205,16 +219,13 @@ public class ConversationViewModel extends AndroidViewModel
}
@UiThread
void sendMessage(@Nullable String text,
List<AttachmentHeader> headers, long timestamp) {
void sendMessage(@Nullable String text, List<AttachmentHeader> headers) {
// messagingGroupId is loaded with the contact
observeForeverOnce(messagingGroupId, groupId -> {
requireNonNull(groupId);
observeForeverOnce(imageSupport, hasImageSupport -> {
requireNonNull(hasImageSupport);
createMessage(groupId, text, headers, timestamp,
hasImageSupport);
});
observeForeverOnce(privateMessageFormat, format ->
storeMessage(requireNonNull(contactId), groupId, text,
headers, format));
});
}
@@ -244,10 +255,10 @@ public class ConversationViewModel extends AndroidViewModel
@DatabaseExecutor
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images are supported
boolean imagesSupported = db.transactionWithResult(true, txn ->
messagingManager.contactSupportsImages(txn, c));
imageSupport.postValue(imagesSupported);
// check if images and auto-deletion are supported
PrivateMessageFormat format = db.transactionWithResult(true, txn ->
messagingManager.getContactMessageFormat(txn, c));
privateMessageFormat.postValue(format);
// check if introductions are supported
Collection<Contact> contacts = contactManager.getContacts();
@@ -256,7 +267,7 @@ public class ConversationViewModel extends AndroidViewModel
// we only show one onboarding dialog at a time
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (imagesSupported &&
if (format != TEXT_ONLY &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
onOnboardingShown(SHOW_ONBOARDING_IMAGE);
showImageOnboarding.postEvent(true);
@@ -274,40 +285,67 @@ public class ConversationViewModel extends AndroidViewModel
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
}
@UiThread
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long timestamp,
boolean hasImageSupport) {
private PrivateMessage createMessage(Transaction txn, ContactId c,
GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, PrivateMessageFormat format)
throws DbException {
long timestamp =
conversationManager.getTimestampForOutgoingMessage(txn, c);
try {
PrivateMessage pm;
if (hasImageSupport) {
pm = privateMessageFactory.createPrivateMessage(groupId,
if (format == TEXT_ONLY) {
return privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
} else if (format == TEXT_IMAGES) {
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers);
} else {
pm = privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
timestamp);
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers, timer);
}
storeMessage(pm);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
@UiThread
private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
private void storeMessage(ContactId c, GroupId groupId,
@Nullable String text, List<AttachmentHeader> headers,
PrivateMessageFormat format) {
dbExecutor.execute(() -> {
try {
long start = now();
messagingManager.addLocalMessage(m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
m.hasText(), m.getAttachmentHeaders());
// TODO add text to cache when available here
addedHeader.postEvent(h);
db.transaction(false, txn -> {
long start = now();
PrivateMessage m = createMessage(txn, c, groupId, text,
headers, format);
messagingManager.addLocalMessage(txn, m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
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) {
logException(LOG, WARNING, e);
}
@@ -330,8 +368,8 @@ public class ConversationViewModel extends AndroidViewModel
return contactName;
}
LiveData<Boolean> hasImageSupport() {
return imageSupport;
LiveData<PrivateMessageFormat> getPrivateMessageFormat() {
return privateMessageFormat;
}
LiveEvent<Boolean> showImageOnboarding() {

View File

@@ -211,8 +211,7 @@ public class IntroductionMessageFragment extends BaseFragment
introductionActivity.runOnDbThread(() -> {
// actually make the introduction
try {
long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, text, timestamp);
introductionManager.makeIntroduction(c1, c2, text);
} catch (DbException e) {
logException(LOG, WARNING, e);
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.DbException;
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.LocalAuthor;
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.briar.android.contactselection.ContactSelectorControllerImpl;
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.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup;
@@ -35,6 +39,8 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
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;
@Immutable
@@ -43,9 +49,12 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
implements CreateGroupController {
private static final Logger LOG =
Logger.getLogger(CreateGroupControllerImpl.class.getName());
getLogger(CreateGroupControllerImpl.class.getName());
private final Executor cryptoExecutor;
private final TransactionManager db;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final PrivateGroupFactory groupFactory;
@@ -56,16 +65,26 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private final Clock clock;
@Inject
CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
CreateGroupControllerImpl(
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
IdentityManager identityManager, PrivateGroupFactory groupFactory,
TransactionManager db,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
LifecycleManager lifecycleManager,
ContactManager contactManager,
IdentityManager identityManager,
PrivateGroupFactory groupFactory,
GroupMessageFactory groupMessageFactory,
PrivateGroupManager groupManager,
GroupInvitationFactory groupInvitationFactory,
GroupInvitationManager groupInvitationManager, Clock clock) {
GroupInvitationManager groupInvitationManager,
Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
this.cryptoExecutor = cryptoExecutor;
this.db = db;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
this.contactManager = contactManager;
this.identityManager = identityManager;
this.groupFactory = groupFactory;
@@ -129,16 +148,14 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> {
try {
LocalAuthor localAuthor = identityManager.getLocalAuthor();
List<Contact> contacts = new ArrayList<>();
for (ContactId c : contactIds) {
try {
contacts.add(contactManager.getContact(c));
} catch (NoSuchContactException e) {
// Continue
}
}
signInvitations(g, localAuthor, contacts, text, handler);
db.transaction(true, txn -> {
LocalAuthor localAuthor =
identityManager.getLocalAuthor(txn);
List<InvitationContext> contexts =
createInvitationContexts(txn, contactIds);
txn.attach(() -> signInvitations(g, localAuthor, contexts,
text, handler));
});
} catch (DbException e) {
logException(LOG, WARNING, 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,
Collection<Contact> contacts, @Nullable String text,
List<InvitationContext> contexts, @Nullable String text,
ResultExceptionHandler<Void, DbException> handler) {
cryptoExecutor.execute(() -> {
long timestamp = clock.currentTimeMillis();
List<InvitationContext> contexts = new ArrayList<>();
for (Contact c : contacts) {
byte[] signature = groupInvitationFactory.signInvitation(c, g,
timestamp, localAuthor.getPrivateKey());
contexts.add(new InvitationContext(c.getId(), timestamp,
signature));
for (InvitationContext ctx : contexts) {
ctx.signature = groupInvitationFactory.signInvitation(
ctx.contact, g, ctx.timestamp,
localAuthor.getPrivateKey());
}
sendInvitations(g, contexts, text, handler);
});
@@ -167,16 +199,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> {
try {
for (InvitationContext context : contexts) {
for (InvitationContext ctx : contexts) {
try {
groupInvitationManager.sendInvitation(g,
context.contactId, text, context.timestamp,
context.signature);
ctx.contact.getId(), text, ctx.timestamp,
requireNonNull(ctx.signature),
ctx.autoDeleteTimer);
} catch (NoSuchContactException e) {
// Continue
}
}
//noinspection ConstantConditions
handler.onResult(null);
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -187,15 +219,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private static class InvitationContext {
private final ContactId contactId;
private final long timestamp;
private final byte[] signature;
private final Contact contact;
private final long timestamp, autoDeleteTimer;
@Nullable
private byte[] signature = null;
private InvitationContext(ContactId contactId, long timestamp,
byte[] signature) {
this.contactId = contactId;
private InvitationContext(Contact contact, long timestamp,
long autoDeleteTimer) {
this.contact = contact;
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.nullsafety.NotNullByDefault;
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.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -25,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -33,21 +32,16 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
implements ShareBlogController {
private final static Logger LOG =
Logger.getLogger(ShareBlogControllerImpl.class.getName());
getLogger(ShareBlogControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final BlogSharingManager blogSharingManager;
private final Clock clock;
@Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) {
BlogSharingManager blogSharingManager) {
super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager;
this.clock = clock;
}
@Override
@@ -62,10 +56,7 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
try {
for (ContactId c : contacts) {
try {
long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
blogSharingManager.sendInvitation(g, c, text, time);
blogSharingManager.sendInvitation(g, c, text);
} catch (NoSuchContactException | NoSuchGroupException 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.nullsafety.NotNullByDefault;
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.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import java.util.Collection;
@@ -25,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -33,21 +32,16 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
implements ShareForumController {
private final static Logger LOG =
Logger.getLogger(ShareForumControllerImpl.class.getName());
getLogger(ShareForumControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final ForumSharingManager forumSharingManager;
private final Clock clock;
@Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) {
ForumSharingManager forumSharingManager) {
super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager;
this.clock = clock;
}
@Override
@@ -62,10 +56,7 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
try {
for (ContactId c : contacts) {
try {
long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
forumSharingManager.sendInvitation(g, c, text, time);
forumSharingManager.sendInvitation(g, c, text);
} catch (NoSuchContactException | NoSuchGroupException 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"
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>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -67,6 +67,16 @@
android:layout_height="wrap_content"
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>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -68,6 +68,17 @@
android:layout_height="wrap_content"
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>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -73,11 +73,20 @@
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:textColor="@color/private_message_date_inverse"
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
android:id="@+id/status"
android:layout_width="wrap_content"

View File

@@ -48,10 +48,28 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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"
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>
</LinearLayout>
</LinearLayout>

View File

@@ -55,14 +55,26 @@
app:layout_constraintTop_toBottomOf="@+id/text"
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
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/bomb"
app:layout_constraintTop_toTopOf="@+id/time"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered" />

View File

@@ -68,10 +68,27 @@
style="@style/TextMessage.Timestamp"
android:layout_width="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"
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>
</LinearLayout>
</LinearLayout>

View File

@@ -1,30 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_introduction"
android:enabled="false"
android:icon="@drawable/introduction_white"
android:title="@string/introduction_menu_item"
android:enabled="false"
app:showAsAction="never"/>
app:showAsAction="never" />
<item
android:id="@+id/action_set_alias"
android:title="@string/set_contact_alias"
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
android:id="@+id/action_delete_all_messages"
android:title="@string/delete_all_messages"
app:showAsAction="never"/>
app:showAsAction="never" />
<item
android:id="@+id/action_social_remove_person"
android:icon="@drawable/action_delete_white"
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_hint">Contact name</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="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>
@@ -545,6 +546,20 @@
<string name="choose_ringtone_title">Choose 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 -->
<string name="feedback_settings_title">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,
boolean local, boolean read, boolean sent, boolean seen,
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,
text, available, canBeOpened);
text, available, canBeOpened, autoDeleteTimer);
}
@Override

View File

@@ -12,9 +12,10 @@ public class BlogInvitationResponse extends InvitationResponse {
public BlogInvitationResponse(MessageId id, GroupId groupId, long time,
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,
accept, shareableId);
accept, shareableId, autoDeleteTimer);
}
@Override

View File

@@ -18,5 +18,5 @@ public interface BlogSharingManager extends SharingManager<Blog> {
/**
* 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;
/**
* 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.
*/

View File

@@ -12,18 +12,20 @@ public abstract class ConversationMessageHeader {
private final MessageId id;
private final GroupId groupId;
private final long timestamp;
private final boolean local, sent, seen, read;
private final long timestamp, autoDeleteTimer;
private final boolean local, read, sent, seen;
public ConversationMessageHeader(MessageId id, GroupId groupId, long timestamp,
boolean local, boolean read, boolean sent, boolean seen) {
public ConversationMessageHeader(MessageId id, GroupId groupId,
long timestamp, boolean local, boolean read, boolean sent,
boolean seen, long autoDeleteTimer) {
this.id = id;
this.groupId = groupId;
this.timestamp = timestamp;
this.local = local;
this.read = read;
this.sent = sent;
this.seen = seen;
this.read = read;
this.autoDeleteTimer = autoDeleteTimer;
}
public MessageId getId() {
@@ -55,4 +57,8 @@ public abstract class ConversationMessageHeader {
}
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 boolean answered;
public ConversationRequest(MessageId messageId, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, N nameable, @Nullable String text,
boolean answered) {
super(messageId, groupId, time, local, read, sent, seen);
public ConversationRequest(MessageId messageId, GroupId groupId,
long timestamp, boolean local, boolean read, boolean sent,
boolean seen, SessionId sessionId, N nameable,
@Nullable String text, boolean answered, long autoDeleteTimer) {
super(messageId, groupId, timestamp, local, read, sent, seen,
autoDeleteTimer);
this.sessionId = sessionId;
this.nameable = nameable;
this.text = text;

View File

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

View File

@@ -17,9 +17,9 @@ public class ForumInvitationRequest extends InvitationRequest<Forum> {
public ForumInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen,
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,
text, available, canBeOpened);
text, available, canBeOpened, autoDeleteTimer);
}
@Override

View File

@@ -15,9 +15,10 @@ public class ForumInvitationResponse extends InvitationResponse {
public ForumInvitationResponse(MessageId id, GroupId groupId, long time,
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,
accept, shareableId);
accept, shareableId, autoDeleteTimer);
}
@Override

View File

@@ -18,5 +18,5 @@ public interface ForumSharingManager extends SharingManager<Forum> {
/**
* 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.
*/
int MINOR_VERSION = 0;
int MINOR_VERSION = 1;
/**
* Sends two initial introduction messages.
*/
void makeIntroduction(Contact c1, Contact c2, @Nullable String text,
long timestamp) throws DbException;
void makeIntroduction(Contact c1, Contact c2, @Nullable String text)
throws DbException;
/**
* Responds to an introduction.
*/
void respondToIntroduction(ContactId contactId, SessionId sessionId,
long timestamp, boolean accept) throws DbException;
boolean accept) throws DbException;
}

View File

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

View File

@@ -25,9 +25,10 @@ public class IntroductionResponse extends ConversationResponse {
public IntroductionResponse(MessageId messageId, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen,
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,
accepted);
accepted, autoDeleteTimer);
this.introducedAuthor = author;
this.introducedAuthorInfo = introducedAuthorInfo;
this.ourRole = role;

View File

@@ -30,13 +30,18 @@ public interface MessagingManager extends ConversationClient {
/**
* The current minor version of the messaging client.
*/
int MINOR_VERSION = 2;
int MINOR_VERSION = 3;
/**
* Stores a local private message.
*/
void addLocalMessage(PrivateMessage m) throws DbException;
/**
* Stores a local private message.
*/
void addLocalMessage(Transaction txn, PrivateMessage m) throws DbException;
/**
* Stores a local attachment message.
*
@@ -77,12 +82,8 @@ public interface MessagingManager extends ConversationClient {
Attachment getAttachment(AttachmentHeader h) throws DbException;
/**
* Returns true if the contact with the given {@link ContactId} does support
* image attachments.
*
* Added: 2019-01-01
* Returns the private message format supported by the given contact.
*/
boolean contactSupportsImages(Transaction txn, ContactId c)
PrivateMessageFormat getContactMessageFormat(Transaction txn, ContactId c)
throws DbException;
}

View File

@@ -8,44 +8,66 @@ import java.util.List;
import javax.annotation.concurrent.Immutable;
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
@NotNullByDefault
public class PrivateMessage {
private final Message message;
private final boolean legacyFormat, hasText;
private final boolean hasText;
private final List<AttachmentHeader> attachmentHeaders;
private final long autoDeleteTimer;
private final PrivateMessageFormat format;
/**
* Constructor for private messages in the legacy format, which does not
* support attachments.
* Constructor for private messages in the
* {@link PrivateMessageFormat#TEXT_ONLY TEXT_ONLY} format.
*/
public PrivateMessage(Message message) {
this.message = message;
legacyFormat = true;
hasText = true;
attachmentHeaders = emptyList();
autoDeleteTimer = NO_AUTO_DELETE_TIMER;
format = TEXT_ONLY;
}
/**
* Constructor for private messages in the current format, which supports
* attachments.
* Constructor for private messages in the
* {@link PrivateMessageFormat#TEXT_IMAGES TEXT_IMAGES} format.
*/
public PrivateMessage(Message message, boolean hasText,
List<AttachmentHeader> headers) {
this.message = message;
this.hasText = hasText;
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() {
return message;
}
public boolean isLegacyFormat() {
return legacyFormat;
public PrivateMessageFormat getFormat() {
return format;
}
public boolean hasText() {
@@ -55,4 +77,8 @@ public class PrivateMessage {
public List<AttachmentHeader> getAttachmentHeaders() {
return attachmentHeaders;
}
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
}

View File

@@ -11,11 +11,29 @@ import javax.annotation.Nullable;
@NotNullByDefault
public interface PrivateMessageFactory {
/**
* Creates a private message in the
* {@link PrivateMessageFormat#TEXT_ONLY TEXT_ONLY} format.
*/
PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp,
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,
@Nullable String text, List<AttachmentHeader> headers)
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,
boolean local, boolean read, boolean sent, boolean seen,
boolean hasText, List<AttachmentHeader> headers) {
super(id, groupId, timestamp, local, read, sent, seen);
boolean hasText, List<AttachmentHeader> headers,
long autoDeleteTimer) {
super(id, groupId, timestamp, local, read, sent, seen, autoDeleteTimer);
this.hasText = hasText;
this.attachmentHeaders = headers;
}
@@ -37,5 +38,4 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
public <T> T accept(ConversationMessageVisitor<T> v) {
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.
*/
int MINOR_VERSION = 0;
int MINOR_VERSION = 1;
/**
* Sends an invitation to share the given private group with the given
@@ -43,7 +43,8 @@ public interface GroupInvitationManager extends ConversationClient {
* pending.
*/
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.

View File

@@ -18,9 +18,10 @@ public class GroupInvitationRequest extends InvitationRequest<PrivateGroup> {
public GroupInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen,
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,
text, available, canBeOpened);
text, available, canBeOpened, autoDeleteTimer);
}
@Override

View File

@@ -15,9 +15,10 @@ public class GroupInvitationResponse extends InvitationResponse {
public GroupInvitationResponse(MessageId id, GroupId groupId, long time,
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,
accept, shareableId);
accept, shareableId, autoDeleteTimer);
}
@Override

View File

@@ -15,9 +15,9 @@ public abstract class InvitationRequest<S extends Shareable> extends
public InvitationRequest(MessageId messageId, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen,
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,
object, text, !available);
object, text, !available, autoDeleteTimer);
this.canBeOpened = canBeOpened;
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar;
import org.briarproject.briar.autodelete.AutoDeleteModule;
import org.briarproject.briar.blog.BlogModule;
import org.briarproject.briar.client.BriarClientModule;
import org.briarproject.briar.feed.DnsModule;
@@ -15,6 +16,7 @@ import org.briarproject.briar.test.TestModule;
import dagger.Module;
@Module(includes = {
AutoDeleteModule.class,
BlogModule.class,
BriarClientModule.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.concurrent.Immutable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@Immutable
@NotNullByDefault
class AbortMessage extends AbstractIntroductionMessage {
@@ -16,7 +18,8 @@ class AbortMessage extends AbstractIntroductionMessage {
protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId) {
super(messageId, groupId, timestamp, previousMessageId);
super(messageId, groupId, timestamp, previousMessageId,
NO_AUTO_DELETE_TIMER);
this.sessionId = sessionId;
}

View File

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

View File

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

View File

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

View File

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

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.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
@@ -22,6 +25,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.lang.Math.max;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
@@ -50,19 +54,23 @@ class IntroducerProtocolEngine
IdentityManager identityManager,
MessageParser messageParser,
MessageEncoder messageEncoder,
ClientVersioningManager clientVersioningManager,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) {
super(db, clientHelper, contactManager, contactGroupFactory,
messageTracker, identityManager, messageParser, messageEncoder,
clock);
clientVersioningManager, autoDeleteManager,
conversationManager, clock);
}
@Override
public IntroducerSession onRequestAction(Transaction txn,
IntroducerSession s, @Nullable String text, long timestamp)
IntroducerSession s, @Nullable String text)
throws DbException {
switch (s.getState()) {
case START:
return onLocalRequest(txn, s, text, timestamp);
return onLocalRequest(txn, s, text);
case AWAIT_RESPONSES:
case AWAIT_RESPONSE_A:
case AWAIT_RESPONSE_B:
@@ -82,37 +90,24 @@ class IntroducerProtocolEngine
@Override
public IntroducerSession onAcceptAction(Transaction txn,
IntroducerSession s, long timestamp) {
IntroducerSession s) {
throw new UnsupportedOperationException(); // Invalid in this role
}
@Override
public IntroducerSession onDeclineAction(Transaction txn,
IntroducerSession s, long timestamp) {
IntroducerSession s) {
throw new UnsupportedOperationException(); // Invalid in this role
}
IntroducerSession onIntroduceeRemoved(Transaction txn,
Introducee remainingIntroducee, IntroducerSession session)
throws DbException {
// abort session
IntroducerSession s = abort(txn, session);
// reset information for introducee that was removed
Introducee introduceeA, introduceeB;
if (remainingIntroducee.author.equals(s.getIntroduceeA().author)) {
introduceeA = s.getIntroduceeA();
introduceeB =
new Introducee(s.getSessionId(), s.getIntroduceeB().groupId,
s.getIntroduceeB().author);
} else if (remainingIntroducee.author
.equals(s.getIntroduceeB().author)) {
introduceeA =
new Introducee(s.getSessionId(), s.getIntroduceeA().groupId,
s.getIntroduceeA().author);
introduceeB = s.getIntroduceeB();
} else throw new DbException();
// abort session with remaining introducee
IntroducerSession s = abort(txn, session, remainingIntroducee);
return new IntroducerSession(s.getSessionId(), s.getState(),
s.getRequestTimestamp(), introduceeA, introduceeB);
s.getRequestTimestamp(), s.getIntroduceeA(),
s.getIntroduceeB());
}
@Override
@@ -222,13 +217,13 @@ class IntroducerProtocolEngine
}
private IntroducerSession onLocalRequest(Transaction txn,
IntroducerSession s, @Nullable String text, long timestamp)
throws DbException {
IntroducerSession s, @Nullable String text) throws DbException {
// Send REQUEST messages
long maxIntroduceeTimestamp =
Math.max(getLocalTimestamp(s, s.getIntroduceeA()),
getLocalTimestamp(s, s.getIntroduceeB()));
long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp);
long timestampA =
getTimestampForVisibleMessage(txn, s, s.getIntroduceeA());
long timestampB =
getTimestampForVisibleMessage(txn, s, s.getIntroduceeB());
long localTimestamp = max(timestampA, timestampB);
Message sentA = sendRequestMessage(txn, s.getIntroduceeA(),
localTimestamp, s.getIntroduceeB().author, text);
Message sentB = sendRequestMessage(txn, s.getIntroduceeB(),
@@ -265,14 +260,16 @@ class IntroducerProtocolEngine
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward ACCEPT message
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent =
sendAcceptMessage(txn, i, timestamp, m.getEphemeralPublicKey(),
m.getAcceptTimestamp(), m.getTransportProperties(),
false);
// 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.getTransportProperties(), false);
// Create the next state
IntroducerState state = AWAIT_AUTHS;
@@ -326,10 +323,14 @@ class IntroducerProtocolEngine
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward ACCEPT message
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.getTransportProperties(), false);
@@ -377,11 +378,14 @@ class IntroducerProtocolEngine
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward DECLINE message
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent = sendDeclineMessage(txn, i, timestamp, false);
// The forwarded message will be visible to the introducee
long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
// Create the next state
IntroducerState state = START;
@@ -429,11 +433,14 @@ class IntroducerProtocolEngine
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Forward DECLINE message
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent = sendDeclineMessage(txn, i, timestamp, false);
// The forwarded message will be visible to the introducee
long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
Introducee introduceeA, introduceeB;
Author sender, other;
@@ -473,8 +480,8 @@ class IntroducerProtocolEngine
// Forward AUTH message
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent = sendAuthMessage(txn, i, timestamp, m.getMac(),
long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendAuthMessage(txn, i, localTimestamp, m.getMac(),
m.getSignature());
// Move to the next state
@@ -509,8 +516,8 @@ class IntroducerProtocolEngine
// Forward ACTIVATE message
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent = sendActivateMessage(txn, i, timestamp, m.getMac());
long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendActivateMessage(txn, i, localTimestamp, m.getMac());
// Move to the next state
IntroducerState state = START;
@@ -532,8 +539,8 @@ class IntroducerProtocolEngine
IntroducerSession s, AbortMessage m) throws DbException {
// Forward ABORT message
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent = sendAbortMessage(txn, i, timestamp);
long localTimestamp = getTimestampForInvisibleMessage(s, i);
Message sent = sendAbortMessage(txn, i, localTimestamp);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
@@ -551,15 +558,45 @@ class IntroducerProtocolEngine
s.getRequestTimestamp(), introduceeA, introduceeB);
}
private IntroducerSession abort(Transaction txn,
IntroducerSession s) throws DbException {
private IntroducerSession abort(Transaction txn, IntroducerSession s,
Introducee remainingIntroducee) throws DbException {
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Send an ABORT message to the remaining introducee
long localTimestamp =
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
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// 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);
long timestampB = getLocalTimestamp(s, s.getIntroduceeB());
long timestampB =
getTimestampForInvisibleMessage(s, s.getIntroduceeB());
Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB);
// Reset the session back to initial state
Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA);
@@ -589,9 +626,33 @@ class IntroducerProtocolEngine
return isInvalidDependency(expected, dependency);
}
private long getLocalTimestamp(IntroducerSession s, PeerSession p) {
return getLocalTimestamp(p.getLocalTimestamp(),
s.getRequestTimestamp());
/**
* Returns a timestamp for a visible outgoing message. The timestamp is
* 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 {
// Group metadata keys
String GROUP_KEY_CONTACT_ID = "contactId";
// Message metadata keys
String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_SESSION_ID = "sessionId";
@@ -12,6 +9,7 @@ interface IntroductionConstants {
String MSG_KEY_LOCAL = "local";
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
// Session Keys
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.B_DECLINED;
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.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
@@ -136,13 +135,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
clientHelper.setContactId(txn, g.getId(), c.getId());
}
@Override
@@ -183,7 +176,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
}
StoredSession ss = getSession(txn, sessionId);
// Handle the message
Session session;
Session<?> session;
MessageId storageId;
if (ss == null) {
if (meta.getMessageType() != REQUEST) throw new FormatException();
@@ -211,7 +204,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
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 local = identityManager.getLocalAuthor(txn);
Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
@@ -223,7 +216,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
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)
throws DbException, FormatException {
if (type == REQUEST) {
@@ -263,13 +256,6 @@ class IntroductionManagerImpl extends ConversationClientImpl
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 {
Message m = clientHelper
.createMessageForStoringMetadata(localGroup.getId());
@@ -278,7 +264,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
}
private void storeSession(Transaction txn, MessageId storageId,
Session session) throws DbException {
Session<?> session) throws DbException {
BdfDictionary d;
if (session.getRole() == INTRODUCER) {
d = sessionEncoder
@@ -325,8 +311,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
}
@Override
public void makeIntroduction(Contact c1, Contact c2, @Nullable String text,
long timestamp) throws DbException {
public void makeIntroduction(Contact c1, Contact c2, @Nullable String text)
throws DbException {
Transaction txn = db.startTransaction(false);
try {
// Look up the session, if there is one
@@ -358,8 +344,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
storageId = ss.storageId;
}
// Handle the request action
session = introducerEngine
.onRequestAction(txn, session, text, timestamp);
session = introducerEngine.onRequestAction(txn, session, text);
// Store the updated session
storeSession(txn, storageId, session);
db.commitTransaction(txn);
@@ -372,7 +357,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
@Override
public void respondToIntroduction(ContactId contactId, SessionId sessionId,
long timestamp, boolean accept) throws DbException {
boolean accept) throws DbException {
Transaction txn = db.startTransaction(false);
try {
// Look up the session
@@ -390,11 +375,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
// Handle the join or leave action
if (accept) {
session = introduceeEngine
.onAcceptAction(txn, session, timestamp);
session = introduceeEngine.onAcceptAction(txn, session);
} else {
session = introduceeEngine
.onDeclineAction(txn, session, timestamp);
session = introduceeEngine.onDeclineAction(txn, session);
}
// Store the updated session
storeSession(txn, ss.storageId, session);
@@ -461,7 +444,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(),
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
sessionId, author, text, !meta.isAvailableToAnswer(),
authorInfo);
authorInfo, rm.getAutoDeleteTimer());
}
private IntroductionResponse parseInvitationResponse(Transaction txn,
@@ -499,7 +482,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
}
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
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,
@@ -665,7 +649,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
try {
StoredSession ss = getSession(txn, sessionId);
if (ss == null) throw new AssertionError();
Session s;
Session<?> s;
Role role = sessionParser.getRole(ss.bdfSession);
if (role == INTRODUCER) {
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.util.ValidationUtils.checkLength;
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.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
@Immutable
@@ -52,13 +54,14 @@ class IntroductionValidator extends BdfMessageValidator {
return validateRequestMessage(m, body);
case ACCEPT:
return validateAcceptMessage(m, body);
case DECLINE:
return validateDeclineMessage(type, m, body);
case AUTH:
return validateAuthMessage(m, body);
case ACTIVATE:
return validateActivateMessage(m, body);
case DECLINE:
case ABORT:
return validateOtherMessage(type, m, body);
return validateAbortMessage(type, m, body);
default:
throw new FormatException();
}
@@ -66,7 +69,11 @@ class IntroductionValidator extends BdfMessageValidator {
private BdfMessageContext validateRequestMessage(Message m, BdfList body)
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);
checkLength(previousMessageId, UniqueId.LENGTH);
@@ -77,8 +84,13 @@ class IntroductionValidator extends BdfMessageValidator {
String text = body.getOptionalString(3);
checkLength(text, 1, MAX_INTRODUCTION_TEXT_LENGTH);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 5) {
timer = validateAutoDeleteTimer(body.getOptionalLong(4));
}
BdfDictionary meta =
messageEncoder.encodeRequestMetadata(m.getTimestamp());
messageEncoder.encodeRequestMetadata(m.getTimestamp(), timer);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
@@ -89,7 +101,12 @@ class IntroductionValidator extends BdfMessageValidator {
private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
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);
checkLength(sessionIdBytes, UniqueId.LENGTH);
@@ -109,9 +126,44 @@ class IntroductionValidator extends BdfMessageValidator {
clientHelper
.parseAndValidateTransportPropertiesMap(transportProperties);
long timer = NO_AUTO_DELETE_TIMER;
if (body.size() == 7) {
timer = validateAutoDeleteTimer(body.getOptionalLong(6));
}
SessionId sessionId = new SessionId(sessionIdBytes);
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) {
return new BdfMessageContext(meta);
} else {
@@ -138,7 +190,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes);
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);
return new BdfMessageContext(meta, singletonList(dependency));
}
@@ -158,7 +210,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId,
m.getTimestamp(), false, false, false);
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} 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 {
checkSize(body, 3);
@@ -179,7 +231,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
m.getTimestamp(), false, false, false);
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
@@ -187,5 +239,4 @@ class IntroductionValidator extends BdfMessageValidator {
return new BdfMessageContext(meta, singletonList(dependency));
}
}
}

View File

@@ -18,11 +18,12 @@ import javax.annotation.Nullable;
@NotNullByDefault
interface MessageEncoder {
BdfDictionary encodeRequestMetadata(long timestamp);
BdfDictionary encodeRequestMetadata(long timestamp,
long autoDeleteTimer);
BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp, boolean local,
boolean read, boolean visible);
boolean read, boolean visible, long autoDeleteTimer);
void addSessionId(BdfDictionary meta, SessionId sessionId);
@@ -30,18 +31,53 @@ interface MessageEncoder {
void setAvailableToAnswer(BdfDictionary meta, boolean available);
/**
* Encodes a request message without an auto-delete timer.
*/
Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, Author author,
@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,
@Nullable MessageId previousMessageId, SessionId sessionId,
PublicKey ephemeralPublicKey, long acceptTimestamp,
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,
@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,
@Nullable MessageId previousMessageId, SessionId sessionId,
byte[] mac, byte[] signature);

View File

@@ -20,7 +20,9 @@ import java.util.Map;
import javax.annotation.Nullable;
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.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_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
@@ -48,9 +50,10 @@ class MessageEncoderImpl implements MessageEncoder {
}
@Override
public BdfDictionary encodeRequestMetadata(long timestamp) {
BdfDictionary meta =
encodeMetadata(REQUEST, null, timestamp, false, false, false);
public BdfDictionary encodeRequestMetadata(long timestamp,
long autoDeleteTimer) {
BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp,
false, false, false, autoDeleteTimer);
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
return meta;
}
@@ -58,7 +61,7 @@ class MessageEncoderImpl implements MessageEncoder {
@Override
public BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp, boolean local,
boolean read, boolean visible) {
boolean read, boolean visible, long autoDeleteTimer) {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
if (sessionId != null)
@@ -69,6 +72,9 @@ class MessageEncoderImpl implements MessageEncoder {
meta.put(MSG_KEY_LOCAL, local);
meta.put(MSG_KEY_READ, read);
meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
if (autoDeleteTimer != NO_AUTO_DELETE_TIMER) {
meta.put(MSG_KEY_AUTO_DELETE_TIMER, autoDeleteTimer);
}
return meta;
}
@@ -103,6 +109,23 @@ class MessageEncoderImpl implements MessageEncoder {
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
public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId,
@@ -119,11 +142,46 @@ class MessageEncoderImpl implements MessageEncoder {
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
public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, SessionId sessionId) {
return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp,
previousMessageId);
BdfList body = BdfList.of(
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
@@ -156,15 +214,8 @@ class MessageEncoderImpl implements MessageEncoder {
@Override
public Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
@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(
type.getValue(),
ABORT.getValue(),
sessionId,
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;
@Nullable
private final SessionId sessionId;
private final long timestamp;
private final long timestamp, autoDeleteTimer;
private final boolean local, read, visible, available;
MessageMetadata(MessageType type, @Nullable SessionId sessionId,
long timestamp, boolean local, boolean read, boolean visible,
boolean available) {
boolean available, long autoDeleteTimer) {
this.type = type;
this.sessionId = sessionId;
this.timestamp = timestamp;
@@ -26,6 +26,7 @@ class MessageMetadata {
this.read = read;
this.visible = visible;
this.available = available;
this.autoDeleteTimer = autoDeleteTimer;
}
MessageType getMessageType() {
@@ -57,4 +58,7 @@ class MessageMetadata {
return available;
}
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
}

View File

@@ -19,7 +19,9 @@ import java.util.Map;
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.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_LOCAL;
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 visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
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,
visible, available);
visible, available, timer);
}
@Override
@@ -77,8 +80,10 @@ class MessageParserImpl implements MessageParser {
new MessageId(previousMsgBytes));
Author author = clientHelper.parseAndValidateAuthor(body.getList(2));
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(),
m.getTimestamp(), previousMessageId, author, text);
m.getTimestamp(), previousMessageId, author, text, timer);
}
@Override
@@ -92,9 +97,11 @@ class MessageParserImpl implements MessageParser {
long acceptTimestamp = body.getLong(4);
Map<TransportId, TransportProperties> transportProperties = clientHelper
.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(),
previousMessageId, sessionId, ephemeralPublicKey,
acceptTimestamp, transportProperties);
acceptTimestamp, transportProperties, timer);
}
@Override
@@ -104,8 +111,10 @@ class MessageParserImpl implements MessageParser {
byte[] previousMsgBytes = body.getOptionalRaw(2);
MessageId previousMessageId = (previousMsgBytes == null ? null :
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(),
previousMessageId, sessionId);
previousMessageId, sessionId, timer);
}
@Override

View File

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

View File

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

View File

@@ -2,9 +2,6 @@ package org.briarproject.briar.messaging;
interface MessagingConstants {
// Metadata keys for groups
String GROUP_KEY_CONTACT_ID = "contactId";
// Metadata keys for messages
String MSG_KEY_TIMESTAMP = "timestamp";
String MSG_KEY_LOCAL = "local";
@@ -13,4 +10,5 @@ interface MessagingConstants {
String MSG_KEY_DESCRIPTOR_LENGTH = "descriptorLength";
String MSG_KEY_HAS_TEXT = "hasText";
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.versioning.ClientVersioningManager;
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.GroupCount;
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.MessagingManager;
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.event.AttachmentReceivedEvent;
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.validation.MessageState.DELIVERED;
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.messaging.MessageTypes.ATTACHMENT;
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_AUTO_DELETE_TIMER;
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_HAS_TEXT;
@@ -84,18 +90,24 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
private final MessageTracker messageTracker;
private final ClientVersioningManager clientVersioningManager;
private final ContactGroupFactory contactGroupFactory;
private final AutoDeleteManager autoDeleteManager;
@Inject
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
MessagingManagerImpl(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory) {
MetadataParser metadataParser,
MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory,
AutoDeleteManager autoDeleteManager) {
this.db = db;
this.clientHelper = clientHelper;
this.metadataParser = metadataParser;
this.messageTracker = messageTracker;
this.clientVersioningManager = clientVersioningManager;
this.contactGroupFactory = contactGroupFactory;
this.autoDeleteManager = autoDeleteManager;
}
@Override
@@ -133,13 +145,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group
BdfDictionary d = new BdfDictionary();
d.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
} catch (FormatException e) {
throw new AssertionError(e);
}
clientHelper.setContactId(txn, g.getId(), c.getId());
// Initialize the group count with current time
messageTracker.initializeGroupCount(txn, g.getId());
}
@@ -196,14 +202,18 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
boolean local = meta.getBoolean(MSG_KEY_LOCAL);
boolean read = meta.getBoolean(MSG_KEY_READ);
long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER,
NO_AUTO_DELETE_TIMER);
PrivateMessageHeader header =
new PrivateMessageHeader(m.getId(), groupId, timestamp, local,
read, false, false, hasText, headers);
read, false, false, hasText, headers, timer);
ContactId contactId = getContactId(txn, groupId);
PrivateMessageReceivedEvent event =
new PrivateMessageReceivedEvent(header, contactId);
txn.attach(event);
messageTracker.trackIncomingMessage(txn, m);
autoDeleteManager.receiveAutoDeleteTimer(txn, contactId, timer,
timestamp);
}
private List<AttachmentHeader> parseAttachmentHeaders(BdfDictionary meta)
@@ -228,13 +238,18 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
@Override
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 {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_TIMESTAMP, m.getMessage().getTimestamp());
meta.put(MSG_KEY_LOCAL, 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_HAS_TEXT, m.hasText());
BdfList headers = new BdfList();
@@ -243,6 +258,12 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
BdfList.of(a.getMessageId(), a.getContentType()));
}
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
for (AttachmentHeader a : m.getAttachmentHeaders()) {
@@ -252,11 +273,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
false);
messageTracker.trackOutgoingMessage(txn, m.getMessage());
db.commitTransaction(txn);
} catch (FormatException e) {
throw new AssertionError(e);
} finally {
db.endTransaction(txn);
}
}
@@ -355,12 +373,14 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
if (messageType == null) {
headers.add(new PrivateMessageHeader(id, g, timestamp,
local, read, s.isSent(), s.isSeen(), true,
emptyList()));
emptyList(), NO_AUTO_DELETE_TIMER));
} else {
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,
local, read, s.isSent(), s.isSeen(), hasText,
parseAttachmentHeaders(meta)));
parseAttachmentHeaders(meta), timer));
}
} catch (FormatException e) {
throw new DbException(e);
@@ -422,12 +442,13 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
}
@Override
public boolean contactSupportsImages(Transaction txn, ContactId c)
throws DbException {
public PrivateMessageFormat getContactMessageFormat(Transaction txn,
ContactId c) throws DbException {
int minorVersion = clientVersioningManager
.getClientMinorVersion(txn, c, CLIENT_ID, 0);
// support was added in 0.1
return minorVersion > 0;
if (minorVersion >= 3) return TEXT_IMAGES_AUTO_DELETE;
else if (minorVersion >= 1) return TEXT_IMAGES;
else return TEXT_ONLY;
}
@Override

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.messaging;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.BdfReaderFactory;
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.MAJOR_VERSION;
import static org.briarproject.briar.api.messaging.MessagingManager.MINOR_VERSION;
@Module
public class MessagingModule {
@@ -57,17 +57,14 @@ public class MessagingModule {
ContactManager contactManager, ValidationManager validationManager,
ConversationManager conversationManager,
ClientVersioningManager clientVersioningManager,
FeatureFlags featureFlags, MessagingManagerImpl messagingManager) {
MessagingManagerImpl messagingManager) {
lifecycleManager.registerOpenDatabaseHook(messagingManager);
contactManager.registerContactHook(messagingManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
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,
minorVersion, messagingManager);
MINOR_VERSION, messagingManager);
return messagingManager;
}

View File

@@ -17,6 +17,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
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.messaging.MessageTypes.PRIVATE_MESSAGE;
@@ -47,21 +48,43 @@ class PrivateMessageFactoryImpl implements PrivateMessageFactory {
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
@Nullable String text, List<AttachmentHeader> headers)
throws FormatException {
// Validate the arguments
if (text == null) {
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()));
}
validateTextAndAttachmentHeaders(text, headers);
BdfList attachmentList = serialiseAttachmentHeaders(headers);
// Serialise the message
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
Message m = clientHelper.createMessage(groupId, timestamp, body);
return new PrivateMessage(m, text != null, headers);
}
@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.util.ValidationUtils.checkLength;
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_CONTENT_TYPE_BYTES;
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.PRIVATE_MESSAGE;
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_DESCRIPTOR_LENGTH;
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_MSG_TYPE;
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
@Immutable
@NotNullByDefault
@@ -99,7 +102,7 @@ class PrivateMessageValidator implements MessageValidator {
private BdfMessageContext validateLegacyPrivateMessage(Message m,
BdfList body) throws FormatException {
// Private message text
// Client version 0.0: Private message text
checkSize(body, 1);
String text = body.getString(0);
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
@@ -113,8 +116,11 @@ class PrivateMessageValidator implements MessageValidator {
private BdfMessageContext validatePrivateMessage(Message m, BdfList body)
throws FormatException {
// Message type, optional private message text, attachment headers
checkSize(body, 3);
// Client version 0.1 to 0.2: Message type, optional private message
// 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);
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
BdfList headers = body.getList(2);
@@ -129,6 +135,10 @@ class PrivateMessageValidator implements MessageValidator {
String contentType = header.getString(1);
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
BdfDictionary meta = new BdfDictionary();
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_HAS_TEXT, text != null);
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);
}

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.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.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
import static java.lang.Math.max;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
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.JOIN;
@@ -39,7 +41,7 @@ import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
@Immutable
@NotNullByDefault
abstract class AbstractProtocolEngine<S extends Session>
abstract class AbstractProtocolEngine<S extends Session<?>>
implements ProtocolEngine<S> {
protected final DatabaseComponent db;
@@ -53,15 +55,23 @@ abstract class AbstractProtocolEngine<S extends Session>
private final IdentityManager identityManager;
private final MessageParser messageParser;
private final MessageEncoder messageEncoder;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
private final Clock clock;
AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
AbstractProtocolEngine(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
PrivateGroupManager privateGroupManager,
PrivateGroupFactory privateGroupFactory,
GroupMessageFactory groupMessageFactory,
IdentityManager identityManager, MessageParser messageParser,
MessageEncoder messageEncoder, MessageTracker messageTracker,
IdentityManager identityManager,
MessageParser messageParser,
MessageEncoder messageEncoder,
MessageTracker messageTracker,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) {
this.db = db;
this.clientHelper = clientHelper;
@@ -73,16 +83,11 @@ abstract class AbstractProtocolEngine<S extends Session>
this.messageParser = messageParser;
this.messageEncoder = messageEncoder;
this.messageTracker = messageTracker;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
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)
throws DbException {
if (!db.containsGroup(txn, g)) return false;
@@ -90,6 +95,7 @@ abstract class AbstractProtocolEngine<S extends Session>
return group.getClientId().equals(PrivateGroupManager.CLIENT_ID);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
boolean isValidDependency(S session, @Nullable MessageId dependency) {
MessageId expected = session.getLastRemoteMessageId();
if (dependency == null) return expected == null;
@@ -99,54 +105,108 @@ abstract class AbstractProtocolEngine<S extends Session>
void setPrivateGroupVisibility(Transaction txn, S session,
Visibility preferred) throws DbException, FormatException {
// 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,
contactId, CLIENT_ID, MAJOR_VERSION);
contactId, PrivateGroupManager.CLIENT_ID,
PrivateGroupManager.MAJOR_VERSION);
Visibility min = Visibility.min(preferred, client);
db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), min);
}
Message sendInviteMessage(Transaction txn, S session,
@Nullable String text, long timestamp, byte[] signature)
throws DbException {
Group g = db.getGroup(txn, session.getPrivateGroupId());
Message sendInviteMessage(Transaction txn, S s,
@Nullable String text, long timestamp, byte[] signature,
long timer) throws DbException {
Group g = db.getGroup(txn, s.getPrivateGroupId());
PrivateGroup privateGroup;
try {
privateGroup = privateGroupFactory.parsePrivateGroup(g);
} catch (FormatException e) {
throw new DbException(e); // Invalid group descriptor
}
Message m = messageEncoder.encodeInviteMessage(
session.getContactGroupId(), privateGroup.getId(),
timestamp, privateGroup.getName(), privateGroup.getCreator(),
privateGroup.getSalt(), text, signature);
sendMessage(txn, m, INVITE, privateGroup.getId(), true);
Message m;
ContactId c = getContactId(txn, s.getContactGroupId());
if (contactSupportsAutoDeletion(txn, c)) {
m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
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;
}
Message sendJoinMessage(Transaction txn, S session, boolean visibleInUi)
Message sendJoinMessage(Transaction txn, S s, boolean visibleInUi)
throws DbException {
Message m = messageEncoder.encodeJoinMessage(
session.getContactGroupId(), session.getPrivateGroupId(),
getLocalTimestamp(session), session.getLastLocalMessageId());
sendMessage(txn, m, JOIN, session.getPrivateGroupId(), visibleInUi);
Message m;
long localTimestamp = visibleInUi
? getTimestampForVisibleMessage(txn, s)
: 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;
}
Message sendLeaveMessage(Transaction txn, S session, boolean visibleInUi)
Message sendLeaveMessage(Transaction txn, S s, boolean visibleInUi)
throws DbException {
Message m = messageEncoder.encodeLeaveMessage(
session.getContactGroupId(), session.getPrivateGroupId(),
getLocalTimestamp(session), session.getLastLocalMessageId());
sendMessage(txn, m, LEAVE, session.getPrivateGroupId(), visibleInUi);
Message m;
long localTimestamp = visibleInUi
? getTimestampForVisibleMessage(txn, s)
: 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;
}
Message sendAbortMessage(Transaction txn, S session) throws DbException {
Message m = messageEncoder.encodeAbortMessage(
session.getContactGroupId(), session.getPrivateGroupId(),
getLocalTimestamp(session));
sendMessage(txn, m, ABORT, session.getPrivateGroupId(), false);
getTimestampForInvisibleMessage(session));
sendMessage(txn, m, ABORT, session.getPrivateGroupId(), false,
NO_AUTO_DELETE_TIMER);
return m;
}
@@ -200,7 +260,7 @@ abstract class AbstractProtocolEngine<S extends Session>
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
invite.getGroupName(), invite.getCreator(), invite.getSalt());
long timestamp =
Math.max(clock.currentTimeMillis(), invite.getTimestamp() + 1);
max(clock.currentTimeMillis(), invite.getTimestamp() + 1);
// TODO: Create the join message on the crypto executor
LocalAuthor member = identityManager.getLocalAuthor(txn);
GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
@@ -210,18 +270,49 @@ abstract class AbstractProtocolEngine<S extends Session>
.addPrivateGroup(txn, privateGroup, joinMessage, false);
}
long getLocalTimestamp(S session) {
return Math.max(clock.currentTimeMillis(),
Math.max(session.getLocalTimestamp(),
session.getInviteTimestamp()) + 1);
/**
* Returns a timestamp for a visible outgoing message. The timestamp is
* later than the timestamp of any message sent or received so far in the
* 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,
GroupId privateGroupId, boolean visibleInConversation)
throws DbException {
BdfDictionary meta = messageEncoder
.encodeMetadata(type, privateGroupId, m.getTimestamp(), true,
true, visibleInConversation, false, false);
GroupId privateGroupId, boolean visibleInConversation,
long autoDeleteTimer) throws DbException {
BdfDictionary meta = messageEncoder.encodeMetadata(type,
privateGroupId, m.getTimestamp(), true, true,
visibleInConversation, false, false, autoDeleteTimer);
try {
clientHelper.addLocalMessage(txn, m, meta, true, false);
} 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.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
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.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.SHARED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED;
@@ -36,26 +39,34 @@ import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
@NotNullByDefault
class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
CreatorProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
CreatorProtocolEngine(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
PrivateGroupManager privateGroupManager,
PrivateGroupFactory privateGroupFactory,
GroupMessageFactory groupMessageFactory,
IdentityManager identityManager, MessageParser messageParser,
MessageEncoder messageEncoder, MessageTracker messageTracker,
IdentityManager identityManager,
MessageParser messageParser,
MessageEncoder messageEncoder,
MessageTracker messageTracker,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) {
super(db, clientHelper, clientVersioningManager, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock);
messageParser, messageEncoder, messageTracker,
autoDeleteManager, conversationManager, clock);
}
@Override
public CreatorSession onInviteAction(Transaction txn, CreatorSession s,
@Nullable String text, long timestamp, byte[] signature)
throws DbException {
@Nullable String text, long timestamp, byte[] signature,
long autoDeleteTimer) throws DbException {
switch (s.getState()) {
case START:
return onLocalInvite(txn, s, text, timestamp, signature);
return onLocalInvite(txn, s, text, timestamp, signature,
autoDeleteTimer);
case INVITED:
case JOINED:
case LEFT:
@@ -145,14 +156,16 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
}
private CreatorSession onLocalInvite(Transaction txn, CreatorSession s,
@Nullable String text, long timestamp, byte[] signature)
throws DbException {
@Nullable String text, long timestamp, byte[] signature,
long autoDeleteTimer) throws DbException {
// Send an INVITE message
Message sent = sendInviteMessage(txn, s, text, timestamp, signature);
Message sent = sendInviteMessage(txn, s, text, timestamp, signature,
autoDeleteTimer);
// Track the message
messageTracker.trackOutgoingMessage(txn, sent);
// 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(),
sent.getId(), s.getLastRemoteMessageId(), localTimestamp,
timestamp, INVITED);
@@ -188,10 +201,13 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Share the private group with the contact
setPrivateGroupVisibility(txn, s, SHARED);
// Broadcast an event
ContactId contactId = getContactId(txn, m.getContactGroupId());
ContactId contactId =
clientHelper.getContactId(txn, m.getContactGroupId());
txn.attach(new GroupInvitationResponseReceivedEvent(
createInvitationResponse(m, true), contactId));
// Move to the JOINED state
@@ -212,8 +228,11 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Broadcast an event
ContactId contactId = getContactId(txn, m.getContactGroupId());
ContactId contactId =
clientHelper.getContactId(txn, m.getContactGroupId());
txn.attach(new GroupInvitationResponseReceivedEvent(
createInvitationResponse(m, false), contactId));
// Move to the START state
@@ -253,10 +272,10 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
}
private GroupInvitationResponse createInvitationResponse(
GroupInvitationMessage m, boolean accept) {
DeletableGroupInvitationMessage m, boolean accept) {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationResponse(m.getId(), m.getContactGroupId(),
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 {
// Group metadata keys
String GROUP_KEY_CONTACT_ID = "contactId";
// Message metadata keys
String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId";
@@ -13,6 +10,7 @@ interface GroupInvitationConstants {
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted";
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
// Session keys
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.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.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
@@ -123,13 +122,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
clientHelper.setContactId(txn, g.getId(), c.getId());
// If the contact belongs to any private groups, create a peer session
for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID,
PrivateGroupManager.MAJOR_VERSION)) {
@@ -159,7 +152,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
SessionId sessionId = getSessionId(meta.getPrivateGroupId());
StoredSession ss = getSession(txn, m.getGroupId(), sessionId);
// Handle the message
Session session;
Session<?> session;
MessageId storageId;
if (ss == null) {
session = handleFirstMessage(txn, m, body, meta);
@@ -189,8 +182,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
results.values().iterator().next());
}
private Session handleFirstMessage(Transaction txn, Message m, BdfList body,
MessageMetadata meta) throws DbException, FormatException {
private Session<?> handleFirstMessage(Transaction txn, Message m,
BdfList body, MessageMetadata meta)
throws DbException, FormatException {
GroupId privateGroupId = meta.getPrivateGroupId();
MessageType type = meta.getMessageType();
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)
throws DbException, FormatException {
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)
throws DbException, FormatException {
if (type == INVITE) {
@@ -256,15 +250,15 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
}
private void storeSession(Transaction txn, MessageId storageId,
Session session) throws DbException, FormatException {
Session<?> session) throws DbException, FormatException {
BdfDictionary d = sessionEncoder.encodeSession(session);
clientHelper.mergeMessageMetadata(txn, storageId, d);
}
@Override
public void sendInvitation(GroupId privateGroupId, ContactId c,
@Nullable String text, long timestamp, byte[] signature)
throws DbException {
@Nullable String text, long timestamp, byte[] signature,
long autoDeleteTimer) throws DbException {
SessionId sessionId = getSessionId(privateGroupId);
Transaction txn = db.startTransaction(false);
try {
@@ -287,7 +281,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
}
// Handle the invite action
session = creatorEngine.onInviteAction(txn, session, text,
timestamp, signature);
timestamp, signature, autoDeleteTimer);
// Store the updated session
storeSession(txn, storageId, session);
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)
throws DbException {
if (type == LocalAction.INVITE) {
@@ -420,7 +414,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return new GroupInvitationRequest(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), meta.isRead(),
status.isSent(), status.isSeen(), sessionId, pg,
invite.getText(), meta.isAvailableToAnswer(), canBeOpened);
invite.getText(), meta.isAvailableToAnswer(), canBeOpened,
invite.getAutoDeleteTimer());
}
private GroupInvitationResponse parseInvitationResponse(
@@ -430,7 +425,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return new GroupInvitationResponse(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), meta.isRead(),
status.isSent(), status.isSeen(), sessionId, accept,
meta.getPrivateGroupId());
meta.getPrivateGroupId(), meta.getAutoDeleteTimer());
}
@Override
@@ -508,7 +503,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
SessionId sessionId = getSessionId(privateGroupId);
StoredSession ss = getSession(txn, contactGroupId, sessionId);
// Create or parse the session
Session session;
Session<?> session;
MessageId storageId;
if (ss == null) {
// 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);
if (ss == null) continue; // No session for this contact
// Handle the action
Session session = handleAction(txn, LocalAction.LEAVE,
Session<?> session = handleAction(txn, LocalAction.LEAVE,
contactGroupId, ss.bdfSession);
// Store the updated 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)
throws DbException, FormatException {
Role role = sessionParser.getRole(bdfSession);
@@ -613,7 +608,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
Map<GroupId, Visibility> m = new HashMap<>();
for (BdfDictionary d : results.values()) {
Session s = sessionParser.parseSession(contactGroupId, d);
Session<?> s = sessionParser.parseSession(contactGroupId, d);
m.put(s.getPrivateGroupId(), s.getState().getVisibility());
}
return m;
@@ -644,7 +639,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
Map<GroupId, DeletableSession> sessions = new HashMap<>();
for (BdfDictionary d : metadata.values()) {
if (!sessionParser.isSession(d)) continue;
Session session;
Session<?> session;
try {
session = sessionParser.parseSession(g, d);
} catch (FormatException e) {
@@ -673,7 +668,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
getSessionId(messageMetadata.getPrivateGroupId());
StoredSession ss = getSession(txn1, g, sessionId);
if (ss == null) throw new DbException();
Session session = sessionParser
Session<?> session = sessionParser
.parseSession(g, metadata.get(ss.storageId));
sessions.put(session.getPrivateGroupId(),
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.util.ValidationUtils.checkLength;
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.MAX_GROUP_INVITATION_TEXT_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.JOIN;
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
@Immutable
@NotNullByDefault
@@ -71,8 +73,11 @@ class GroupInvitationValidator extends BdfMessageValidator {
private BdfMessageContext validateInviteMessage(Message m, BdfList body)
throws FormatException {
// Message type, creator, group name, salt, optional text, signature
checkSize(body, 6);
// Client version 0.0: Message type, creator, group name, salt,
// 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);
String groupName = body.getString(2);
checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH);
@@ -82,6 +87,10 @@ class GroupInvitationValidator extends BdfMessageValidator {
checkLength(text, 1, MAX_GROUP_INVITATION_TEXT_LENGTH);
byte[] signature = body.getRaw(5);
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
Author creator = clientHelper.parseAndValidateAuthor(creatorList);
@@ -102,20 +111,29 @@ class GroupInvitationValidator extends BdfMessageValidator {
// Create the metadata
BdfDictionary meta = messageEncoder.encodeMetadata(INVITE,
privateGroup.getId(), m.getTimestamp(), false, false, false,
false, false);
false, false, timer);
return new BdfMessageContext(meta);
}
private BdfMessageContext validateJoinMessage(Message m, BdfList body)
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);
checkLength(privateGroupId, 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));
}
BdfDictionary meta = messageEncoder.encodeMetadata(JOIN,
new GroupId(privateGroupId), m.getTimestamp(), false, false,
false, false, false);
false, false, false, timer);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
@@ -127,14 +145,23 @@ class GroupInvitationValidator extends BdfMessageValidator {
private BdfMessageContext validateLeaveMessage(Message m, BdfList body)
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);
checkLength(privateGroupId, 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));
}
BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE,
new GroupId(privateGroupId), m.getTimestamp(), false, false,
false, false, false);
false, false, false, timer);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
@@ -151,7 +178,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
checkLength(privateGroupId, UniqueId.LENGTH);
BdfDictionary meta = messageEncoder.encodeMetadata(ABORT,
new GroupId(privateGroupId), m.getTimestamp(), false, false,
false, false, false);
false, false, false, NO_AUTO_DELETE_TIMER);
return new BdfMessageContext(meta);
}
}

View File

@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class InviteMessage extends GroupInvitationMessage {
class InviteMessage extends DeletableGroupInvitationMessage {
private final String groupName;
private final Author creator;
@@ -20,8 +20,8 @@ class InviteMessage extends GroupInvitationMessage {
InviteMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
long timestamp, String groupName, Author creator, byte[] salt,
@Nullable String text, byte[] signature) {
super(id, contactGroupId, privateGroupId, timestamp);
@Nullable String text, byte[] signature, long autoDeleteTimer) {
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
this.groupName = groupName;
this.creator = creator;
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.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
@@ -41,22 +43,30 @@ import static org.briarproject.briar.privategroup.invitation.InviteeState.START;
@NotNullByDefault
class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
InviteeProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
InviteeProtocolEngine(
DatabaseComponent db,
ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
PrivateGroupManager privateGroupManager,
PrivateGroupFactory privateGroupFactory,
GroupMessageFactory groupMessageFactory,
IdentityManager identityManager, MessageParser messageParser,
MessageEncoder messageEncoder, MessageTracker messageTracker,
IdentityManager identityManager,
MessageParser messageParser,
MessageEncoder messageEncoder,
MessageTracker messageTracker,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
Clock clock) {
super(db, clientHelper, clientVersioningManager, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock);
messageParser, messageEncoder, messageTracker,
autoDeleteManager, conversationManager, clock);
}
@Override
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
}
@@ -230,7 +240,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
// The timestamp must be higher than the last invite message, if any
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// 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();
if (!contact.getId().equals(m.getCreator().getId()))
return abort(txn, s);
@@ -240,6 +251,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Receive the auto-delete timer
receiveAutoDeleteTimer(txn, m);
// Broadcast an event
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
m.getGroupName(), m.getCreator(), m.getSalt());
@@ -330,7 +343,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationRequest(m.getId(), m.getContactGroupId(),
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
@NotNullByDefault
class JoinMessage extends GroupInvitationMessage {
class JoinMessage extends DeletableGroupInvitationMessage {
@Nullable
private final MessageId previousMessageId;
JoinMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
long timestamp, @Nullable MessageId previousMessageId) {
super(id, contactGroupId, privateGroupId, timestamp);
long timestamp, @Nullable MessageId previousMessageId,
long autoDeleteTimer) {
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
this.previousMessageId = previousMessageId;
}

View File

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

View File

@@ -14,7 +14,7 @@ interface MessageEncoder {
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted);
boolean available, boolean accepted, long autoDeleteTimer);
void setVisibleInUi(BdfDictionary meta, boolean visible);
@@ -22,16 +22,49 @@ interface MessageEncoder {
void setInvitationAccepted(BdfDictionary meta, boolean accepted);
/**
* Encodes an invite message without an auto-delete timer.
*/
Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId,
long timestamp, String groupName, Author creator, byte[] salt,
@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,
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,
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,
long timestamp);
}

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