mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
82 Commits
feature-fl
...
1837-conve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f5c6f76ad | ||
|
|
927b52a6b6 | ||
|
|
a77d90355b | ||
|
|
787348042e | ||
|
|
8d2640f459 | ||
|
|
6eb803596c | ||
|
|
fd544b2c28 | ||
|
|
f9d6bca64e | ||
|
|
7aea2a99fa | ||
|
|
322b8de5f7 | ||
|
|
620c9188ba | ||
|
|
c8b3a8221f | ||
|
|
9fd27d7890 | ||
|
|
63ae41994b | ||
|
|
7da57a1a1b | ||
|
|
ea7e68d731 | ||
|
|
c0989fb631 | ||
|
|
11ebaeaaea | ||
|
|
694ddd949f | ||
|
|
319adb6b8d | ||
|
|
10d76b120b | ||
|
|
b980307fcf | ||
|
|
35c57cfa2c | ||
|
|
baa6d93034 | ||
|
|
c23d67c765 | ||
|
|
86ea384f71 | ||
|
|
145c1e1c64 | ||
|
|
b48811e9b8 | ||
|
|
87b200f0b5 | ||
|
|
e313f61b9e | ||
|
|
5ca24c0c10 | ||
|
|
a4ea1fd257 | ||
|
|
630b4ff561 | ||
|
|
45c205e4ba | ||
|
|
aff649cb06 | ||
|
|
501ca326d7 | ||
|
|
0b2a581f81 | ||
|
|
04cdf27a1c | ||
|
|
6bac5b08ab | ||
|
|
482c90a45e | ||
|
|
c042b1c6d0 | ||
|
|
df43a3d461 | ||
|
|
1f73137e52 | ||
|
|
e662d942f0 | ||
|
|
6c6c3ab3a8 | ||
|
|
b8b8894f48 | ||
|
|
c9ad852aee | ||
|
|
9d0c894fce | ||
|
|
2e3335ef66 | ||
|
|
dd216890ed | ||
|
|
238512e9bf | ||
|
|
ae41e1f780 | ||
|
|
2b41700fa7 | ||
|
|
18f98766db | ||
|
|
7e6871149b | ||
|
|
524d6a93f1 | ||
|
|
67b9ebff8e | ||
|
|
d559e821ca | ||
|
|
d829c25717 | ||
|
|
433e4e79ae | ||
|
|
647f179016 | ||
|
|
f57b16e9bf | ||
|
|
2430cc409f | ||
|
|
8693546170 | ||
|
|
7b97bb1f20 | ||
|
|
92d04e0417 | ||
|
|
bab2b4594d | ||
|
|
029ddddd27 | ||
|
|
3ee75643fc | ||
|
|
e88de213fb | ||
|
|
c26bad9f94 | ||
|
|
bce1ce0a81 | ||
|
|
1601a85ad1 | ||
|
|
b6b1bdbf82 | ||
|
|
28ecece34d | ||
|
|
f9e6b3ed3a | ||
|
|
28cd086972 | ||
|
|
3b6b77ccf5 | ||
|
|
dd9c6697b2 | ||
|
|
228907543e | ||
|
|
b477962321 | ||
|
|
98b0f64785 |
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.api.client;
|
package org.briarproject.bramble.api.client;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
@@ -119,4 +120,17 @@ public interface ClientHelper {
|
|||||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||||
BdfDictionary properties) throws FormatException;
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the contact ID from the group metadata of the given contact
|
||||||
|
* group.
|
||||||
|
*/
|
||||||
|
ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
|
throws DbException, FormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the given contact ID in the group metadata of the given contact
|
||||||
|
* group.
|
||||||
|
*/
|
||||||
|
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
|
||||||
|
throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -6,7 +6,9 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ValidationUtils {
|
public class ValidationUtils {
|
||||||
|
|
||||||
@@ -64,4 +66,9 @@ public class ValidationUtils {
|
|||||||
if (dictionary != null && dictionary.size() != size)
|
if (dictionary != null && dictionary.size() != size)
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void checkRange(@Nullable Long l, long min, long max)
|
||||||
|
throws FormatException {
|
||||||
|
if (l != null && (l < min || l > max)) throw new FormatException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package org.briarproject.bramble.client;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
@@ -39,6 +41,7 @@ import java.util.Map.Entry;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
@@ -389,4 +392,27 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
return tpMap;
|
return tpMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
|
throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta =
|
||||||
|
getGroupMetadataAsDictionary(txn, contactGroupId);
|
||||||
|
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e); // Invalid group metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContactId(Transaction txn, GroupId contactGroupId,
|
||||||
|
ContactId c) throws DbException {
|
||||||
|
BdfDictionary meta = BdfDictionary.of(
|
||||||
|
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
|
||||||
|
try {
|
||||||
|
mergeGroupMetadata(txn, contactGroupId, meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ interface ClientVersioningConstants {
|
|||||||
// Metadata keys
|
// Metadata keys
|
||||||
String MSG_KEY_UPDATE_VERSION = "version";
|
String MSG_KEY_UPDATE_VERSION = "version";
|
||||||
String MSG_KEY_LOCAL = "local";
|
String MSG_KEY_LOCAL = "local";
|
||||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import static java.util.Collections.emptyList;
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
|
|
||||||
@@ -161,13 +160,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
db.addGroup(txn, g);
|
db.addGroup(txn, g);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary meta = new BdfDictionary();
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
|
||||||
try {
|
|
||||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
// Create and store the first local update
|
// Create and store the first local update
|
||||||
List<ClientVersion> versions = new ArrayList<>(clients);
|
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||||
Collections.sort(versions);
|
Collections.sort(versions);
|
||||||
@@ -229,7 +222,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
Map<ClientMajorVersion, Visibility> after =
|
Map<ClientMajorVersion, Visibility> after =
|
||||||
getVisibilities(newLocalStates, newRemoteStates);
|
getVisibilities(newLocalStates, newRemoteStates);
|
||||||
// Call hooks for any visibilities that have changed
|
// Call hooks for any visibilities that have changed
|
||||||
ContactId c = getContactId(txn, m.getGroupId());
|
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||||
if (!before.equals(after)) {
|
if (!before.equals(after)) {
|
||||||
Contact contact = db.getContact(txn, c);
|
Contact contact = db.getContact(txn, c);
|
||||||
callVisibilityHooks(txn, contact, before, after);
|
callVisibilityHooks(txn, contact, before, after);
|
||||||
@@ -521,17 +514,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
storeUpdate(txn, g, states, 1);
|
storeUpdate(txn, g, states, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId getContactId(Transaction txn, GroupId g)
|
|
||||||
throws DbException {
|
|
||||||
try {
|
|
||||||
BdfDictionary meta =
|
|
||||||
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
|
||||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ClientState> updateStatesFromRemoteStates(
|
private List<ClientState> updateStatesFromRemoteStates(
|
||||||
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
||||||
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -60,8 +59,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final ClientId clientId = getClientId();
|
private final ClientId clientId = getClientId();
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
private final Transaction txn = new Transaction(null, false);
|
private final Transaction txn = new Transaction(null, false);
|
||||||
private final BdfDictionary groupMeta = BdfDictionary.of(
|
|
||||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
|
||||||
|
|
||||||
private ClientVersioningManagerImpl createInstance() {
|
private ClientVersioningManagerImpl createInstance() {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -123,8 +120,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).addGroup(txn, contactGroup);
|
oneOf(db).addGroup(txn, contactGroup);
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroup.getId(), SHARED);
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||||
groupMeta);
|
contact.getId());
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||||
@@ -460,9 +457,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
|
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
|
||||||
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
|
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
|
||||||
// Get contact ID
|
// Get contact ID
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||||
contactGroup.getId());
|
will(returnValue(contact.getId()));
|
||||||
will(returnValue(groupMeta));
|
|
||||||
// No states or visibilities have changed
|
// No states or visibilities have changed
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -492,10 +488,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
// Load the latest local update
|
// Load the latest local update
|
||||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||||
will(returnValue(oldLocalUpdateBody));
|
will(returnValue(oldLocalUpdateBody));
|
||||||
// Get client ID
|
// Get contact ID
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||||
contactGroup.getId());
|
will(returnValue(contact.getId()));
|
||||||
will(returnValue(groupMeta));
|
|
||||||
// No states or visibilities have changed
|
// No states or visibilities have changed
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -546,8 +541,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
BdfDictionary groupMeta = BdfDictionary.of(
|
|
||||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||||
@@ -577,9 +570,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true, false);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||||
contactGroup.getId());
|
will(returnValue(contact.getId()));
|
||||||
will(returnValue(groupMeta));
|
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
will(returnValue(contact));
|
will(returnValue(contact));
|
||||||
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
||||||
@@ -619,8 +611,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
BdfDictionary groupMeta = BdfDictionary.of(
|
|
||||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||||
@@ -650,9 +640,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true, false);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||||
contactGroup.getId());
|
will(returnValue(contact.getId()));
|
||||||
will(returnValue(groupMeta));
|
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
will(returnValue(contact));
|
will(returnValue(contact));
|
||||||
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);
|
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);
|
||||||
|
|||||||
@@ -141,6 +141,16 @@
|
|||||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".android.conversation.ConversationSettingsActivity"
|
||||||
|
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||||
|
android:label="@string/disappearing_messages_title"
|
||||||
|
android:theme="@style/BriarTheme">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
||||||
android:label="@string/groups_create_group_title"
|
android:label="@string/groups_create_group_title"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
|
|||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
@@ -39,6 +40,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|||||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||||
import org.briarproject.briar.api.android.LockManager;
|
import org.briarproject.briar.api.android.LockManager;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.blog.BlogManager;
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
@@ -169,6 +171,10 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
AndroidWakeLockManager wakeLockManager();
|
AndroidWakeLockManager wakeLockManager();
|
||||||
|
|
||||||
|
TransactionManager transactionManager();
|
||||||
|
|
||||||
|
AutoDeleteManager autoDeleteManager();
|
||||||
|
|
||||||
void inject(SignInReminderReceiver briarService);
|
void inject(SignInReminderReceiver briarService);
|
||||||
|
|
||||||
void inject(BriarService briarService);
|
void inject(BriarService briarService);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
|
|||||||
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
|
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
|
||||||
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
||||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ConversationSettingsActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ConversationSettingsFragment;
|
||||||
import org.briarproject.briar.android.conversation.ImageActivity;
|
import org.briarproject.briar.android.conversation.ImageActivity;
|
||||||
import org.briarproject.briar.android.conversation.ImageFragment;
|
import org.briarproject.briar.android.conversation.ImageFragment;
|
||||||
import org.briarproject.briar.android.forum.CreateForumActivity;
|
import org.briarproject.briar.android.forum.CreateForumActivity;
|
||||||
@@ -184,6 +186,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(PendingContactListActivity activity);
|
void inject(PendingContactListActivity activity);
|
||||||
|
|
||||||
|
void inject(ConversationSettingsActivity activity);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
|
|
||||||
void inject(AuthorNameFragment fragment);
|
void inject(AuthorNameFragment fragment);
|
||||||
@@ -234,4 +238,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(ImageFragment imageFragment);
|
void inject(ImageFragment imageFragment);
|
||||||
|
|
||||||
|
void inject(ConversationSettingsFragment conversationSettingsFragment);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
|||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -273,15 +275,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||||
sendController = new TextAttachmentController(textInputView,
|
sendController = new TextAttachmentController(textInputView,
|
||||||
imagePreview, this, viewModel);
|
imagePreview, this, viewModel);
|
||||||
viewModel.hasImageSupport().observe(this, new Observer<Boolean>() {
|
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
|
||||||
@Override
|
if (format != TEXT_ONLY) {
|
||||||
public void onChanged(@Nullable Boolean hasSupport) {
|
// TODO: remove cast when removing feature flag
|
||||||
if (hasSupport != null && hasSupport) {
|
((TextAttachmentController) sendController)
|
||||||
// TODO: remove cast when removing feature flag
|
.setImagesSupported();
|
||||||
((TextAttachmentController) sendController)
|
|
||||||
.setImagesSupported();
|
|
||||||
viewModel.hasImageSupport().removeObserver(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -384,6 +382,12 @@ public class ConversationActivity extends BriarActivity
|
|||||||
// enable alias action if available
|
// enable alias action if available
|
||||||
observeOnce(viewModel.getContact(), this, contact ->
|
observeOnce(viewModel.getContact(), this, contact ->
|
||||||
menu.findItem(R.id.action_set_alias).setEnabled(true));
|
menu.findItem(R.id.action_set_alias).setEnabled(true));
|
||||||
|
// show auto-delete timer setting only, if contacts supports it
|
||||||
|
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
|
||||||
|
boolean visible = format == TEXT_IMAGES_AUTO_DELETE;
|
||||||
|
menu.findItem(R.id.action_conversation_settings)
|
||||||
|
.setVisible(visible);
|
||||||
|
});
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -405,6 +409,12 @@ public class ConversationActivity extends BriarActivity
|
|||||||
AliasDialogFragment.newInstance().show(
|
AliasDialogFragment.newInstance().show(
|
||||||
getSupportFragmentManager(), AliasDialogFragment.TAG);
|
getSupportFragmentManager(), AliasDialogFragment.TAG);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_conversation_settings:
|
||||||
|
if (contactId == null) return false;
|
||||||
|
intent = new Intent(this, ConversationSettingsActivity.class);
|
||||||
|
intent.putExtra(CONTACT_ID, contactId.getInt());
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
case R.id.action_delete_all_messages:
|
case R.id.action_delete_all_messages:
|
||||||
askToDeleteAllMessages();
|
askToDeleteAllMessages();
|
||||||
return true;
|
return true;
|
||||||
@@ -657,8 +667,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
}
|
}
|
||||||
} else if (e instanceof ConversationMessageReceivedEvent) {
|
} else if (e instanceof ConversationMessageReceivedEvent) {
|
||||||
ConversationMessageReceivedEvent p =
|
ConversationMessageReceivedEvent<?> p =
|
||||||
(ConversationMessageReceivedEvent) e;
|
(ConversationMessageReceivedEvent<?>) e;
|
||||||
if (p.getContactId().equals(contactId)) {
|
if (p.getContactId().equals(contactId)) {
|
||||||
LOG.info("Message received, adding");
|
LOG.info("Message received, adding");
|
||||||
onNewConversationMessage(p.getMessageHeader());
|
onNewConversationMessage(p.getMessageHeader());
|
||||||
@@ -756,18 +766,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
List<AttachmentHeader> attachmentHeaders) {
|
List<AttachmentHeader> attachmentHeaders) {
|
||||||
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
|
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
long timestamp = System.currentTimeMillis();
|
viewModel.sendMessage(text, attachmentHeaders);
|
||||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
|
||||||
viewModel.sendMessage(text, attachmentHeaders, timestamp);
|
|
||||||
textInputView.clearText();
|
textInputView.clearText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getMinTimestampForNewMessage() {
|
|
||||||
// Don't use an earlier timestamp than the newest message
|
|
||||||
ConversationItem item = adapter.getLastItem();
|
|
||||||
return item == null ? 0 : item.getTime() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
||||||
if (h == null) return;
|
if (h == null) return;
|
||||||
addConversationItem(h.accept(visitor));
|
addConversationItem(h.accept(visitor));
|
||||||
@@ -972,13 +974,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
adapter.notifyItemChanged(position, item);
|
adapter.notifyItemChanged(position, item);
|
||||||
}
|
}
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
|
||||||
try {
|
try {
|
||||||
switch (item.getRequestType()) {
|
switch (item.getRequestType()) {
|
||||||
case INTRODUCTION:
|
case INTRODUCTION:
|
||||||
respondToIntroductionRequest(item.getSessionId(),
|
respondToIntroductionRequest(item.getSessionId(),
|
||||||
accept, timestamp);
|
accept);
|
||||||
break;
|
break;
|
||||||
case FORUM:
|
case FORUM:
|
||||||
respondToForumRequest(item.getSessionId(), accept);
|
respondToForumRequest(item.getSessionId(), accept);
|
||||||
@@ -1054,9 +1054,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void respondToIntroductionRequest(SessionId sessionId,
|
private void respondToIntroductionRequest(SessionId sessionId,
|
||||||
boolean accept, long time) throws DbException {
|
boolean accept) throws DbException {
|
||||||
introductionManager.respondToIntroduction(contactId, sessionId, time,
|
introductionManager.respondToIntroduction(contactId, sessionId, accept);
|
||||||
accept);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ abstract class ConversationItem {
|
|||||||
protected String text;
|
protected String text;
|
||||||
private final MessageId id;
|
private final MessageId id;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final long time;
|
private final long time, autoDeleteTimer;
|
||||||
private final boolean isIncoming;
|
private final boolean isIncoming;
|
||||||
private boolean read, sent, seen;
|
private boolean read, sent, seen;
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ abstract class ConversationItem {
|
|||||||
this.id = h.getId();
|
this.id = h.getId();
|
||||||
this.groupId = h.getGroupId();
|
this.groupId = h.getGroupId();
|
||||||
this.time = h.getTimestamp();
|
this.time = h.getTimestamp();
|
||||||
|
this.autoDeleteTimer = h.getAutoDeleteTimer();
|
||||||
this.read = h.isRead();
|
this.read = h.isRead();
|
||||||
this.sent = h.isSent();
|
this.sent = h.isSent();
|
||||||
this.seen = h.isSeen();
|
this.seen = h.isSeen();
|
||||||
@@ -68,6 +69,10 @@ abstract class ConversationItem {
|
|||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getAutoDeleteTimer() {
|
||||||
|
return autoDeleteTimer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only useful for incoming messages.
|
* Only useful for incoming messages.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ import androidx.annotation.UiThread;
|
|||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.bramble.util.StringUtils.trim;
|
import static org.briarproject.bramble.util.StringUtils.trim;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -26,6 +29,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
|||||||
private final OutItemViewHolder outViewHolder;
|
private final OutItemViewHolder outViewHolder;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
protected final TextView time;
|
protected final TextView time;
|
||||||
|
private final View bomb;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String itemKey = null;
|
private String itemKey = null;
|
||||||
|
|
||||||
@@ -38,6 +42,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
|||||||
layout = v.findViewById(R.id.layout);
|
layout = v.findViewById(R.id.layout);
|
||||||
text = v.findViewById(R.id.text);
|
text = v.findViewById(R.id.text);
|
||||||
time = v.findViewById(R.id.time);
|
time = v.findViewById(R.id.time);
|
||||||
|
bomb = v.findViewById(R.id.bomb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@@ -52,6 +57,9 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
|||||||
long timestamp = item.getTime();
|
long timestamp = item.getTime();
|
||||||
time.setText(formatDate(time.getContext(), timestamp));
|
time.setText(formatDate(time.getContext(), timestamp));
|
||||||
|
|
||||||
|
boolean showBomb = item.getAutoDeleteTimer() != NO_AUTO_DELETE_TIMER;
|
||||||
|
bomb.setVisibility(showBomb ? VISIBLE : GONE);
|
||||||
|
|
||||||
if (outViewHolder != null) outViewHolder.bind(item);
|
if (outViewHolder != null) outViewHolder.bind(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
@@ -28,10 +29,13 @@ import org.briarproject.briar.android.attachment.AttachmentRetriever;
|
|||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
|
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||||
|
|
||||||
@@ -50,6 +54,7 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
import androidx.lifecycle.Transformations;
|
import androidx.lifecycle.Transformations;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
@@ -57,12 +62,15 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ConversationViewModel extends AndroidViewModel
|
public class ConversationViewModel extends AndroidViewModel
|
||||||
implements EventListener, AttachmentManager {
|
implements EventListener, AttachmentManager {
|
||||||
|
|
||||||
private static Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(ConversationViewModel.class.getName());
|
getLogger(ConversationViewModel.class.getName());
|
||||||
|
|
||||||
private static final String SHOW_ONBOARDING_IMAGE =
|
private static final String SHOW_ONBOARDING_IMAGE =
|
||||||
@@ -80,6 +88,8 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
private final PrivateMessageFactory privateMessageFactory;
|
private final PrivateMessageFactory privateMessageFactory;
|
||||||
private final AttachmentRetriever attachmentRetriever;
|
private final AttachmentRetriever attachmentRetriever;
|
||||||
private final AttachmentCreator attachmentCreator;
|
private final AttachmentCreator attachmentCreator;
|
||||||
|
private final AutoDeleteManager autoDeleteManager;
|
||||||
|
private final ConversationManager conversationManager;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ContactId contactId = null;
|
private ContactId contactId = null;
|
||||||
@@ -89,7 +99,7 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
private final LiveData<String> contactName =
|
private final LiveData<String> contactName =
|
||||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||||
private final LiveData<GroupId> messagingGroupId;
|
private final LiveData<GroupId> messagingGroupId;
|
||||||
private final MutableLiveData<Boolean> imageSupport =
|
private final MutableLiveData<PrivateMessageFormat> privateMessageFormat =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
||||||
new MutableLiveEvent<>();
|
new MutableLiveEvent<>();
|
||||||
@@ -112,7 +122,9 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
SettingsManager settingsManager,
|
SettingsManager settingsManager,
|
||||||
PrivateMessageFactory privateMessageFactory,
|
PrivateMessageFactory privateMessageFactory,
|
||||||
AttachmentRetriever attachmentRetriever,
|
AttachmentRetriever attachmentRetriever,
|
||||||
AttachmentCreator attachmentCreator) {
|
AttachmentCreator attachmentCreator,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -123,6 +135,8 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
this.privateMessageFactory = privateMessageFactory;
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
this.attachmentRetriever = attachmentRetriever;
|
this.attachmentRetriever = attachmentRetriever;
|
||||||
this.attachmentCreator = attachmentCreator;
|
this.attachmentCreator = attachmentCreator;
|
||||||
|
this.autoDeleteManager = autoDeleteManager;
|
||||||
|
this.conversationManager = conversationManager;
|
||||||
messagingGroupId = Transformations
|
messagingGroupId = Transformations
|
||||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
@@ -205,16 +219,13 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void sendMessage(@Nullable String text,
|
void sendMessage(@Nullable String text, List<AttachmentHeader> headers) {
|
||||||
List<AttachmentHeader> headers, long timestamp) {
|
|
||||||
// messagingGroupId is loaded with the contact
|
// messagingGroupId is loaded with the contact
|
||||||
observeForeverOnce(messagingGroupId, groupId -> {
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
requireNonNull(groupId);
|
requireNonNull(groupId);
|
||||||
observeForeverOnce(imageSupport, hasImageSupport -> {
|
observeForeverOnce(privateMessageFormat, format ->
|
||||||
requireNonNull(hasImageSupport);
|
storeMessage(requireNonNull(contactId), groupId, text,
|
||||||
createMessage(groupId, text, headers, timestamp,
|
headers, format));
|
||||||
hasImageSupport);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,10 +255,10 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
|
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
|
||||||
// check if images are supported
|
// check if images and auto-deletion are supported
|
||||||
boolean imagesSupported = db.transactionWithResult(true, txn ->
|
PrivateMessageFormat format = db.transactionWithResult(true, txn ->
|
||||||
messagingManager.contactSupportsImages(txn, c));
|
messagingManager.getContactMessageFormat(txn, c));
|
||||||
imageSupport.postValue(imagesSupported);
|
privateMessageFormat.postValue(format);
|
||||||
|
|
||||||
// check if introductions are supported
|
// check if introductions are supported
|
||||||
Collection<Contact> contacts = contactManager.getContacts();
|
Collection<Contact> contacts = contactManager.getContacts();
|
||||||
@@ -256,7 +267,7 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
|
|
||||||
// we only show one onboarding dialog at a time
|
// we only show one onboarding dialog at a time
|
||||||
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||||
if (imagesSupported &&
|
if (format != TEXT_ONLY &&
|
||||||
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
|
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
|
||||||
onOnboardingShown(SHOW_ONBOARDING_IMAGE);
|
onOnboardingShown(SHOW_ONBOARDING_IMAGE);
|
||||||
showImageOnboarding.postEvent(true);
|
showImageOnboarding.postEvent(true);
|
||||||
@@ -274,40 +285,67 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
private PrivateMessage createMessage(Transaction txn, ContactId c,
|
||||||
private void createMessage(GroupId groupId, @Nullable String text,
|
GroupId groupId, @Nullable String text,
|
||||||
List<AttachmentHeader> headers, long timestamp,
|
List<AttachmentHeader> headers, PrivateMessageFormat format)
|
||||||
boolean hasImageSupport) {
|
throws DbException {
|
||||||
|
long timestamp =
|
||||||
|
conversationManager.getTimestampForOutgoingMessage(txn, c);
|
||||||
try {
|
try {
|
||||||
PrivateMessage pm;
|
if (format == TEXT_ONLY) {
|
||||||
if (hasImageSupport) {
|
return privateMessageFactory.createLegacyPrivateMessage(
|
||||||
pm = privateMessageFactory.createPrivateMessage(groupId,
|
groupId, timestamp, requireNonNull(text));
|
||||||
|
} else if (format == TEXT_IMAGES) {
|
||||||
|
return privateMessageFactory.createPrivateMessage(groupId,
|
||||||
timestamp, text, headers);
|
timestamp, text, headers);
|
||||||
} else {
|
} else {
|
||||||
pm = privateMessageFactory.createLegacyPrivateMessage(
|
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
|
||||||
groupId, timestamp, requireNonNull(text));
|
timestamp);
|
||||||
|
return privateMessageFactory.createPrivateMessage(groupId,
|
||||||
|
timestamp, text, headers, timer);
|
||||||
}
|
}
|
||||||
storeMessage(pm);
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void storeMessage(PrivateMessage m) {
|
private void storeMessage(ContactId c, GroupId groupId,
|
||||||
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
@Nullable String text, List<AttachmentHeader> headers,
|
||||||
|
PrivateMessageFormat format) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
db.transaction(false, txn -> {
|
||||||
messagingManager.addLocalMessage(m);
|
long start = now();
|
||||||
logDuration(LOG, "Storing message", start);
|
PrivateMessage m = createMessage(txn, c, groupId, text,
|
||||||
Message message = m.getMessage();
|
headers, format);
|
||||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
messagingManager.addLocalMessage(txn, m);
|
||||||
message.getId(), message.getGroupId(),
|
logDuration(LOG, "Storing message", start);
|
||||||
message.getTimestamp(), true, true, false, false,
|
Message message = m.getMessage();
|
||||||
m.hasText(), m.getAttachmentHeaders());
|
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||||
// TODO add text to cache when available here
|
message.getId(), message.getGroupId(),
|
||||||
addedHeader.postEvent(h);
|
message.getTimestamp(), true, true, false, false,
|
||||||
|
m.hasText(), m.getAttachmentHeaders(),
|
||||||
|
m.getAutoDeleteTimer());
|
||||||
|
// TODO add text to cache when available here
|
||||||
|
MessageId id = message.getId();
|
||||||
|
txn.attach(() -> attachmentCreator.onAttachmentsSent(id));
|
||||||
|
addedHeader.postEvent(h);
|
||||||
|
});
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAutoDeleteTimerEnabled(boolean enabled) {
|
||||||
|
final long timer = enabled ? DAYS.toMillis(7) : NO_AUTO_DELETE_TIMER;
|
||||||
|
// ContactId is set before menu gets inflated and UI interaction
|
||||||
|
final ContactId c = requireNonNull(contactId);
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
db.transaction(false, txn ->
|
||||||
|
autoDeleteManager.setAutoDeleteTimer(txn, c, timer));
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -330,8 +368,8 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
return contactName;
|
return contactName;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<Boolean> hasImageSupport() {
|
LiveData<PrivateMessageFormat> getPrivateMessageFormat() {
|
||||||
return imageSupport;
|
return privateMessageFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveEvent<Boolean> showImageOnboarding() {
|
LiveEvent<Boolean> showImageOnboarding() {
|
||||||
|
|||||||
@@ -211,8 +211,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
introductionActivity.runOnDbThread(() -> {
|
introductionActivity.runOnDbThread(() -> {
|
||||||
// actually make the introduction
|
// actually make the introduction
|
||||||
try {
|
try {
|
||||||
long timestamp = System.currentTimeMillis();
|
introductionManager.makeIntroduction(c1, c2, text);
|
||||||
introductionManager.makeIntroduction(c1, c2, text, timestamp);
|
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
introductionError();
|
introductionError();
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
@@ -15,6 +17,8 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||||
@@ -35,6 +39,8 @@ import javax.inject.Inject;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -43,9 +49,12 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
|||||||
implements CreateGroupController {
|
implements CreateGroupController {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(CreateGroupControllerImpl.class.getName());
|
getLogger(CreateGroupControllerImpl.class.getName());
|
||||||
|
|
||||||
private final Executor cryptoExecutor;
|
private final Executor cryptoExecutor;
|
||||||
|
private final TransactionManager db;
|
||||||
|
private final AutoDeleteManager autoDeleteManager;
|
||||||
|
private final ConversationManager conversationManager;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
private final IdentityManager identityManager;
|
private final IdentityManager identityManager;
|
||||||
private final PrivateGroupFactory groupFactory;
|
private final PrivateGroupFactory groupFactory;
|
||||||
@@ -56,16 +65,26 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
CreateGroupControllerImpl(
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@CryptoExecutor Executor cryptoExecutor,
|
@CryptoExecutor Executor cryptoExecutor,
|
||||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
TransactionManager db,
|
||||||
IdentityManager identityManager, PrivateGroupFactory groupFactory,
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
ContactManager contactManager,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
PrivateGroupFactory groupFactory,
|
||||||
GroupMessageFactory groupMessageFactory,
|
GroupMessageFactory groupMessageFactory,
|
||||||
PrivateGroupManager groupManager,
|
PrivateGroupManager groupManager,
|
||||||
GroupInvitationFactory groupInvitationFactory,
|
GroupInvitationFactory groupInvitationFactory,
|
||||||
GroupInvitationManager groupInvitationManager, Clock clock) {
|
GroupInvitationManager groupInvitationManager,
|
||||||
|
Clock clock) {
|
||||||
super(dbExecutor, lifecycleManager, contactManager);
|
super(dbExecutor, lifecycleManager, contactManager);
|
||||||
this.cryptoExecutor = cryptoExecutor;
|
this.cryptoExecutor = cryptoExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.autoDeleteManager = autoDeleteManager;
|
||||||
|
this.conversationManager = conversationManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
this.groupFactory = groupFactory;
|
this.groupFactory = groupFactory;
|
||||||
@@ -129,16 +148,14 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
|||||||
ResultExceptionHandler<Void, DbException> handler) {
|
ResultExceptionHandler<Void, DbException> handler) {
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
LocalAuthor localAuthor = identityManager.getLocalAuthor();
|
db.transaction(true, txn -> {
|
||||||
List<Contact> contacts = new ArrayList<>();
|
LocalAuthor localAuthor =
|
||||||
for (ContactId c : contactIds) {
|
identityManager.getLocalAuthor(txn);
|
||||||
try {
|
List<InvitationContext> contexts =
|
||||||
contacts.add(contactManager.getContact(c));
|
createInvitationContexts(txn, contactIds);
|
||||||
} catch (NoSuchContactException e) {
|
txn.attach(() -> signInvitations(g, localAuthor, contexts,
|
||||||
// Continue
|
text, handler));
|
||||||
}
|
});
|
||||||
}
|
|
||||||
signInvitations(g, localAuthor, contacts, text, handler);
|
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
handler.onException(e);
|
handler.onException(e);
|
||||||
@@ -146,17 +163,32 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<InvitationContext> createInvitationContexts(Transaction txn,
|
||||||
|
Collection<ContactId> contactIds) throws DbException {
|
||||||
|
List<InvitationContext> contexts = new ArrayList<>();
|
||||||
|
for (ContactId c : contactIds) {
|
||||||
|
try {
|
||||||
|
Contact contact = contactManager.getContact(txn, c);
|
||||||
|
long timestamp = conversationManager
|
||||||
|
.getTimestampForOutgoingMessage(txn, c);
|
||||||
|
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
|
||||||
|
timestamp);
|
||||||
|
contexts.add(new InvitationContext(contact, timestamp, timer));
|
||||||
|
} catch (NoSuchContactException e) {
|
||||||
|
// Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
private void signInvitations(GroupId g, LocalAuthor localAuthor,
|
private void signInvitations(GroupId g, LocalAuthor localAuthor,
|
||||||
Collection<Contact> contacts, @Nullable String text,
|
List<InvitationContext> contexts, @Nullable String text,
|
||||||
ResultExceptionHandler<Void, DbException> handler) {
|
ResultExceptionHandler<Void, DbException> handler) {
|
||||||
cryptoExecutor.execute(() -> {
|
cryptoExecutor.execute(() -> {
|
||||||
long timestamp = clock.currentTimeMillis();
|
for (InvitationContext ctx : contexts) {
|
||||||
List<InvitationContext> contexts = new ArrayList<>();
|
ctx.signature = groupInvitationFactory.signInvitation(
|
||||||
for (Contact c : contacts) {
|
ctx.contact, g, ctx.timestamp,
|
||||||
byte[] signature = groupInvitationFactory.signInvitation(c, g,
|
localAuthor.getPrivateKey());
|
||||||
timestamp, localAuthor.getPrivateKey());
|
|
||||||
contexts.add(new InvitationContext(c.getId(), timestamp,
|
|
||||||
signature));
|
|
||||||
}
|
}
|
||||||
sendInvitations(g, contexts, text, handler);
|
sendInvitations(g, contexts, text, handler);
|
||||||
});
|
});
|
||||||
@@ -167,16 +199,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
|||||||
ResultExceptionHandler<Void, DbException> handler) {
|
ResultExceptionHandler<Void, DbException> handler) {
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
for (InvitationContext context : contexts) {
|
for (InvitationContext ctx : contexts) {
|
||||||
try {
|
try {
|
||||||
groupInvitationManager.sendInvitation(g,
|
groupInvitationManager.sendInvitation(g,
|
||||||
context.contactId, text, context.timestamp,
|
ctx.contact.getId(), text, ctx.timestamp,
|
||||||
context.signature);
|
requireNonNull(ctx.signature),
|
||||||
|
ctx.autoDeleteTimer);
|
||||||
} catch (NoSuchContactException e) {
|
} catch (NoSuchContactException e) {
|
||||||
// Continue
|
// Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//noinspection ConstantConditions
|
|
||||||
handler.onResult(null);
|
handler.onResult(null);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
@@ -187,15 +219,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
|||||||
|
|
||||||
private static class InvitationContext {
|
private static class InvitationContext {
|
||||||
|
|
||||||
private final ContactId contactId;
|
private final Contact contact;
|
||||||
private final long timestamp;
|
private final long timestamp, autoDeleteTimer;
|
||||||
private final byte[] signature;
|
@Nullable
|
||||||
|
private byte[] signature = null;
|
||||||
|
|
||||||
private InvitationContext(ContactId contactId, long timestamp,
|
private InvitationContext(Contact contact, long timestamp,
|
||||||
byte[] signature) {
|
long autoDeleteTimer) {
|
||||||
this.contactId = contactId;
|
this.contact = contact;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.signature = signature;
|
this.autoDeleteTimer = autoDeleteTimer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
||||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -25,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -33,21 +32,16 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
|
|||||||
implements ShareBlogController {
|
implements ShareBlogController {
|
||||||
|
|
||||||
private final static Logger LOG =
|
private final static Logger LOG =
|
||||||
Logger.getLogger(ShareBlogControllerImpl.class.getName());
|
getLogger(ShareBlogControllerImpl.class.getName());
|
||||||
|
|
||||||
private final ConversationManager conversationManager;
|
|
||||||
private final BlogSharingManager blogSharingManager;
|
private final BlogSharingManager blogSharingManager;
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||||
ConversationManager conversationManager,
|
BlogSharingManager blogSharingManager) {
|
||||||
BlogSharingManager blogSharingManager, Clock clock) {
|
|
||||||
super(dbExecutor, lifecycleManager, contactManager);
|
super(dbExecutor, lifecycleManager, contactManager);
|
||||||
this.conversationManager = conversationManager;
|
|
||||||
this.blogSharingManager = blogSharingManager;
|
this.blogSharingManager = blogSharingManager;
|
||||||
this.clock = clock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,10 +56,7 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
|
|||||||
try {
|
try {
|
||||||
for (ContactId c : contacts) {
|
for (ContactId c : contacts) {
|
||||||
try {
|
try {
|
||||||
long time = Math.max(clock.currentTimeMillis(),
|
blogSharingManager.sendInvitation(g, c, text);
|
||||||
conversationManager.getGroupCount(c)
|
|
||||||
.getLatestMsgTime() + 1);
|
|
||||||
blogSharingManager.sendInvitation(g, c, text, time);
|
|
||||||
} catch (NoSuchContactException | NoSuchGroupException e) {
|
} catch (NoSuchContactException | NoSuchGroupException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
||||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
|
||||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -25,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -33,21 +32,16 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
|
|||||||
implements ShareForumController {
|
implements ShareForumController {
|
||||||
|
|
||||||
private final static Logger LOG =
|
private final static Logger LOG =
|
||||||
Logger.getLogger(ShareForumControllerImpl.class.getName());
|
getLogger(ShareForumControllerImpl.class.getName());
|
||||||
|
|
||||||
private final ConversationManager conversationManager;
|
|
||||||
private final ForumSharingManager forumSharingManager;
|
private final ForumSharingManager forumSharingManager;
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||||
ConversationManager conversationManager,
|
ForumSharingManager forumSharingManager) {
|
||||||
ForumSharingManager forumSharingManager, Clock clock) {
|
|
||||||
super(dbExecutor, lifecycleManager, contactManager);
|
super(dbExecutor, lifecycleManager, contactManager);
|
||||||
this.conversationManager = conversationManager;
|
|
||||||
this.forumSharingManager = forumSharingManager;
|
this.forumSharingManager = forumSharingManager;
|
||||||
this.clock = clock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,10 +56,7 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
|
|||||||
try {
|
try {
|
||||||
for (ContactId c : contacts) {
|
for (ContactId c : contacts) {
|
||||||
try {
|
try {
|
||||||
long time = Math.max(clock.currentTimeMillis(),
|
forumSharingManager.sendInvitation(g, c, text);
|
||||||
conversationManager.getGroupCount(c)
|
|
||||||
.getLatestMsgTime() + 1);
|
|
||||||
forumSharingManager.sendInvitation(g, c, text, time);
|
|
||||||
} catch (NoSuchContactException | NoSuchGroupException e) {
|
} catch (NoSuchContactException | NoSuchGroupException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
|
|||||||
9
briar-android/src/main/res/drawable/ic_bomb.xml
Normal file
9
briar-android/src/main/res/drawable/ic_bomb.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -68,6 +68,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
app:tint="?android:attr/textColorSecondary"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -67,6 +67,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
app:tint="?android:attr/textColorSecondary"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -68,6 +68,17 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
app:tint="?android:attr/textColorSecondary"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -73,11 +73,20 @@
|
|||||||
style="@style/TextMessage.Timestamp"
|
style="@style/TextMessage.Timestamp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="6dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:layout_marginRight="6dp"
|
android:layout_marginRight="4dp"
|
||||||
android:textColor="@color/private_message_date_inverse"
|
android:textColor="@color/private_message_date_inverse"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/status"
|
android:id="@+id/status"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -48,10 +48,28 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/text"
|
app:layout_constraintEnd_toStartOf="@+id/bomb"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/text"
|
app:layout_constraintTop_toBottomOf="@+id/text"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/text"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/time"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/text"
|
||||||
|
app:tint="?android:attr/textColorSecondary"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -55,14 +55,26 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/text"
|
app:layout_constraintTop_toBottomOf="@+id/text"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/time"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/time"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/time"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/status"
|
android:id="@+id/status"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/margin_medium"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_marginLeft="@dimen/margin_medium"
|
android:layout_marginLeft="4dp"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/time"
|
app:layout_constraintBottom_toBottomOf="@+id/time"
|
||||||
app:layout_constraintStart_toEndOf="@+id/time"
|
app:layout_constraintStart_toEndOf="@+id/bomb"
|
||||||
app:layout_constraintTop_toTopOf="@+id/time"
|
app:layout_constraintTop_toTopOf="@+id/time"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@drawable/message_delivered" />
|
tools:src="@drawable/message_delivered" />
|
||||||
|
|||||||
@@ -68,10 +68,27 @@
|
|||||||
style="@style/TextMessage.Timestamp"
|
style="@style/TextMessage.Timestamp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@+id/bomb"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/acceptButton"
|
app:layout_constraintTop_toBottomOf="@+id/acceptButton"
|
||||||
tools:text="Dec 24, 13:37" />
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bomb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:src="@drawable/ic_bomb"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/time"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/acceptButton"
|
||||||
|
app:tint="?android:attr/textColorSecondary"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -1,30 +1,37 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_introduction"
|
android:id="@+id/action_introduction"
|
||||||
|
android:enabled="false"
|
||||||
android:icon="@drawable/introduction_white"
|
android:icon="@drawable/introduction_white"
|
||||||
android:title="@string/introduction_menu_item"
|
android:title="@string/introduction_menu_item"
|
||||||
android:enabled="false"
|
app:showAsAction="never" />
|
||||||
app:showAsAction="never"/>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_set_alias"
|
android:id="@+id/action_set_alias"
|
||||||
android:title="@string/set_contact_alias"
|
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
app:showAsAction="never"/>
|
android:title="@string/set_contact_alias"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_conversation_settings"
|
||||||
|
android:title="@string/menu_item_disappearing_messages"
|
||||||
|
android:visible="false"
|
||||||
|
app:showAsAction="never"
|
||||||
|
tools:visible="true" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_delete_all_messages"
|
android:id="@+id/action_delete_all_messages"
|
||||||
android:title="@string/delete_all_messages"
|
android:title="@string/delete_all_messages"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_social_remove_person"
|
android:id="@+id/action_social_remove_person"
|
||||||
android:icon="@drawable/action_delete_white"
|
android:icon="@drawable/action_delete_white"
|
||||||
android:title="@string/delete_contact"
|
android:title="@string/delete_contact"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -165,6 +165,7 @@
|
|||||||
<string name="set_contact_alias">Change contact name</string>
|
<string name="set_contact_alias">Change contact name</string>
|
||||||
<string name="set_contact_alias_hint">Contact name</string>
|
<string name="set_contact_alias_hint">Contact name</string>
|
||||||
<string name="set_alias_button">Change</string>
|
<string name="set_alias_button">Change</string>
|
||||||
|
<string name="menu_item_disappearing_messages">Disappearing messages</string>
|
||||||
<string name="delete_all_messages">Delete all messages</string>
|
<string name="delete_all_messages">Delete all messages</string>
|
||||||
<string name="dialog_title_delete_all_messages">Confirm Message Deletion</string>
|
<string name="dialog_title_delete_all_messages">Confirm Message Deletion</string>
|
||||||
<string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string>
|
<string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string>
|
||||||
@@ -545,6 +546,20 @@
|
|||||||
<string name="choose_ringtone_title">Choose ringtone</string>
|
<string name="choose_ringtone_title">Choose ringtone</string>
|
||||||
<string name="cannot_load_ringtone">Cannot load ringtone</string>
|
<string name="cannot_load_ringtone">Cannot load ringtone</string>
|
||||||
|
|
||||||
|
<!-- Conversation Settings -->
|
||||||
|
<string name="disappearing_messages_title">Disappearing messages</string>
|
||||||
|
<string name="disappearing_messages_cat_title">Self-destructing messages</string>
|
||||||
|
<string name="disappearing_messages_explanation_long">You can configure disappearing messages here.
|
||||||
|
Messages that will disappear are marked with a bomb icon.
|
||||||
|
\n\nTurning on this setting will make new messages in this conversation automatically disappear 7 days after being received.
|
||||||
|
This applies to messages you send to your contact as well as messages your contact sends to you.
|
||||||
|
Your contact can also change this setting for the both of you.
|
||||||
|
\n\nKeep in mind that recipients can still make analogue copies of the messages you sent.
|
||||||
|
\n\nIf you change this setting, it will apply to your messages immediately and to messages of
|
||||||
|
your contact once they receive any message from you after changing the setting.</string>
|
||||||
|
<string name="learn_more">Learn more about disappearing messages</string>
|
||||||
|
<string name="disappearing_messages_summary">Make future messages in this conversation automatically disappear 7\u00A0days after being received.</string>
|
||||||
|
|
||||||
<!-- Settings Feedback -->
|
<!-- Settings Feedback -->
|
||||||
<string name="feedback_settings_title">Feedback</string>
|
<string name="feedback_settings_title">Feedback</string>
|
||||||
<string name="send_feedback">Send feedback</string>
|
<string name="send_feedback">Send feedback</string>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -15,9 +15,9 @@ public class BlogInvitationRequest extends InvitationRequest<Blog> {
|
|||||||
public BlogInvitationRequest(MessageId id, GroupId groupId, long time,
|
public BlogInvitationRequest(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, Blog blog, @Nullable String text,
|
SessionId sessionId, Blog blog, @Nullable String text,
|
||||||
boolean available, boolean canBeOpened) {
|
boolean available, boolean canBeOpened, long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId, blog,
|
super(id, groupId, time, local, read, sent, seen, sessionId, blog,
|
||||||
text, available, canBeOpened);
|
text, available, canBeOpened, autoDeleteTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ public class BlogInvitationResponse extends InvitationResponse {
|
|||||||
|
|
||||||
public BlogInvitationResponse(MessageId id, GroupId groupId, long time,
|
public BlogInvitationResponse(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, boolean accept, GroupId shareableId) {
|
SessionId sessionId, boolean accept, GroupId shareableId,
|
||||||
|
long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId,
|
super(id, groupId, time, local, read, sent, seen, sessionId,
|
||||||
accept, shareableId);
|
accept, shareableId, autoDeleteTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ public interface BlogSharingManager extends SharingManager<Blog> {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the blog sharing client.
|
* The current minor version of the blog sharing client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 0;
|
int MINOR_VERSION = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,18 @@ public interface ConversationManager {
|
|||||||
*/
|
*/
|
||||||
GroupCount getGroupCount(ContactId c) throws DbException;
|
GroupCount getGroupCount(ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the unified group count for all private conversation messages.
|
||||||
|
*/
|
||||||
|
GroupCount getGroupCount(Transaction txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a timestamp for an outgoing message, which is later than the
|
||||||
|
* timestamp of any message in the conversation with the given contact.
|
||||||
|
*/
|
||||||
|
long getTimestampForOutgoingMessage(Transaction txn, ContactId c)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all messages exchanged with the given contact.
|
* Deletes all messages exchanged with the given contact.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,18 +12,20 @@ public abstract class ConversationMessageHeader {
|
|||||||
|
|
||||||
private final MessageId id;
|
private final MessageId id;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final long timestamp;
|
private final long timestamp, autoDeleteTimer;
|
||||||
private final boolean local, sent, seen, read;
|
private final boolean local, read, sent, seen;
|
||||||
|
|
||||||
public ConversationMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
public ConversationMessageHeader(MessageId id, GroupId groupId,
|
||||||
boolean local, boolean read, boolean sent, boolean seen) {
|
long timestamp, boolean local, boolean read, boolean sent,
|
||||||
|
boolean seen, long autoDeleteTimer) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.local = local;
|
this.local = local;
|
||||||
|
this.read = read;
|
||||||
this.sent = sent;
|
this.sent = sent;
|
||||||
this.seen = seen;
|
this.seen = seen;
|
||||||
this.read = read;
|
this.autoDeleteTimer = autoDeleteTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageId getId() {
|
public MessageId getId() {
|
||||||
@@ -55,4 +57,8 @@ public abstract class ConversationMessageHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract <T> T accept(ConversationMessageVisitor<T> v);
|
public abstract <T> T accept(ConversationMessageVisitor<T> v);
|
||||||
|
|
||||||
|
public long getAutoDeleteTimer() {
|
||||||
|
return autoDeleteTimer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ public abstract class ConversationRequest<N extends Nameable>
|
|||||||
private final String text;
|
private final String text;
|
||||||
private final boolean answered;
|
private final boolean answered;
|
||||||
|
|
||||||
public ConversationRequest(MessageId messageId, GroupId groupId, long time,
|
public ConversationRequest(MessageId messageId, GroupId groupId,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
long timestamp, boolean local, boolean read, boolean sent,
|
||||||
SessionId sessionId, N nameable, @Nullable String text,
|
boolean seen, SessionId sessionId, N nameable,
|
||||||
boolean answered) {
|
@Nullable String text, boolean answered, long autoDeleteTimer) {
|
||||||
super(messageId, groupId, time, local, read, sent, seen);
|
super(messageId, groupId, timestamp, local, read, sent, seen,
|
||||||
|
autoDeleteTimer);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.nameable = nameable;
|
this.nameable = nameable;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ public abstract class ConversationResponse extends ConversationMessageHeader {
|
|||||||
|
|
||||||
public ConversationResponse(MessageId id, GroupId groupId, long time,
|
public ConversationResponse(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, boolean accepted) {
|
SessionId sessionId, boolean accepted, long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen);
|
super(id, groupId, time, local, read, sent, seen, autoDeleteTimer);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.accepted = accepted;
|
this.accepted = accepted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ public class ForumInvitationRequest extends InvitationRequest<Forum> {
|
|||||||
public ForumInvitationRequest(MessageId id, GroupId groupId, long time,
|
public ForumInvitationRequest(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, Forum forum, @Nullable String text,
|
SessionId sessionId, Forum forum, @Nullable String text,
|
||||||
boolean available, boolean canBeOpened) {
|
boolean available, boolean canBeOpened, long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId, forum,
|
super(id, groupId, time, local, read, sent, seen, sessionId, forum,
|
||||||
text, available, canBeOpened);
|
text, available, canBeOpened, autoDeleteTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ public class ForumInvitationResponse extends InvitationResponse {
|
|||||||
|
|
||||||
public ForumInvitationResponse(MessageId id, GroupId groupId, long time,
|
public ForumInvitationResponse(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, boolean accept, GroupId shareableId) {
|
SessionId sessionId, boolean accept, GroupId shareableId,
|
||||||
|
long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId,
|
super(id, groupId, time, local, read, sent, seen, sessionId,
|
||||||
accept, shareableId);
|
accept, shareableId, autoDeleteTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ public interface ForumSharingManager extends SharingManager<Forum> {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the forum sharing client.
|
* The current minor version of the forum sharing client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 0;
|
int MINOR_VERSION = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,18 +31,18 @@ public interface IntroductionManager extends ConversationClient {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the introduction client.
|
* The current minor version of the introduction client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 0;
|
int MINOR_VERSION = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends two initial introduction messages.
|
* Sends two initial introduction messages.
|
||||||
*/
|
*/
|
||||||
void makeIntroduction(Contact c1, Contact c2, @Nullable String text,
|
void makeIntroduction(Contact c1, Contact c2, @Nullable String text)
|
||||||
long timestamp) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responds to an introduction.
|
* Responds to an introduction.
|
||||||
*/
|
*/
|
||||||
void respondToIntroduction(ContactId contactId, SessionId sessionId,
|
void respondToIntroduction(ContactId contactId, SessionId sessionId,
|
||||||
long timestamp, boolean accept) throws DbException;
|
boolean accept) throws DbException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ public class IntroductionRequest extends ConversationRequest<Author> {
|
|||||||
|
|
||||||
private final AuthorInfo authorInfo;
|
private final AuthorInfo authorInfo;
|
||||||
|
|
||||||
public IntroductionRequest(MessageId messageId, GroupId groupId,
|
public IntroductionRequest(MessageId messageId, GroupId groupId, long time,
|
||||||
long time, boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, Author author, @Nullable String text,
|
SessionId sessionId, Author author, @Nullable String text,
|
||||||
boolean answered, AuthorInfo authorInfo) {
|
boolean answered, AuthorInfo authorInfo, long autoDeleteTimer) {
|
||||||
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
||||||
author, text, answered);
|
author, text, answered, autoDeleteTimer);
|
||||||
this.authorInfo = authorInfo;
|
this.authorInfo = authorInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ public class IntroductionResponse extends ConversationResponse {
|
|||||||
public IntroductionResponse(MessageId messageId, GroupId groupId, long time,
|
public IntroductionResponse(MessageId messageId, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, boolean accepted, Author author,
|
SessionId sessionId, boolean accepted, Author author,
|
||||||
AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed) {
|
AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed,
|
||||||
|
long autoDeleteTimer) {
|
||||||
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
||||||
accepted);
|
accepted, autoDeleteTimer);
|
||||||
this.introducedAuthor = author;
|
this.introducedAuthor = author;
|
||||||
this.introducedAuthorInfo = introducedAuthorInfo;
|
this.introducedAuthorInfo = introducedAuthorInfo;
|
||||||
this.ourRole = role;
|
this.ourRole = role;
|
||||||
|
|||||||
@@ -30,13 +30,18 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the messaging client.
|
* The current minor version of the messaging client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 2;
|
int MINOR_VERSION = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a local private message.
|
* Stores a local private message.
|
||||||
*/
|
*/
|
||||||
void addLocalMessage(PrivateMessage m) throws DbException;
|
void addLocalMessage(PrivateMessage m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a local private message.
|
||||||
|
*/
|
||||||
|
void addLocalMessage(Transaction txn, PrivateMessage m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a local attachment message.
|
* Stores a local attachment message.
|
||||||
*
|
*
|
||||||
@@ -77,12 +82,8 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
Attachment getAttachment(AttachmentHeader h) throws DbException;
|
Attachment getAttachment(AttachmentHeader h) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the contact with the given {@link ContactId} does support
|
* Returns the private message format supported by the given contact.
|
||||||
* image attachments.
|
|
||||||
*
|
|
||||||
* Added: 2019-01-01
|
|
||||||
*/
|
*/
|
||||||
boolean contactSupportsImages(Transaction txn, ContactId c)
|
PrivateMessageFormat getContactMessageFormat(Transaction txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,44 +8,66 @@ import java.util.List;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class PrivateMessage {
|
public class PrivateMessage {
|
||||||
|
|
||||||
private final Message message;
|
private final Message message;
|
||||||
private final boolean legacyFormat, hasText;
|
private final boolean hasText;
|
||||||
private final List<AttachmentHeader> attachmentHeaders;
|
private final List<AttachmentHeader> attachmentHeaders;
|
||||||
|
private final long autoDeleteTimer;
|
||||||
|
private final PrivateMessageFormat format;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for private messages in the legacy format, which does not
|
* Constructor for private messages in the
|
||||||
* support attachments.
|
* {@link PrivateMessageFormat#TEXT_ONLY TEXT_ONLY} format.
|
||||||
*/
|
*/
|
||||||
public PrivateMessage(Message message) {
|
public PrivateMessage(Message message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
legacyFormat = true;
|
|
||||||
hasText = true;
|
hasText = true;
|
||||||
attachmentHeaders = emptyList();
|
attachmentHeaders = emptyList();
|
||||||
|
autoDeleteTimer = NO_AUTO_DELETE_TIMER;
|
||||||
|
format = TEXT_ONLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for private messages in the current format, which supports
|
* Constructor for private messages in the
|
||||||
* attachments.
|
* {@link PrivateMessageFormat#TEXT_IMAGES TEXT_IMAGES} format.
|
||||||
*/
|
*/
|
||||||
public PrivateMessage(Message message, boolean hasText,
|
public PrivateMessage(Message message, boolean hasText,
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentHeader> headers) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.hasText = hasText;
|
this.hasText = hasText;
|
||||||
this.attachmentHeaders = headers;
|
this.attachmentHeaders = headers;
|
||||||
legacyFormat = false;
|
autoDeleteTimer = NO_AUTO_DELETE_TIMER;
|
||||||
|
format = TEXT_IMAGES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for private messages in the
|
||||||
|
* {@link PrivateMessageFormat#TEXT_IMAGES_AUTO_DELETE TEXT_IMAGES_AUTO_DELETE}
|
||||||
|
* format.
|
||||||
|
*/
|
||||||
|
public PrivateMessage(Message message, boolean hasText,
|
||||||
|
List<AttachmentHeader> headers, long autoDeleteTimer) {
|
||||||
|
this.message = message;
|
||||||
|
this.hasText = hasText;
|
||||||
|
this.attachmentHeaders = headers;
|
||||||
|
this.autoDeleteTimer = autoDeleteTimer;
|
||||||
|
format = TEXT_IMAGES_AUTO_DELETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message getMessage() {
|
public Message getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLegacyFormat() {
|
public PrivateMessageFormat getFormat() {
|
||||||
return legacyFormat;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasText() {
|
public boolean hasText() {
|
||||||
@@ -55,4 +77,8 @@ public class PrivateMessage {
|
|||||||
public List<AttachmentHeader> getAttachmentHeaders() {
|
public List<AttachmentHeader> getAttachmentHeaders() {
|
||||||
return attachmentHeaders;
|
return attachmentHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getAutoDeleteTimer() {
|
||||||
|
return autoDeleteTimer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,29 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface PrivateMessageFactory {
|
public interface PrivateMessageFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a private message in the
|
||||||
|
* {@link PrivateMessageFormat#TEXT_ONLY TEXT_ONLY} format.
|
||||||
|
*/
|
||||||
PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp,
|
PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp,
|
||||||
String text) throws FormatException;
|
String text) throws FormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a private message in the
|
||||||
|
* {@link PrivateMessageFormat#TEXT_IMAGES TEXT_IMAGES} format. This format
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
@Nullable String text, List<AttachmentHeader> headers)
|
@Nullable String text, List<AttachmentHeader> headers)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a private message in the
|
||||||
|
* {@link PrivateMessageFormat#TEXT_IMAGES_AUTO_DELETE TEXT_IMAGES_AUTO_DELETE}
|
||||||
|
* format. This format requires the contact to support client version 0.3
|
||||||
|
* or higher.
|
||||||
|
*/
|
||||||
|
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
|
@Nullable String text, List<AttachmentHeader> headers,
|
||||||
|
long autoDeleteTimer) throws FormatException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -19,8 +19,9 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
|
|||||||
|
|
||||||
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
boolean hasText, List<AttachmentHeader> headers) {
|
boolean hasText, List<AttachmentHeader> headers,
|
||||||
super(id, groupId, timestamp, local, read, sent, seen);
|
long autoDeleteTimer) {
|
||||||
|
super(id, groupId, timestamp, local, read, sent, seen, autoDeleteTimer);
|
||||||
this.hasText = hasText;
|
this.hasText = hasText;
|
||||||
this.attachmentHeaders = headers;
|
this.attachmentHeaders = headers;
|
||||||
}
|
}
|
||||||
@@ -37,5 +38,4 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
|
|||||||
public <T> T accept(ConversationMessageVisitor<T> v) {
|
public <T> T accept(ConversationMessageVisitor<T> v) {
|
||||||
return v.visitPrivateMessageHeader(this);
|
return v.visitPrivateMessageHeader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public interface GroupInvitationManager extends ConversationClient {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the private group invitation client.
|
* The current minor version of the private group invitation client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 0;
|
int MINOR_VERSION = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an invitation to share the given private group with the given
|
* Sends an invitation to share the given private group with the given
|
||||||
@@ -43,7 +43,8 @@ public interface GroupInvitationManager extends ConversationClient {
|
|||||||
* pending.
|
* pending.
|
||||||
*/
|
*/
|
||||||
void sendInvitation(GroupId g, ContactId c, @Nullable String text,
|
void sendInvitation(GroupId g, ContactId c, @Nullable String text,
|
||||||
long timestamp, byte[] signature) throws DbException;
|
long timestamp, byte[] signature, long autoDeleteTimer)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responds to a pending private group invitation from the given contact.
|
* Responds to a pending private group invitation from the given contact.
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ public class GroupInvitationRequest extends InvitationRequest<PrivateGroup> {
|
|||||||
public GroupInvitationRequest(MessageId id, GroupId groupId, long time,
|
public GroupInvitationRequest(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, PrivateGroup shareable,
|
SessionId sessionId, PrivateGroup shareable,
|
||||||
@Nullable String text, boolean available, boolean canBeOpened) {
|
@Nullable String text, boolean available, boolean canBeOpened,
|
||||||
|
long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId, shareable,
|
super(id, groupId, time, local, read, sent, seen, sessionId, shareable,
|
||||||
text, available, canBeOpened);
|
text, available, canBeOpened, autoDeleteTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ public class GroupInvitationResponse extends InvitationResponse {
|
|||||||
|
|
||||||
public GroupInvitationResponse(MessageId id, GroupId groupId, long time,
|
public GroupInvitationResponse(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, boolean accept, GroupId shareableId) {
|
SessionId sessionId, boolean accept, GroupId shareableId,
|
||||||
|
long autoDeleteTimer) {
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId,
|
super(id, groupId, time, local, read, sent, seen, sessionId,
|
||||||
accept, shareableId);
|
accept, shareableId, autoDeleteTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public abstract class InvitationRequest<S extends Shareable> extends
|
|||||||
public InvitationRequest(MessageId messageId, GroupId groupId, long time,
|
public InvitationRequest(MessageId messageId, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, S object, @Nullable String text,
|
SessionId sessionId, S object, @Nullable String text,
|
||||||
boolean available, boolean canBeOpened) {
|
boolean available, boolean canBeOpened, long autoDeleteTimer) {
|
||||||
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
||||||
object, text, !available);
|
object, text, !available, autoDeleteTimer);
|
||||||
this.canBeOpened = canBeOpened;
|
this.canBeOpened = canBeOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ public abstract class InvitationResponse extends ConversationResponse {
|
|||||||
|
|
||||||
public InvitationResponse(MessageId id, GroupId groupId, long time,
|
public InvitationResponse(MessageId id, GroupId groupId, long time,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
SessionId sessionId, boolean accepted, GroupId shareableId) {
|
SessionId sessionId, boolean accepted, GroupId shareableId,
|
||||||
super(id, groupId, time, local, read, sent, seen, sessionId, accepted);
|
long autoDeleteTimer) {
|
||||||
|
super(id, groupId, time, local, read, sent, seen, sessionId, accepted,
|
||||||
|
autoDeleteTimer);
|
||||||
this.shareableId = shareableId;
|
this.shareableId = shareableId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public interface SharingManager<S extends Shareable>
|
|||||||
* including optional text.
|
* including optional text.
|
||||||
*/
|
*/
|
||||||
void sendInvitation(GroupId shareableId, ContactId contactId,
|
void sendInvitation(GroupId shareableId, ContactId contactId,
|
||||||
@Nullable String text, long timestamp) throws DbException;
|
@Nullable String text) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responds to a pending group invitation
|
* Responds to a pending group invitation
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar;
|
package org.briarproject.briar;
|
||||||
|
|
||||||
|
import org.briarproject.briar.autodelete.AutoDeleteModule;
|
||||||
import org.briarproject.briar.blog.BlogModule;
|
import org.briarproject.briar.blog.BlogModule;
|
||||||
import org.briarproject.briar.feed.FeedModule;
|
import org.briarproject.briar.feed.FeedModule;
|
||||||
import org.briarproject.briar.forum.ForumModule;
|
import org.briarproject.briar.forum.ForumModule;
|
||||||
@@ -11,6 +12,8 @@ import org.briarproject.briar.sharing.SharingModule;
|
|||||||
|
|
||||||
public interface BriarCoreEagerSingletons {
|
public interface BriarCoreEagerSingletons {
|
||||||
|
|
||||||
|
void inject(AutoDeleteModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(BlogModule.EagerSingletons init);
|
void inject(BlogModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(FeedModule.EagerSingletons init);
|
void inject(FeedModule.EagerSingletons init);
|
||||||
@@ -30,6 +33,7 @@ public interface BriarCoreEagerSingletons {
|
|||||||
class Helper {
|
class Helper {
|
||||||
|
|
||||||
public static void injectEagerSingletons(BriarCoreEagerSingletons c) {
|
public static void injectEagerSingletons(BriarCoreEagerSingletons c) {
|
||||||
|
c.inject(new AutoDeleteModule.EagerSingletons());
|
||||||
c.inject(new BlogModule.EagerSingletons());
|
c.inject(new BlogModule.EagerSingletons());
|
||||||
c.inject(new FeedModule.EagerSingletons());
|
c.inject(new FeedModule.EagerSingletons());
|
||||||
c.inject(new ForumModule.EagerSingletons());
|
c.inject(new ForumModule.EagerSingletons());
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar;
|
package org.briarproject.briar;
|
||||||
|
|
||||||
|
import org.briarproject.briar.autodelete.AutoDeleteModule;
|
||||||
import org.briarproject.briar.blog.BlogModule;
|
import org.briarproject.briar.blog.BlogModule;
|
||||||
import org.briarproject.briar.client.BriarClientModule;
|
import org.briarproject.briar.client.BriarClientModule;
|
||||||
import org.briarproject.briar.feed.DnsModule;
|
import org.briarproject.briar.feed.DnsModule;
|
||||||
@@ -15,6 +16,7 @@ import org.briarproject.briar.test.TestModule;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
|
AutoDeleteModule.class,
|
||||||
BlogModule.class,
|
BlogModule.class,
|
||||||
BriarClientModule.class,
|
BriarClientModule.class,
|
||||||
FeedModule.class,
|
FeedModule.class,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import org.briarproject.briar.api.client.SessionId;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AbortMessage extends AbstractIntroductionMessage {
|
class AbortMessage extends AbstractIntroductionMessage {
|
||||||
@@ -16,7 +18,8 @@ class AbortMessage extends AbstractIntroductionMessage {
|
|||||||
|
|
||||||
protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp,
|
protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId) {
|
@Nullable MessageId previousMessageId, SessionId sessionId) {
|
||||||
super(messageId, groupId, timestamp, previousMessageId);
|
super(messageId, groupId, timestamp, previousMessageId,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,16 @@ abstract class AbstractIntroductionMessage {
|
|||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final MessageId previousMessageId;
|
private final MessageId previousMessageId;
|
||||||
|
private final long autoDeleteTimer;
|
||||||
|
|
||||||
AbstractIntroductionMessage(MessageId messageId, GroupId groupId,
|
AbstractIntroductionMessage(MessageId messageId, GroupId groupId,
|
||||||
long timestamp, @Nullable MessageId previousMessageId) {
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
|
long autoDeleteTimer) {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.previousMessageId = previousMessageId;
|
this.previousMessageId = previousMessageId;
|
||||||
|
this.autoDeleteTimer = autoDeleteTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageId getMessageId() {
|
MessageId getMessageId() {
|
||||||
@@ -42,4 +45,7 @@ abstract class AbstractIntroductionMessage {
|
|||||||
return previousMessageId;
|
return previousMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getAutoDeleteTimer() {
|
||||||
|
return autoDeleteTimer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
|
|||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
@@ -17,11 +18,15 @@ import org.briarproject.bramble.api.identity.IdentityManager;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.client.SessionId;
|
import org.briarproject.briar.api.client.SessionId;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||||
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
||||||
|
|
||||||
@@ -30,6 +35,9 @@ import java.util.Map;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||||
|
import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||||
@@ -39,7 +47,7 @@ import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class AbstractProtocolEngine<S extends Session>
|
abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||||
implements ProtocolEngine<S> {
|
implements ProtocolEngine<S> {
|
||||||
|
|
||||||
protected final DatabaseComponent db;
|
protected final DatabaseComponent db;
|
||||||
@@ -50,6 +58,9 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
protected final IdentityManager identityManager;
|
protected final IdentityManager identityManager;
|
||||||
protected final MessageParser messageParser;
|
protected final MessageParser messageParser;
|
||||||
protected final MessageEncoder messageEncoder;
|
protected final MessageEncoder messageEncoder;
|
||||||
|
protected final ClientVersioningManager clientVersioningManager;
|
||||||
|
protected final AutoDeleteManager autoDeleteManager;
|
||||||
|
protected final ConversationManager conversationManager;
|
||||||
protected final Clock clock;
|
protected final Clock clock;
|
||||||
|
|
||||||
AbstractProtocolEngine(
|
AbstractProtocolEngine(
|
||||||
@@ -61,6 +72,9 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
IdentityManager identityManager,
|
IdentityManager identityManager,
|
||||||
MessageParser messageParser,
|
MessageParser messageParser,
|
||||||
MessageEncoder messageEncoder,
|
MessageEncoder messageEncoder,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
@@ -70,16 +84,29 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
this.messageParser = messageParser;
|
this.messageParser = messageParser;
|
||||||
this.messageEncoder = messageEncoder;
|
this.messageEncoder = messageEncoder;
|
||||||
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
|
this.autoDeleteManager = autoDeleteManager;
|
||||||
|
this.conversationManager = conversationManager;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message sendRequestMessage(Transaction txn, PeerSession s,
|
Message sendRequestMessage(Transaction txn, PeerSession s,
|
||||||
long timestamp, Author author, @Nullable String text)
|
long timestamp, Author author, @Nullable String text)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Message m = messageEncoder
|
Message m;
|
||||||
.encodeRequestMessage(s.getContactGroupId(), timestamp,
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
s.getLastLocalMessageId(), author, text);
|
if (contactSupportsAutoDeletion(txn, c)) {
|
||||||
sendMessage(txn, REQUEST, s.getSessionId(), m, true);
|
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
|
||||||
|
timestamp);
|
||||||
|
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
|
||||||
|
timestamp, s.getLastLocalMessageId(), author, text, timer);
|
||||||
|
sendMessage(txn, REQUEST, s.getSessionId(), m, true, timer);
|
||||||
|
} else {
|
||||||
|
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
|
||||||
|
timestamp, s.getLastLocalMessageId(), author, text);
|
||||||
|
sendMessage(txn, REQUEST, s.getSessionId(), m, true,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,21 +114,43 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
PublicKey ephemeralPublicKey, long acceptTimestamp,
|
PublicKey ephemeralPublicKey, long acceptTimestamp,
|
||||||
Map<TransportId, TransportProperties> transportProperties,
|
Map<TransportId, TransportProperties> transportProperties,
|
||||||
boolean visible) throws DbException {
|
boolean visible) throws DbException {
|
||||||
Message m = messageEncoder
|
Message m;
|
||||||
.encodeAcceptMessage(s.getContactGroupId(), timestamp,
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
s.getLastLocalMessageId(), s.getSessionId(),
|
if (contactSupportsAutoDeletion(txn, c)) {
|
||||||
ephemeralPublicKey, acceptTimestamp,
|
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
|
||||||
transportProperties);
|
timestamp);
|
||||||
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible);
|
m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(),
|
||||||
|
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
|
||||||
|
ephemeralPublicKey, acceptTimestamp, transportProperties,
|
||||||
|
timer);
|
||||||
|
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible, timer);
|
||||||
|
} else {
|
||||||
|
m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(),
|
||||||
|
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
|
||||||
|
ephemeralPublicKey, acceptTimestamp, transportProperties);
|
||||||
|
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp,
|
Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp,
|
||||||
boolean visible) throws DbException {
|
boolean visible) throws DbException {
|
||||||
Message m = messageEncoder
|
Message m;
|
||||||
.encodeDeclineMessage(s.getContactGroupId(), timestamp,
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
s.getLastLocalMessageId(), s.getSessionId());
|
if (contactSupportsAutoDeletion(txn, c)) {
|
||||||
sendMessage(txn, DECLINE, s.getSessionId(), m, visible);
|
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
|
||||||
|
timestamp);
|
||||||
|
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
|
||||||
|
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
|
||||||
|
timer);
|
||||||
|
sendMessage(txn, DECLINE, s.getSessionId(), m, visible, timer);
|
||||||
|
} else {
|
||||||
|
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
|
||||||
|
timestamp, s.getLastLocalMessageId(), s.getSessionId());
|
||||||
|
sendMessage(txn, DECLINE, s.getSessionId(), m, visible,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +160,8 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
.encodeAuthMessage(s.getContactGroupId(), timestamp,
|
.encodeAuthMessage(s.getContactGroupId(), timestamp,
|
||||||
s.getLastLocalMessageId(), s.getSessionId(), mac,
|
s.getLastLocalMessageId(), s.getSessionId(), mac,
|
||||||
signature);
|
signature);
|
||||||
sendMessage(txn, AUTH, s.getSessionId(), m, false);
|
sendMessage(txn, AUTH, s.getSessionId(), m, false,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +170,8 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
Message m = messageEncoder
|
Message m = messageEncoder
|
||||||
.encodeActivateMessage(s.getContactGroupId(), timestamp,
|
.encodeActivateMessage(s.getContactGroupId(), timestamp,
|
||||||
s.getLastLocalMessageId(), s.getSessionId(), mac);
|
s.getLastLocalMessageId(), s.getSessionId(), mac);
|
||||||
sendMessage(txn, ACTIVATE, s.getSessionId(), m, false);
|
sendMessage(txn, ACTIVATE, s.getSessionId(), m, false,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,16 +180,17 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
Message m = messageEncoder
|
Message m = messageEncoder
|
||||||
.encodeAbortMessage(s.getContactGroupId(), timestamp,
|
.encodeAbortMessage(s.getContactGroupId(), timestamp,
|
||||||
s.getLastLocalMessageId(), s.getSessionId());
|
s.getLastLocalMessageId(), s.getSessionId());
|
||||||
sendMessage(txn, ABORT, s.getSessionId(), m, false);
|
sendMessage(txn, ABORT, s.getSessionId(), m, false,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(Transaction txn, MessageType type,
|
private void sendMessage(Transaction txn, MessageType type,
|
||||||
SessionId sessionId, Message m, boolean visibleInConversation)
|
SessionId sessionId, Message m, boolean visibleInConversation,
|
||||||
throws DbException {
|
long autoDeleteTimer) throws DbException {
|
||||||
BdfDictionary meta = messageEncoder
|
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
||||||
.encodeMetadata(type, sessionId, m.getTimestamp(), true, true,
|
m.getTimestamp(), true, true, visibleInConversation,
|
||||||
visibleInConversation);
|
autoDeleteTimer);
|
||||||
try {
|
try {
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
@@ -146,9 +198,10 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void broadcastIntroductionResponseReceivedEvent(Transaction txn, Session s,
|
void broadcastIntroductionResponseReceivedEvent(Transaction txn,
|
||||||
AuthorId sender, Author otherAuthor, AbstractIntroductionMessage m,
|
Session<?> s, AuthorId sender, Author otherAuthor,
|
||||||
boolean canSucceed) throws DbException {
|
AbstractIntroductionMessage m, boolean canSucceed)
|
||||||
|
throws DbException {
|
||||||
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
|
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
|
||||||
Contact c = contactManager.getContact(txn, sender, localAuthorId);
|
Contact c = contactManager.getContact(txn, sender, localAuthorId);
|
||||||
AuthorInfo otherAuthorInfo =
|
AuthorInfo otherAuthorInfo =
|
||||||
@@ -157,7 +210,8 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
new IntroductionResponse(m.getMessageId(), m.getGroupId(),
|
new IntroductionResponse(m.getMessageId(), m.getGroupId(),
|
||||||
m.getTimestamp(), false, false, false, false,
|
m.getTimestamp(), false, false, false, false,
|
||||||
s.getSessionId(), m instanceof AcceptMessage,
|
s.getSessionId(), m instanceof AcceptMessage,
|
||||||
otherAuthor, otherAuthorInfo, s.getRole(), canSucceed);
|
otherAuthor, otherAuthorInfo, s.getRole(), canSucceed,
|
||||||
|
m.getAutoDeleteTimer());
|
||||||
IntroductionResponseReceivedEvent e =
|
IntroductionResponseReceivedEvent e =
|
||||||
new IntroductionResponseReceivedEvent(response, c.getId());
|
new IntroductionResponseReceivedEvent(response, c.getId());
|
||||||
txn.attach(e);
|
txn.attach(e);
|
||||||
@@ -180,14 +234,33 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
return !dependency.equals(lastRemoteMessageId);
|
return !dependency.equals(lastRemoteMessageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
long getLocalTimestamp(long localTimestamp, long requestTimestamp) {
|
long getTimestampForOutgoingMessage(Transaction txn, GroupId contactGroupId)
|
||||||
return Math.max(
|
throws DbException {
|
||||||
clock.currentTimeMillis(),
|
ContactId c = getContactId(txn, contactGroupId);
|
||||||
Math.max(
|
return conversationManager.getTimestampForOutgoingMessage(txn, c);
|
||||||
localTimestamp,
|
|
||||||
requestTimestamp
|
|
||||||
) + 1
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void receiveAutoDeleteTimer(Transaction txn, AbstractIntroductionMessage m)
|
||||||
|
throws DbException {
|
||||||
|
ContactId c = getContactId(txn, m.getGroupId());
|
||||||
|
autoDeleteManager.receiveAutoDeleteTimer(txn, c, m.getAutoDeleteTimer(),
|
||||||
|
m.getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
|
throws DbException {
|
||||||
|
try {
|
||||||
|
return clientHelper.getContactId(txn, contactGroupId);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contactSupportsAutoDeletion(Transaction txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
int minorVersion = clientVersioningManager.getClientMinorVersion(txn, c,
|
||||||
|
CLIENT_ID, MAJOR_VERSION);
|
||||||
|
// Auto-delete was added in client version 0.1
|
||||||
|
return minorVersion >= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ class AcceptMessage extends AbstractIntroductionMessage {
|
|||||||
long timestamp, @Nullable MessageId previousMessageId,
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
SessionId sessionId, PublicKey ephemeralPublicKey,
|
SessionId sessionId, PublicKey ephemeralPublicKey,
|
||||||
long acceptTimestamp,
|
long acceptTimestamp,
|
||||||
Map<TransportId, TransportProperties> transportProperties) {
|
Map<TransportId, TransportProperties> transportProperties,
|
||||||
super(messageId, groupId, timestamp, previousMessageId);
|
long autoDeleteTimer) {
|
||||||
|
super(messageId, groupId, timestamp, previousMessageId,
|
||||||
|
autoDeleteTimer);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.ephemeralPublicKey = ephemeralPublicKey;
|
this.ephemeralPublicKey = ephemeralPublicKey;
|
||||||
this.acceptTimestamp = acceptTimestamp;
|
this.acceptTimestamp = acceptTimestamp;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import org.briarproject.briar.api.client.SessionId;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ActivateMessage extends AbstractIntroductionMessage {
|
class ActivateMessage extends AbstractIntroductionMessage {
|
||||||
@@ -17,7 +19,8 @@ class ActivateMessage extends AbstractIntroductionMessage {
|
|||||||
protected ActivateMessage(MessageId messageId, GroupId groupId,
|
protected ActivateMessage(MessageId messageId, GroupId groupId,
|
||||||
long timestamp, MessageId previousMessageId, SessionId sessionId,
|
long timestamp, MessageId previousMessageId, SessionId sessionId,
|
||||||
byte[] mac) {
|
byte[] mac) {
|
||||||
super(messageId, groupId, timestamp, previousMessageId);
|
super(messageId, groupId, timestamp, previousMessageId,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.mac = mac;
|
this.mac = mac;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import org.briarproject.briar.api.client.SessionId;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AuthMessage extends AbstractIntroductionMessage {
|
class AuthMessage extends AbstractIntroductionMessage {
|
||||||
@@ -17,7 +19,8 @@ class AuthMessage extends AbstractIntroductionMessage {
|
|||||||
protected AuthMessage(MessageId messageId, GroupId groupId,
|
protected AuthMessage(MessageId messageId, GroupId groupId,
|
||||||
long timestamp, MessageId previousMessageId, SessionId sessionId,
|
long timestamp, MessageId previousMessageId, SessionId sessionId,
|
||||||
byte[] mac, byte[] signature) {
|
byte[] mac, byte[] signature) {
|
||||||
super(messageId, groupId, timestamp, previousMessageId);
|
super(messageId, groupId, timestamp, previousMessageId,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.mac = mac;
|
this.mac = mac;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ class DeclineMessage extends AbstractIntroductionMessage {
|
|||||||
|
|
||||||
protected DeclineMessage(MessageId messageId, GroupId groupId,
|
protected DeclineMessage(MessageId messageId, GroupId groupId,
|
||||||
long timestamp, @Nullable MessageId previousMessageId,
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
SessionId sessionId) {
|
SessionId sessionId, long autoDeleteTimer) {
|
||||||
super(messageId, groupId, timestamp, previousMessageId);
|
super(messageId, groupId, timestamp, previousMessageId,
|
||||||
|
autoDeleteTimer);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
import org.briarproject.briar.api.client.SessionId;
|
import org.briarproject.briar.api.client.SessionId;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||||
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
|
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
|
||||||
@@ -41,6 +44,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
|
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
|
||||||
@@ -73,13 +77,17 @@ class IntroduceeProtocolEngine
|
|||||||
IdentityManager identityManager,
|
IdentityManager identityManager,
|
||||||
MessageParser messageParser,
|
MessageParser messageParser,
|
||||||
MessageEncoder messageEncoder,
|
MessageEncoder messageEncoder,
|
||||||
Clock clock,
|
|
||||||
IntroductionCrypto crypto,
|
IntroductionCrypto crypto,
|
||||||
KeyManager keyManager,
|
KeyManager keyManager,
|
||||||
TransportPropertyManager transportPropertyManager) {
|
TransportPropertyManager transportPropertyManager,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
|
Clock clock) {
|
||||||
super(db, clientHelper, contactManager, contactGroupFactory,
|
super(db, clientHelper, contactManager, contactGroupFactory,
|
||||||
messageTracker, identityManager, messageParser, messageEncoder,
|
messageTracker, identityManager, messageParser, messageEncoder,
|
||||||
clock);
|
clientVersioningManager, autoDeleteManager,
|
||||||
|
conversationManager, clock);
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
this.transportPropertyManager = transportPropertyManager;
|
this.transportPropertyManager = transportPropertyManager;
|
||||||
@@ -87,18 +95,18 @@ class IntroduceeProtocolEngine
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IntroduceeSession onRequestAction(Transaction txn,
|
public IntroduceeSession onRequestAction(Transaction txn,
|
||||||
IntroduceeSession session, @Nullable String text, long timestamp) {
|
IntroduceeSession session, @Nullable String text) {
|
||||||
throw new UnsupportedOperationException(); // Invalid in this role
|
throw new UnsupportedOperationException(); // Invalid in this role
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IntroduceeSession onAcceptAction(Transaction txn,
|
public IntroduceeSession onAcceptAction(Transaction txn,
|
||||||
IntroduceeSession session, long timestamp) throws DbException {
|
IntroduceeSession session) throws DbException {
|
||||||
switch (session.getState()) {
|
switch (session.getState()) {
|
||||||
case AWAIT_RESPONSES:
|
case AWAIT_RESPONSES:
|
||||||
case REMOTE_DECLINED:
|
case REMOTE_DECLINED:
|
||||||
case REMOTE_ACCEPTED:
|
case REMOTE_ACCEPTED:
|
||||||
return onLocalAccept(txn, session, timestamp);
|
return onLocalAccept(txn, session);
|
||||||
case START:
|
case START:
|
||||||
case LOCAL_DECLINED:
|
case LOCAL_DECLINED:
|
||||||
case LOCAL_ACCEPTED:
|
case LOCAL_ACCEPTED:
|
||||||
@@ -112,12 +120,12 @@ class IntroduceeProtocolEngine
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IntroduceeSession onDeclineAction(Transaction txn,
|
public IntroduceeSession onDeclineAction(Transaction txn,
|
||||||
IntroduceeSession session, long timestamp) throws DbException {
|
IntroduceeSession session) throws DbException {
|
||||||
switch (session.getState()) {
|
switch (session.getState()) {
|
||||||
case AWAIT_RESPONSES:
|
case AWAIT_RESPONSES:
|
||||||
case REMOTE_DECLINED:
|
case REMOTE_DECLINED:
|
||||||
case REMOTE_ACCEPTED:
|
case REMOTE_ACCEPTED:
|
||||||
return onLocalDecline(txn, session, timestamp);
|
return onLocalDecline(txn, session);
|
||||||
case START:
|
case START:
|
||||||
case LOCAL_DECLINED:
|
case LOCAL_DECLINED:
|
||||||
case LOCAL_ACCEPTED:
|
case LOCAL_ACCEPTED:
|
||||||
@@ -249,6 +257,9 @@ class IntroduceeProtocolEngine
|
|||||||
messageTracker
|
messageTracker
|
||||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||||
|
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
|
|
||||||
// Broadcast IntroductionRequestReceivedEvent
|
// Broadcast IntroductionRequestReceivedEvent
|
||||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||||
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
|
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
|
||||||
@@ -258,7 +269,7 @@ class IntroduceeProtocolEngine
|
|||||||
IntroductionRequest request = new IntroductionRequest(m.getMessageId(),
|
IntroductionRequest request = new IntroductionRequest(m.getMessageId(),
|
||||||
m.getGroupId(), m.getTimestamp(), false, false, false, false,
|
m.getGroupId(), m.getTimestamp(), false, false, false, false,
|
||||||
s.getSessionId(), m.getAuthor(), m.getText(), false,
|
s.getSessionId(), m.getAuthor(), m.getText(), false,
|
||||||
authorInfo);
|
authorInfo, m.getAutoDeleteTimer());
|
||||||
IntroductionRequestReceivedEvent e =
|
IntroductionRequestReceivedEvent e =
|
||||||
new IntroductionRequestReceivedEvent(request, c.getId());
|
new IntroductionRequestReceivedEvent(request, c.getId());
|
||||||
txn.attach(e);
|
txn.attach(e);
|
||||||
@@ -268,7 +279,7 @@ class IntroduceeProtocolEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IntroduceeSession onLocalAccept(Transaction txn,
|
private IntroduceeSession onLocalAccept(Transaction txn,
|
||||||
IntroduceeSession s, long timestamp) throws DbException {
|
IntroduceeSession s) throws DbException {
|
||||||
// Mark the request message unavailable to answer
|
// Mark the request message unavailable to answer
|
||||||
markRequestsUnavailableToAnswer(txn, s);
|
markRequestsUnavailableToAnswer(txn, s);
|
||||||
|
|
||||||
@@ -279,8 +290,8 @@ class IntroduceeProtocolEngine
|
|||||||
Map<TransportId, TransportProperties> transportProperties =
|
Map<TransportId, TransportProperties> transportProperties =
|
||||||
transportPropertyManager.getLocalProperties(txn);
|
transportPropertyManager.getLocalProperties(txn);
|
||||||
|
|
||||||
// Send a ACCEPT message
|
// Send an ACCEPT message
|
||||||
long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s));
|
long localTimestamp = getTimestampForVisibleMessage(txn, s);
|
||||||
Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey,
|
Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey,
|
||||||
localTimestamp, transportProperties, true);
|
localTimestamp, transportProperties, true);
|
||||||
// Track the message
|
// Track the message
|
||||||
@@ -305,12 +316,12 @@ class IntroduceeProtocolEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IntroduceeSession onLocalDecline(Transaction txn,
|
private IntroduceeSession onLocalDecline(Transaction txn,
|
||||||
IntroduceeSession s, long timestamp) throws DbException {
|
IntroduceeSession s) throws DbException {
|
||||||
// Mark the request message unavailable to answer
|
// Mark the request message unavailable to answer
|
||||||
markRequestsUnavailableToAnswer(txn, s);
|
markRequestsUnavailableToAnswer(txn, s);
|
||||||
|
|
||||||
// Send a DECLINE message
|
// Send a DECLINE message
|
||||||
long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s));
|
long localTimestamp = getTimestampForVisibleMessage(txn, s);
|
||||||
Message sent = sendDeclineMessage(txn, s, localTimestamp, true);
|
Message sent = sendDeclineMessage(txn, s, localTimestamp, true);
|
||||||
|
|
||||||
// Track the message
|
// Track the message
|
||||||
@@ -324,8 +335,7 @@ class IntroduceeProtocolEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IntroduceeSession onRemoteAccept(Transaction txn,
|
private IntroduceeSession onRemoteAccept(Transaction txn,
|
||||||
IntroduceeSession s, AcceptMessage m)
|
IntroduceeSession s, AcceptMessage m) throws DbException {
|
||||||
throws DbException {
|
|
||||||
// The timestamp must be higher than the last request message
|
// The timestamp must be higher than the last request message
|
||||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||||
return abort(txn, s);
|
return abort(txn, s);
|
||||||
@@ -362,6 +372,9 @@ class IntroduceeProtocolEngine
|
|||||||
messageTracker
|
messageTracker
|
||||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||||
|
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
|
|
||||||
// Broadcast IntroductionResponseReceivedEvent
|
// Broadcast IntroductionResponseReceivedEvent
|
||||||
broadcastIntroductionResponseReceivedEvent(txn, s,
|
broadcastIntroductionResponseReceivedEvent(txn, s,
|
||||||
s.getIntroducer().getId(), s.getRemote().author, m, false);
|
s.getIntroducer().getId(), s.getRemote().author, m, false);
|
||||||
@@ -408,8 +421,8 @@ class IntroduceeProtocolEngine
|
|||||||
return abort(txn, s);
|
return abort(txn, s);
|
||||||
}
|
}
|
||||||
if (s.getState() != AWAIT_AUTH) throw new AssertionError();
|
if (s.getState() != AWAIT_AUTH) throw new AssertionError();
|
||||||
Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac,
|
long localTimestamp = getTimestampForInvisibleMessage(s);
|
||||||
signature);
|
Message sent = sendAuthMessage(txn, s, localTimestamp, mac, signature);
|
||||||
return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey,
|
return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey,
|
||||||
aliceMacKey, bobMacKey);
|
aliceMacKey, bobMacKey);
|
||||||
}
|
}
|
||||||
@@ -460,7 +473,8 @@ class IntroduceeProtocolEngine
|
|||||||
|
|
||||||
// send ACTIVATE message with a MAC
|
// send ACTIVATE message with a MAC
|
||||||
byte[] mac = crypto.activateMac(s);
|
byte[] mac = crypto.activateMac(s);
|
||||||
Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s), mac);
|
long localTimestamp = getTimestampForInvisibleMessage(s);
|
||||||
|
Message sent = sendActivateMessage(txn, s, localTimestamp, mac);
|
||||||
|
|
||||||
// Move to AWAIT_ACTIVATE state and clear key material from session
|
// Move to AWAIT_ACTIVATE state and clear key material from session
|
||||||
return IntroduceeSession.awaitActivate(s, m, sent, keys);
|
return IntroduceeSession.awaitActivate(s, m, sent, keys);
|
||||||
@@ -511,7 +525,8 @@ class IntroduceeProtocolEngine
|
|||||||
markRequestsUnavailableToAnswer(txn, s);
|
markRequestsUnavailableToAnswer(txn, s);
|
||||||
|
|
||||||
// Send an ABORT message
|
// Send an ABORT message
|
||||||
Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s));
|
long localTimestamp = getTimestampForInvisibleMessage(s);
|
||||||
|
Message sent = sendAbortMessage(txn, s, localTimestamp);
|
||||||
|
|
||||||
// Broadcast abort event for testing
|
// Broadcast abort event for testing
|
||||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||||
@@ -526,9 +541,34 @@ class IntroduceeProtocolEngine
|
|||||||
return isInvalidDependency(s.getLastRemoteMessageId(), dependency);
|
return isInvalidDependency(s.getLastRemoteMessageId(), dependency);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getLocalTimestamp(IntroduceeSession s) {
|
/**
|
||||||
return getLocalTimestamp(s.getLocalTimestamp(),
|
* Returns a timestamp for a visible outgoing message. The timestamp is
|
||||||
s.getRequestTimestamp());
|
* later than the timestamp of any message sent or received so far in the
|
||||||
|
* conversation, and later than the {@link
|
||||||
|
* #getSessionTimestamp(IntroduceeSession) session timestamp}.
|
||||||
|
*/
|
||||||
|
private long getTimestampForVisibleMessage(Transaction txn,
|
||||||
|
IntroduceeSession s) throws DbException {
|
||||||
|
long conversationTimestamp =
|
||||||
|
getTimestampForOutgoingMessage(txn, s.getContactGroupId());
|
||||||
|
return max(conversationTimestamp, getSessionTimestamp(s) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a timestamp for an invisible outgoing message. The timestamp is
|
||||||
|
* later than the {@link #getSessionTimestamp(IntroduceeSession) session
|
||||||
|
* timestamp}.
|
||||||
|
*/
|
||||||
|
private long getTimestampForInvisibleMessage(IntroduceeSession s) {
|
||||||
|
return max(clock.currentTimeMillis(), getSessionTimestamp(s) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest timestamp of any message sent so far in the session,
|
||||||
|
* and any request message received so far in the session.
|
||||||
|
*/
|
||||||
|
private long getSessionTimestamp(IntroduceeSession s) {
|
||||||
|
return max(s.getLocalTimestamp(), s.getRequestTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSessionId(Transaction txn, MessageId m, SessionId sessionId)
|
private void addSessionId(Transaction txn, MessageId m, SessionId sessionId)
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||||
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
||||||
|
|
||||||
@@ -22,6 +25,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
|
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
|
||||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
|
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
|
||||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
|
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
|
||||||
@@ -50,19 +54,23 @@ class IntroducerProtocolEngine
|
|||||||
IdentityManager identityManager,
|
IdentityManager identityManager,
|
||||||
MessageParser messageParser,
|
MessageParser messageParser,
|
||||||
MessageEncoder messageEncoder,
|
MessageEncoder messageEncoder,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
super(db, clientHelper, contactManager, contactGroupFactory,
|
super(db, clientHelper, contactManager, contactGroupFactory,
|
||||||
messageTracker, identityManager, messageParser, messageEncoder,
|
messageTracker, identityManager, messageParser, messageEncoder,
|
||||||
clock);
|
clientVersioningManager, autoDeleteManager,
|
||||||
|
conversationManager, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IntroducerSession onRequestAction(Transaction txn,
|
public IntroducerSession onRequestAction(Transaction txn,
|
||||||
IntroducerSession s, @Nullable String text, long timestamp)
|
IntroducerSession s, @Nullable String text)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
switch (s.getState()) {
|
switch (s.getState()) {
|
||||||
case START:
|
case START:
|
||||||
return onLocalRequest(txn, s, text, timestamp);
|
return onLocalRequest(txn, s, text);
|
||||||
case AWAIT_RESPONSES:
|
case AWAIT_RESPONSES:
|
||||||
case AWAIT_RESPONSE_A:
|
case AWAIT_RESPONSE_A:
|
||||||
case AWAIT_RESPONSE_B:
|
case AWAIT_RESPONSE_B:
|
||||||
@@ -82,37 +90,24 @@ class IntroducerProtocolEngine
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IntroducerSession onAcceptAction(Transaction txn,
|
public IntroducerSession onAcceptAction(Transaction txn,
|
||||||
IntroducerSession s, long timestamp) {
|
IntroducerSession s) {
|
||||||
throw new UnsupportedOperationException(); // Invalid in this role
|
throw new UnsupportedOperationException(); // Invalid in this role
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IntroducerSession onDeclineAction(Transaction txn,
|
public IntroducerSession onDeclineAction(Transaction txn,
|
||||||
IntroducerSession s, long timestamp) {
|
IntroducerSession s) {
|
||||||
throw new UnsupportedOperationException(); // Invalid in this role
|
throw new UnsupportedOperationException(); // Invalid in this role
|
||||||
}
|
}
|
||||||
|
|
||||||
IntroducerSession onIntroduceeRemoved(Transaction txn,
|
IntroducerSession onIntroduceeRemoved(Transaction txn,
|
||||||
Introducee remainingIntroducee, IntroducerSession session)
|
Introducee remainingIntroducee, IntroducerSession session)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
// abort session
|
// abort session with remaining introducee
|
||||||
IntroducerSession s = abort(txn, session);
|
IntroducerSession s = abort(txn, session, remainingIntroducee);
|
||||||
// reset information for introducee that was removed
|
|
||||||
Introducee introduceeA, introduceeB;
|
|
||||||
if (remainingIntroducee.author.equals(s.getIntroduceeA().author)) {
|
|
||||||
introduceeA = s.getIntroduceeA();
|
|
||||||
introduceeB =
|
|
||||||
new Introducee(s.getSessionId(), s.getIntroduceeB().groupId,
|
|
||||||
s.getIntroduceeB().author);
|
|
||||||
} else if (remainingIntroducee.author
|
|
||||||
.equals(s.getIntroduceeB().author)) {
|
|
||||||
introduceeA =
|
|
||||||
new Introducee(s.getSessionId(), s.getIntroduceeA().groupId,
|
|
||||||
s.getIntroduceeA().author);
|
|
||||||
introduceeB = s.getIntroduceeB();
|
|
||||||
} else throw new DbException();
|
|
||||||
return new IntroducerSession(s.getSessionId(), s.getState(),
|
return new IntroducerSession(s.getSessionId(), s.getState(),
|
||||||
s.getRequestTimestamp(), introduceeA, introduceeB);
|
s.getRequestTimestamp(), s.getIntroduceeA(),
|
||||||
|
s.getIntroduceeB());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -222,13 +217,13 @@ class IntroducerProtocolEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IntroducerSession onLocalRequest(Transaction txn,
|
private IntroducerSession onLocalRequest(Transaction txn,
|
||||||
IntroducerSession s, @Nullable String text, long timestamp)
|
IntroducerSession s, @Nullable String text) throws DbException {
|
||||||
throws DbException {
|
|
||||||
// Send REQUEST messages
|
// Send REQUEST messages
|
||||||
long maxIntroduceeTimestamp =
|
long timestampA =
|
||||||
Math.max(getLocalTimestamp(s, s.getIntroduceeA()),
|
getTimestampForVisibleMessage(txn, s, s.getIntroduceeA());
|
||||||
getLocalTimestamp(s, s.getIntroduceeB()));
|
long timestampB =
|
||||||
long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp);
|
getTimestampForVisibleMessage(txn, s, s.getIntroduceeB());
|
||||||
|
long localTimestamp = max(timestampA, timestampB);
|
||||||
Message sentA = sendRequestMessage(txn, s.getIntroduceeA(),
|
Message sentA = sendRequestMessage(txn, s.getIntroduceeA(),
|
||||||
localTimestamp, s.getIntroduceeB().author, text);
|
localTimestamp, s.getIntroduceeB().author, text);
|
||||||
Message sentB = sendRequestMessage(txn, s.getIntroduceeB(),
|
Message sentB = sendRequestMessage(txn, s.getIntroduceeB(),
|
||||||
@@ -265,14 +260,16 @@ class IntroducerProtocolEngine
|
|||||||
// Track the incoming message
|
// Track the incoming message
|
||||||
messageTracker
|
messageTracker
|
||||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
|
|
||||||
// Forward ACCEPT message
|
// Forward ACCEPT message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
long timestamp = getLocalTimestamp(s, i);
|
// The forwarded message will not be visible to the introducee
|
||||||
Message sent =
|
long localTimestamp = getTimestampForInvisibleMessage(s, i);
|
||||||
sendAcceptMessage(txn, i, timestamp, m.getEphemeralPublicKey(),
|
Message sent = sendAcceptMessage(txn, i, localTimestamp,
|
||||||
m.getAcceptTimestamp(), m.getTransportProperties(),
|
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
|
||||||
false);
|
m.getTransportProperties(), false);
|
||||||
|
|
||||||
// Create the next state
|
// Create the next state
|
||||||
IntroducerState state = AWAIT_AUTHS;
|
IntroducerState state = AWAIT_AUTHS;
|
||||||
@@ -326,10 +323,14 @@ class IntroducerProtocolEngine
|
|||||||
// Track the incoming message
|
// Track the incoming message
|
||||||
messageTracker
|
messageTracker
|
||||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
|
|
||||||
// Forward ACCEPT message
|
// Forward ACCEPT message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
Message sent = sendAcceptMessage(txn, i, getLocalTimestamp(s, i),
|
// The forwarded message will not be visible to the introducee
|
||||||
|
long localTimestamp = getTimestampForInvisibleMessage(s, i);
|
||||||
|
Message sent = sendAcceptMessage(txn, i, localTimestamp,
|
||||||
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
|
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
|
||||||
m.getTransportProperties(), false);
|
m.getTransportProperties(), false);
|
||||||
|
|
||||||
@@ -377,11 +378,14 @@ class IntroducerProtocolEngine
|
|||||||
// Track the incoming message
|
// Track the incoming message
|
||||||
messageTracker
|
messageTracker
|
||||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
|
|
||||||
// Forward DECLINE message
|
// Forward DECLINE message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
long timestamp = getLocalTimestamp(s, i);
|
// The forwarded message will be visible to the introducee
|
||||||
Message sent = sendDeclineMessage(txn, i, timestamp, false);
|
long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
|
||||||
|
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
|
||||||
|
|
||||||
// Create the next state
|
// Create the next state
|
||||||
IntroducerState state = START;
|
IntroducerState state = START;
|
||||||
@@ -429,11 +433,14 @@ class IntroducerProtocolEngine
|
|||||||
// Track the incoming message
|
// Track the incoming message
|
||||||
messageTracker
|
messageTracker
|
||||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
|
|
||||||
// Forward DECLINE message
|
// Forward DECLINE message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
long timestamp = getLocalTimestamp(s, i);
|
// The forwarded message will be visible to the introducee
|
||||||
Message sent = sendDeclineMessage(txn, i, timestamp, false);
|
long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
|
||||||
|
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
|
||||||
|
|
||||||
Introducee introduceeA, introduceeB;
|
Introducee introduceeA, introduceeB;
|
||||||
Author sender, other;
|
Author sender, other;
|
||||||
@@ -473,8 +480,8 @@ class IntroducerProtocolEngine
|
|||||||
|
|
||||||
// Forward AUTH message
|
// Forward AUTH message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
long timestamp = getLocalTimestamp(s, i);
|
long localTimestamp = getTimestampForInvisibleMessage(s, i);
|
||||||
Message sent = sendAuthMessage(txn, i, timestamp, m.getMac(),
|
Message sent = sendAuthMessage(txn, i, localTimestamp, m.getMac(),
|
||||||
m.getSignature());
|
m.getSignature());
|
||||||
|
|
||||||
// Move to the next state
|
// Move to the next state
|
||||||
@@ -509,8 +516,8 @@ class IntroducerProtocolEngine
|
|||||||
|
|
||||||
// Forward ACTIVATE message
|
// Forward ACTIVATE message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
long timestamp = getLocalTimestamp(s, i);
|
long localTimestamp = getTimestampForInvisibleMessage(s, i);
|
||||||
Message sent = sendActivateMessage(txn, i, timestamp, m.getMac());
|
Message sent = sendActivateMessage(txn, i, localTimestamp, m.getMac());
|
||||||
|
|
||||||
// Move to the next state
|
// Move to the next state
|
||||||
IntroducerState state = START;
|
IntroducerState state = START;
|
||||||
@@ -532,8 +539,8 @@ class IntroducerProtocolEngine
|
|||||||
IntroducerSession s, AbortMessage m) throws DbException {
|
IntroducerSession s, AbortMessage m) throws DbException {
|
||||||
// Forward ABORT message
|
// Forward ABORT message
|
||||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||||
long timestamp = getLocalTimestamp(s, i);
|
long localTimestamp = getTimestampForInvisibleMessage(s, i);
|
||||||
Message sent = sendAbortMessage(txn, i, timestamp);
|
Message sent = sendAbortMessage(txn, i, localTimestamp);
|
||||||
|
|
||||||
// Broadcast abort event for testing
|
// Broadcast abort event for testing
|
||||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||||
@@ -551,15 +558,45 @@ class IntroducerProtocolEngine
|
|||||||
s.getRequestTimestamp(), introduceeA, introduceeB);
|
s.getRequestTimestamp(), introduceeA, introduceeB);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntroducerSession abort(Transaction txn,
|
private IntroducerSession abort(Transaction txn, IntroducerSession s,
|
||||||
IntroducerSession s) throws DbException {
|
Introducee remainingIntroducee) throws DbException {
|
||||||
|
// Broadcast abort event for testing
|
||||||
|
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||||
|
|
||||||
|
// Send an ABORT message to the remaining introducee
|
||||||
|
long localTimestamp =
|
||||||
|
getTimestampForInvisibleMessage(s, remainingIntroducee);
|
||||||
|
Message sent =
|
||||||
|
sendAbortMessage(txn, remainingIntroducee, localTimestamp);
|
||||||
|
// Reset the session back to initial state
|
||||||
|
Introducee introduceeA = s.getIntroduceeA();
|
||||||
|
Introducee introduceeB = s.getIntroduceeB();
|
||||||
|
if (remainingIntroducee.author.equals(introduceeA.author)) {
|
||||||
|
introduceeA = new Introducee(introduceeA, sent);
|
||||||
|
introduceeB = new Introducee(s.getSessionId(), introduceeB.groupId,
|
||||||
|
introduceeB.author);
|
||||||
|
} else if (remainingIntroducee.author.equals(introduceeB.author)) {
|
||||||
|
introduceeA = new Introducee(s.getSessionId(), introduceeA.groupId,
|
||||||
|
introduceeA.author);
|
||||||
|
introduceeB = new Introducee(introduceeB, sent);
|
||||||
|
} else {
|
||||||
|
throw new DbException();
|
||||||
|
}
|
||||||
|
return new IntroducerSession(s.getSessionId(), START,
|
||||||
|
s.getRequestTimestamp(), introduceeA, introduceeB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntroducerSession abort(Transaction txn, IntroducerSession s)
|
||||||
|
throws DbException {
|
||||||
// Broadcast abort event for testing
|
// Broadcast abort event for testing
|
||||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||||
|
|
||||||
// Send an ABORT message to both introducees
|
// Send an ABORT message to both introducees
|
||||||
long timestampA = getLocalTimestamp(s, s.getIntroduceeA());
|
long timestampA =
|
||||||
|
getTimestampForInvisibleMessage(s, s.getIntroduceeA());
|
||||||
Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA);
|
Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA);
|
||||||
long timestampB = getLocalTimestamp(s, s.getIntroduceeB());
|
long timestampB =
|
||||||
|
getTimestampForInvisibleMessage(s, s.getIntroduceeB());
|
||||||
Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB);
|
Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB);
|
||||||
// Reset the session back to initial state
|
// Reset the session back to initial state
|
||||||
Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA);
|
Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA);
|
||||||
@@ -589,9 +626,33 @@ class IntroducerProtocolEngine
|
|||||||
return isInvalidDependency(expected, dependency);
|
return isInvalidDependency(expected, dependency);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getLocalTimestamp(IntroducerSession s, PeerSession p) {
|
/**
|
||||||
return getLocalTimestamp(p.getLocalTimestamp(),
|
* Returns a timestamp for a visible outgoing message. The timestamp is
|
||||||
s.getRequestTimestamp());
|
* later than the timestamp of any message sent or received so far in the
|
||||||
|
* conversation, and later than the {@link
|
||||||
|
* #getSessionTimestamp(IntroducerSession, PeerSession) session timestamp}.
|
||||||
|
*/
|
||||||
|
private long getTimestampForVisibleMessage(Transaction txn,
|
||||||
|
IntroducerSession s, PeerSession p) throws DbException {
|
||||||
|
long conversationTimestamp =
|
||||||
|
getTimestampForOutgoingMessage(txn, p.getContactGroupId());
|
||||||
|
return max(conversationTimestamp, getSessionTimestamp(s, p) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a timestamp for an invisible outgoing message. The timestamp is
|
||||||
|
* later than the {@link #getSessionTimestamp(IntroducerSession, PeerSession)
|
||||||
|
* session timestamp}.
|
||||||
|
*/
|
||||||
|
private long getTimestampForInvisibleMessage(IntroducerSession s,
|
||||||
|
PeerSession p) {
|
||||||
|
return max(clock.currentTimeMillis(), getSessionTimestamp(s, p) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest timestamp of any message sent so far in the session.
|
||||||
|
*/
|
||||||
|
private long getSessionTimestamp(IntroducerSession s, PeerSession p) {
|
||||||
|
return max(p.getLocalTimestamp(), s.getRequestTimestamp());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package org.briarproject.briar.introduction;
|
|||||||
|
|
||||||
interface IntroductionConstants {
|
interface IntroductionConstants {
|
||||||
|
|
||||||
// Group metadata keys
|
|
||||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
|
||||||
|
|
||||||
// Message metadata keys
|
// Message metadata keys
|
||||||
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
||||||
String MSG_KEY_SESSION_ID = "sessionId";
|
String MSG_KEY_SESSION_ID = "sessionId";
|
||||||
@@ -12,6 +9,7 @@ interface IntroductionConstants {
|
|||||||
String MSG_KEY_LOCAL = "local";
|
String MSG_KEY_LOCAL = "local";
|
||||||
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
|
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
|
||||||
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
|
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
|
||||||
|
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
|
||||||
|
|
||||||
// Session Keys
|
// Session Keys
|
||||||
String SESSION_KEY_SESSION_ID = "sessionId";
|
String SESSION_KEY_SESSION_ID = "sessionId";
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_DECLINE
|
|||||||
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
|
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
|
||||||
import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
|
import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
|
||||||
import static org.briarproject.briar.introduction.IntroducerState.START;
|
import static org.briarproject.briar.introduction.IntroducerState.START;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID;
|
|
||||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||||
@@ -136,13 +135,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary meta = new BdfDictionary();
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
|
||||||
try {
|
|
||||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -183,7 +176,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
StoredSession ss = getSession(txn, sessionId);
|
StoredSession ss = getSession(txn, sessionId);
|
||||||
// Handle the message
|
// Handle the message
|
||||||
Session session;
|
Session<?> session;
|
||||||
MessageId storageId;
|
MessageId storageId;
|
||||||
if (ss == null) {
|
if (ss == null) {
|
||||||
if (meta.getMessageType() != REQUEST) throw new FormatException();
|
if (meta.getMessageType() != REQUEST) throw new FormatException();
|
||||||
@@ -211,7 +204,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
|
|
||||||
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
||||||
Message m, BdfList body) throws DbException, FormatException {
|
Message m, BdfList body) throws DbException, FormatException {
|
||||||
ContactId introducerId = getContactId(txn, m.getGroupId());
|
ContactId introducerId = clientHelper.getContactId(txn, m.getGroupId());
|
||||||
Author introducer = db.getContact(txn, introducerId).getAuthor();
|
Author introducer = db.getContact(txn, introducerId).getAuthor();
|
||||||
Author local = identityManager.getLocalAuthor(txn);
|
Author local = identityManager.getLocalAuthor(txn);
|
||||||
Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
|
Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
|
||||||
@@ -223,7 +216,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
remote);
|
remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S extends Session> S handleMessage(Transaction txn, Message m,
|
private <S extends Session<?>> S handleMessage(Transaction txn, Message m,
|
||||||
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
|
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
if (type == REQUEST) {
|
if (type == REQUEST) {
|
||||||
@@ -263,13 +256,6 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
results.values().iterator().next());
|
results.values().iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
BdfDictionary meta =
|
|
||||||
clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
|
|
||||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageId createStorageId(Transaction txn) throws DbException {
|
private MessageId createStorageId(Transaction txn) throws DbException {
|
||||||
Message m = clientHelper
|
Message m = clientHelper
|
||||||
.createMessageForStoringMetadata(localGroup.getId());
|
.createMessageForStoringMetadata(localGroup.getId());
|
||||||
@@ -278,7 +264,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeSession(Transaction txn, MessageId storageId,
|
private void storeSession(Transaction txn, MessageId storageId,
|
||||||
Session session) throws DbException {
|
Session<?> session) throws DbException {
|
||||||
BdfDictionary d;
|
BdfDictionary d;
|
||||||
if (session.getRole() == INTRODUCER) {
|
if (session.getRole() == INTRODUCER) {
|
||||||
d = sessionEncoder
|
d = sessionEncoder
|
||||||
@@ -325,8 +311,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void makeIntroduction(Contact c1, Contact c2, @Nullable String text,
|
public void makeIntroduction(Contact c1, Contact c2, @Nullable String text)
|
||||||
long timestamp) throws DbException {
|
throws DbException {
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
// Look up the session, if there is one
|
// Look up the session, if there is one
|
||||||
@@ -358,8 +344,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
storageId = ss.storageId;
|
storageId = ss.storageId;
|
||||||
}
|
}
|
||||||
// Handle the request action
|
// Handle the request action
|
||||||
session = introducerEngine
|
session = introducerEngine.onRequestAction(txn, session, text);
|
||||||
.onRequestAction(txn, session, text, timestamp);
|
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, storageId, session);
|
storeSession(txn, storageId, session);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -372,7 +357,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void respondToIntroduction(ContactId contactId, SessionId sessionId,
|
public void respondToIntroduction(ContactId contactId, SessionId sessionId,
|
||||||
long timestamp, boolean accept) throws DbException {
|
boolean accept) throws DbException {
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
// Look up the session
|
// Look up the session
|
||||||
@@ -390,11 +375,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
|
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
|
||||||
// Handle the join or leave action
|
// Handle the join or leave action
|
||||||
if (accept) {
|
if (accept) {
|
||||||
session = introduceeEngine
|
session = introduceeEngine.onAcceptAction(txn, session);
|
||||||
.onAcceptAction(txn, session, timestamp);
|
|
||||||
} else {
|
} else {
|
||||||
session = introduceeEngine
|
session = introduceeEngine.onDeclineAction(txn, session);
|
||||||
.onDeclineAction(txn, session, timestamp);
|
|
||||||
}
|
}
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, ss.storageId, session);
|
storeSession(txn, ss.storageId, session);
|
||||||
@@ -461,7 +444,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(),
|
return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(),
|
||||||
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
|
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
|
||||||
sessionId, author, text, !meta.isAvailableToAnswer(),
|
sessionId, author, text, !meta.isAvailableToAnswer(),
|
||||||
authorInfo);
|
authorInfo, rm.getAutoDeleteTimer());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntroductionResponse parseInvitationResponse(Transaction txn,
|
private IntroductionResponse parseInvitationResponse(Transaction txn,
|
||||||
@@ -499,7 +482,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
|
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
|
||||||
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
|
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
|
||||||
sessionId, accept, author, authorInfo, role, canSucceed);
|
sessionId, accept, author, authorInfo, role, canSucceed,
|
||||||
|
meta.getAutoDeleteTimer());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSessionWithIntroducer(Transaction txn,
|
private void removeSessionWithIntroducer(Transaction txn,
|
||||||
@@ -665,7 +649,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
try {
|
try {
|
||||||
StoredSession ss = getSession(txn, sessionId);
|
StoredSession ss = getSession(txn, sessionId);
|
||||||
if (ss == null) throw new AssertionError();
|
if (ss == null) throw new AssertionError();
|
||||||
Session s;
|
Session<?> s;
|
||||||
Role role = sessionParser.getRole(ss.bdfSession);
|
Role role = sessionParser.getRole(ss.bdfSession);
|
||||||
if (role == INTRODUCER) {
|
if (role == INTRODUCER) {
|
||||||
s = sessionParser.parseIntroducerSession(ss.bdfSession);
|
s = sessionParser.parseIntroducerSession(ss.bdfSession);
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_
|
|||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||||
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
||||||
|
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
|
||||||
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -52,13 +54,14 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
return validateRequestMessage(m, body);
|
return validateRequestMessage(m, body);
|
||||||
case ACCEPT:
|
case ACCEPT:
|
||||||
return validateAcceptMessage(m, body);
|
return validateAcceptMessage(m, body);
|
||||||
|
case DECLINE:
|
||||||
|
return validateDeclineMessage(type, m, body);
|
||||||
case AUTH:
|
case AUTH:
|
||||||
return validateAuthMessage(m, body);
|
return validateAuthMessage(m, body);
|
||||||
case ACTIVATE:
|
case ACTIVATE:
|
||||||
return validateActivateMessage(m, body);
|
return validateActivateMessage(m, body);
|
||||||
case DECLINE:
|
|
||||||
case ABORT:
|
case ABORT:
|
||||||
return validateOtherMessage(type, m, body);
|
return validateAbortMessage(type, m, body);
|
||||||
default:
|
default:
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
@@ -66,7 +69,11 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
private BdfMessageContext validateRequestMessage(Message m, BdfList body)
|
private BdfMessageContext validateRequestMessage(Message m, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
checkSize(body, 4);
|
// Client version 0.0: Message type, optional previous message ID,
|
||||||
|
// author, optional text.
|
||||||
|
// Client version 0.1: Message type, optional previous message ID,
|
||||||
|
// author, optional text, optional auto-delete timer.
|
||||||
|
checkSize(body, 4, 5);
|
||||||
|
|
||||||
byte[] previousMessageId = body.getOptionalRaw(1);
|
byte[] previousMessageId = body.getOptionalRaw(1);
|
||||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||||
@@ -77,8 +84,13 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
String text = body.getOptionalString(3);
|
String text = body.getOptionalString(3);
|
||||||
checkLength(text, 1, MAX_INTRODUCTION_TEXT_LENGTH);
|
checkLength(text, 1, MAX_INTRODUCTION_TEXT_LENGTH);
|
||||||
|
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 5) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(4));
|
||||||
|
}
|
||||||
|
|
||||||
BdfDictionary meta =
|
BdfDictionary meta =
|
||||||
messageEncoder.encodeRequestMetadata(m.getTimestamp());
|
messageEncoder.encodeRequestMetadata(m.getTimestamp(), timer);
|
||||||
if (previousMessageId == null) {
|
if (previousMessageId == null) {
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
} else {
|
} else {
|
||||||
@@ -89,7 +101,12 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
|
private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
checkSize(body, 6);
|
// Client version 0.0: Message type, session ID, optional previous
|
||||||
|
// message ID, ephemeral public key, timestamp, transport properties.
|
||||||
|
// Client version 0.1: Message type, session ID, optional previous
|
||||||
|
// message ID, ephemeral public key, timestamp, transport properties,
|
||||||
|
// optional auto-delete timer.
|
||||||
|
checkSize(body, 6, 7);
|
||||||
|
|
||||||
byte[] sessionIdBytes = body.getRaw(1);
|
byte[] sessionIdBytes = body.getRaw(1);
|
||||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||||
@@ -109,9 +126,44 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
clientHelper
|
clientHelper
|
||||||
.parseAndValidateTransportPropertiesMap(transportProperties);
|
.parseAndValidateTransportPropertiesMap(transportProperties);
|
||||||
|
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 7) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(6));
|
||||||
|
}
|
||||||
|
|
||||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId,
|
BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId,
|
||||||
m.getTimestamp(), false, false, false);
|
m.getTimestamp(), false, false, false, timer);
|
||||||
|
if (previousMessageId == null) {
|
||||||
|
return new BdfMessageContext(meta);
|
||||||
|
} else {
|
||||||
|
MessageId dependency = new MessageId(previousMessageId);
|
||||||
|
return new BdfMessageContext(meta, singletonList(dependency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfMessageContext validateDeclineMessage(MessageType type,
|
||||||
|
Message m, BdfList body) throws FormatException {
|
||||||
|
// Client version 0.0: Message type, session ID, optional previous
|
||||||
|
// message ID.
|
||||||
|
// Client version 0.1: Message type, session ID, optional previous
|
||||||
|
// message ID, optional auto-delete timer.
|
||||||
|
checkSize(body, 3, 4);
|
||||||
|
|
||||||
|
byte[] sessionIdBytes = body.getRaw(1);
|
||||||
|
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||||
|
|
||||||
|
byte[] previousMessageId = body.getOptionalRaw(2);
|
||||||
|
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||||
|
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 4) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||||
|
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
||||||
|
m.getTimestamp(), false, false, false, timer);
|
||||||
if (previousMessageId == null) {
|
if (previousMessageId == null) {
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
} else {
|
} else {
|
||||||
@@ -138,7 +190,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(AUTH, sessionId,
|
BdfDictionary meta = messageEncoder.encodeMetadata(AUTH, sessionId,
|
||||||
m.getTimestamp(), false, false, false);
|
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
|
||||||
MessageId dependency = new MessageId(previousMessageId);
|
MessageId dependency = new MessageId(previousMessageId);
|
||||||
return new BdfMessageContext(meta, singletonList(dependency));
|
return new BdfMessageContext(meta, singletonList(dependency));
|
||||||
}
|
}
|
||||||
@@ -158,7 +210,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId,
|
BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId,
|
||||||
m.getTimestamp(), false, false, false);
|
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
|
||||||
if (previousMessageId == null) {
|
if (previousMessageId == null) {
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
} else {
|
} else {
|
||||||
@@ -167,7 +219,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfMessageContext validateOtherMessage(MessageType type,
|
private BdfMessageContext validateAbortMessage(MessageType type,
|
||||||
Message m, BdfList body) throws FormatException {
|
Message m, BdfList body) throws FormatException {
|
||||||
checkSize(body, 3);
|
checkSize(body, 3);
|
||||||
|
|
||||||
@@ -179,7 +231,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
||||||
m.getTimestamp(), false, false, false);
|
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
|
||||||
if (previousMessageId == null) {
|
if (previousMessageId == null) {
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
} else {
|
} else {
|
||||||
@@ -187,5 +239,4 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
return new BdfMessageContext(meta, singletonList(dependency));
|
return new BdfMessageContext(meta, singletonList(dependency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface MessageEncoder {
|
interface MessageEncoder {
|
||||||
|
|
||||||
BdfDictionary encodeRequestMetadata(long timestamp);
|
BdfDictionary encodeRequestMetadata(long timestamp,
|
||||||
|
long autoDeleteTimer);
|
||||||
|
|
||||||
BdfDictionary encodeMetadata(MessageType type,
|
BdfDictionary encodeMetadata(MessageType type,
|
||||||
@Nullable SessionId sessionId, long timestamp, boolean local,
|
@Nullable SessionId sessionId, long timestamp, boolean local,
|
||||||
boolean read, boolean visible);
|
boolean read, boolean visible, long autoDeleteTimer);
|
||||||
|
|
||||||
void addSessionId(BdfDictionary meta, SessionId sessionId);
|
void addSessionId(BdfDictionary meta, SessionId sessionId);
|
||||||
|
|
||||||
@@ -30,18 +31,53 @@ interface MessageEncoder {
|
|||||||
|
|
||||||
void setAvailableToAnswer(BdfDictionary meta, boolean available);
|
void setAvailableToAnswer(BdfDictionary meta, boolean available);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a request message without an auto-delete timer.
|
||||||
|
*/
|
||||||
Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
|
Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, Author author,
|
@Nullable MessageId previousMessageId, Author author,
|
||||||
@Nullable String text);
|
@Nullable String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a request message with an optional auto-delete timer. This
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
|
Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
|
||||||
|
@Nullable MessageId previousMessageId, Author author,
|
||||||
|
@Nullable String text, long autoDeleteTimer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes an accept message without an auto-delete timer.
|
||||||
|
*/
|
||||||
Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
|
Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId,
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
PublicKey ephemeralPublicKey, long acceptTimestamp,
|
PublicKey ephemeralPublicKey, long acceptTimestamp,
|
||||||
Map<TransportId, TransportProperties> transportProperties);
|
Map<TransportId, TransportProperties> transportProperties);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes an accept message with an optional auto-delete timer. This
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
|
Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
|
||||||
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
|
PublicKey ephemeralPublicKey, long acceptTimestamp,
|
||||||
|
Map<TransportId, TransportProperties> transportProperties,
|
||||||
|
long autoDeleteTimer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a decline message without an auto-delete timer.
|
||||||
|
*/
|
||||||
Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
|
Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId);
|
@Nullable MessageId previousMessageId, SessionId sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a decline message with an optional auto-delete timer. This
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
|
Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
|
||||||
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
|
long autoDeleteTimer);
|
||||||
|
|
||||||
Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
|
Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId,
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
byte[] mac, byte[] signature);
|
byte[] mac, byte[] signature);
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import java.util.Map;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||||
@@ -48,9 +50,10 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BdfDictionary encodeRequestMetadata(long timestamp) {
|
public BdfDictionary encodeRequestMetadata(long timestamp,
|
||||||
BdfDictionary meta =
|
long autoDeleteTimer) {
|
||||||
encodeMetadata(REQUEST, null, timestamp, false, false, false);
|
BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp,
|
||||||
|
false, false, false, autoDeleteTimer);
|
||||||
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
|
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,7 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
@Override
|
@Override
|
||||||
public BdfDictionary encodeMetadata(MessageType type,
|
public BdfDictionary encodeMetadata(MessageType type,
|
||||||
@Nullable SessionId sessionId, long timestamp, boolean local,
|
@Nullable SessionId sessionId, long timestamp, boolean local,
|
||||||
boolean read, boolean visible) {
|
boolean read, boolean visible, long autoDeleteTimer) {
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
|
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
|
||||||
if (sessionId != null)
|
if (sessionId != null)
|
||||||
@@ -69,6 +72,9 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
meta.put(MSG_KEY_LOCAL, local);
|
meta.put(MSG_KEY_LOCAL, local);
|
||||||
meta.put(MSG_KEY_READ, read);
|
meta.put(MSG_KEY_READ, read);
|
||||||
meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
|
meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
|
||||||
|
if (autoDeleteTimer != NO_AUTO_DELETE_TIMER) {
|
||||||
|
meta.put(MSG_KEY_AUTO_DELETE_TIMER, autoDeleteTimer);
|
||||||
|
}
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +109,23 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
return createMessage(contactGroupId, timestamp, body);
|
return createMessage(contactGroupId, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
|
||||||
|
@Nullable MessageId previousMessageId, Author author,
|
||||||
|
@Nullable String text, long autoDeleteTimer) {
|
||||||
|
if (text != null && text.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
BdfList body = BdfList.of(
|
||||||
|
REQUEST.getValue(),
|
||||||
|
previousMessageId,
|
||||||
|
clientHelper.toList(author),
|
||||||
|
text,
|
||||||
|
encodeTimer(autoDeleteTimer)
|
||||||
|
);
|
||||||
|
return createMessage(contactGroupId, timestamp, body);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
|
public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId,
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
@@ -119,11 +142,46 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
return createMessage(contactGroupId, timestamp, body);
|
return createMessage(contactGroupId, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
|
||||||
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
|
PublicKey ephemeralPublicKey, long acceptTimestamp,
|
||||||
|
Map<TransportId, TransportProperties> transportProperties,
|
||||||
|
long autoDeleteTimer) {
|
||||||
|
BdfList body = BdfList.of(
|
||||||
|
ACCEPT.getValue(),
|
||||||
|
sessionId,
|
||||||
|
previousMessageId,
|
||||||
|
ephemeralPublicKey.getEncoded(),
|
||||||
|
acceptTimestamp,
|
||||||
|
clientHelper.toDictionary(transportProperties),
|
||||||
|
encodeTimer(autoDeleteTimer)
|
||||||
|
);
|
||||||
|
return createMessage(contactGroupId, timestamp, body);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
|
public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId) {
|
@Nullable MessageId previousMessageId, SessionId sessionId) {
|
||||||
return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp,
|
BdfList body = BdfList.of(
|
||||||
previousMessageId);
|
DECLINE.getValue(),
|
||||||
|
sessionId,
|
||||||
|
previousMessageId
|
||||||
|
);
|
||||||
|
return createMessage(contactGroupId, timestamp, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
|
||||||
|
@Nullable MessageId previousMessageId, SessionId sessionId,
|
||||||
|
long autoDeleteTimer) {
|
||||||
|
BdfList body = BdfList.of(
|
||||||
|
DECLINE.getValue(),
|
||||||
|
sessionId,
|
||||||
|
previousMessageId,
|
||||||
|
encodeTimer(autoDeleteTimer)
|
||||||
|
);
|
||||||
|
return createMessage(contactGroupId, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -156,15 +214,8 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
@Override
|
@Override
|
||||||
public Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
|
public Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, SessionId sessionId) {
|
@Nullable MessageId previousMessageId, SessionId sessionId) {
|
||||||
return encodeMessage(ABORT, contactGroupId, sessionId, timestamp,
|
|
||||||
previousMessageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message encodeMessage(MessageType type, GroupId contactGroupId,
|
|
||||||
SessionId sessionId, long timestamp,
|
|
||||||
@Nullable MessageId previousMessageId) {
|
|
||||||
BdfList body = BdfList.of(
|
BdfList body = BdfList.of(
|
||||||
type.getValue(),
|
ABORT.getValue(),
|
||||||
sessionId,
|
sessionId,
|
||||||
previousMessageId
|
previousMessageId
|
||||||
);
|
);
|
||||||
@@ -181,4 +232,8 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Long encodeTimer(long autoDeleteTimer) {
|
||||||
|
return autoDeleteTimer == NO_AUTO_DELETE_TIMER ? null : autoDeleteTimer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ class MessageMetadata {
|
|||||||
private final MessageType type;
|
private final MessageType type;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final SessionId sessionId;
|
private final SessionId sessionId;
|
||||||
private final long timestamp;
|
private final long timestamp, autoDeleteTimer;
|
||||||
private final boolean local, read, visible, available;
|
private final boolean local, read, visible, available;
|
||||||
|
|
||||||
MessageMetadata(MessageType type, @Nullable SessionId sessionId,
|
MessageMetadata(MessageType type, @Nullable SessionId sessionId,
|
||||||
long timestamp, boolean local, boolean read, boolean visible,
|
long timestamp, boolean local, boolean read, boolean visible,
|
||||||
boolean available) {
|
boolean available, long autoDeleteTimer) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
@@ -26,6 +26,7 @@ class MessageMetadata {
|
|||||||
this.read = read;
|
this.read = read;
|
||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
this.available = available;
|
this.available = available;
|
||||||
|
this.autoDeleteTimer = autoDeleteTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageType getMessageType() {
|
MessageType getMessageType() {
|
||||||
@@ -57,4 +58,7 @@ class MessageMetadata {
|
|||||||
return available;
|
return available;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getAutoDeleteTimer() {
|
||||||
|
return autoDeleteTimer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import java.util.Map;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||||
@@ -65,8 +67,9 @@ class MessageParserImpl implements MessageParser {
|
|||||||
boolean read = d.getBoolean(MSG_KEY_READ);
|
boolean read = d.getBoolean(MSG_KEY_READ);
|
||||||
boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
|
boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
|
||||||
boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
|
boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
|
||||||
|
long timer = d.getLong(MSG_KEY_AUTO_DELETE_TIMER, NO_AUTO_DELETE_TIMER);
|
||||||
return new MessageMetadata(type, sessionId, timestamp, local, read,
|
return new MessageMetadata(type, sessionId, timestamp, local, read,
|
||||||
visible, available);
|
visible, available, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,8 +80,10 @@ class MessageParserImpl implements MessageParser {
|
|||||||
new MessageId(previousMsgBytes));
|
new MessageId(previousMsgBytes));
|
||||||
Author author = clientHelper.parseAndValidateAuthor(body.getList(2));
|
Author author = clientHelper.parseAndValidateAuthor(body.getList(2));
|
||||||
String text = body.getOptionalString(3);
|
String text = body.getOptionalString(3);
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 5) timer = body.getLong(4, NO_AUTO_DELETE_TIMER);
|
||||||
return new RequestMessage(m.getId(), m.getGroupId(),
|
return new RequestMessage(m.getId(), m.getGroupId(),
|
||||||
m.getTimestamp(), previousMessageId, author, text);
|
m.getTimestamp(), previousMessageId, author, text, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,9 +97,11 @@ class MessageParserImpl implements MessageParser {
|
|||||||
long acceptTimestamp = body.getLong(4);
|
long acceptTimestamp = body.getLong(4);
|
||||||
Map<TransportId, TransportProperties> transportProperties = clientHelper
|
Map<TransportId, TransportProperties> transportProperties = clientHelper
|
||||||
.parseAndValidateTransportPropertiesMap(body.getDictionary(5));
|
.parseAndValidateTransportPropertiesMap(body.getDictionary(5));
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 7) timer = body.getLong(6, NO_AUTO_DELETE_TIMER);
|
||||||
return new AcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
|
return new AcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
|
||||||
previousMessageId, sessionId, ephemeralPublicKey,
|
previousMessageId, sessionId, ephemeralPublicKey,
|
||||||
acceptTimestamp, transportProperties);
|
acceptTimestamp, transportProperties, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -104,8 +111,10 @@ class MessageParserImpl implements MessageParser {
|
|||||||
byte[] previousMsgBytes = body.getOptionalRaw(2);
|
byte[] previousMsgBytes = body.getOptionalRaw(2);
|
||||||
MessageId previousMessageId = (previousMsgBytes == null ? null :
|
MessageId previousMessageId = (previousMsgBytes == null ? null :
|
||||||
new MessageId(previousMsgBytes));
|
new MessageId(previousMsgBytes));
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 4) timer = body.getLong(3, NO_AUTO_DELETE_TIMER);
|
||||||
return new DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
|
return new DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
|
||||||
previousMessageId, sessionId);
|
previousMessageId, sessionId, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,16 +8,14 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ProtocolEngine<S extends Session> {
|
interface ProtocolEngine<S extends Session<?>> {
|
||||||
|
|
||||||
S onRequestAction(Transaction txn, S session, @Nullable String text,
|
S onRequestAction(Transaction txn, S session, @Nullable String text)
|
||||||
long timestamp) throws DbException;
|
|
||||||
|
|
||||||
S onAcceptAction(Transaction txn, S session, long timestamp)
|
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
S onDeclineAction(Transaction txn, S session, long timestamp)
|
S onAcceptAction(Transaction txn, S session) throws DbException;
|
||||||
throws DbException;
|
|
||||||
|
S onDeclineAction(Transaction txn, S session) throws DbException;
|
||||||
|
|
||||||
S onRequestMessage(Transaction txn, S session, RequestMessage m)
|
S onRequestMessage(Transaction txn, S session, RequestMessage m)
|
||||||
throws DbException, FormatException;
|
throws DbException, FormatException;
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ class RequestMessage extends AbstractIntroductionMessage {
|
|||||||
|
|
||||||
RequestMessage(MessageId messageId, GroupId groupId, long timestamp,
|
RequestMessage(MessageId messageId, GroupId groupId, long timestamp,
|
||||||
@Nullable MessageId previousMessageId, Author author,
|
@Nullable MessageId previousMessageId, Author author,
|
||||||
@Nullable String text) {
|
@Nullable String text, long autoDeleteTimer) {
|
||||||
super(messageId, groupId, timestamp, previousMessageId);
|
super(messageId, groupId, timestamp, previousMessageId,
|
||||||
|
autoDeleteTimer);
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
@@ -20,16 +21,20 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationManagerImpl implements ConversationManager {
|
class ConversationManagerImpl implements ConversationManager {
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
|
private final Clock clock;
|
||||||
private final Set<ConversationClient> clients;
|
private final Set<ConversationClient> clients;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConversationManagerImpl(DatabaseComponent db) {
|
ConversationManagerImpl(DatabaseComponent db, Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.clock = clock;
|
||||||
clients = new CopyOnWriteArraySet<>();
|
clients = new CopyOnWriteArraySet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,24 +62,33 @@ class ConversationManagerImpl implements ConversationManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupCount getGroupCount(ContactId contactId) throws DbException {
|
public GroupCount getGroupCount(ContactId contactId) throws DbException {
|
||||||
|
return db.transactionWithResult(true, txn ->
|
||||||
|
getGroupCount(txn, contactId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupCount getGroupCount(Transaction txn, ContactId contactId)
|
||||||
|
throws DbException {
|
||||||
int msgCount = 0, unreadCount = 0;
|
int msgCount = 0, unreadCount = 0;
|
||||||
long latestTime = 0;
|
long latestTime = 0;
|
||||||
Transaction txn = db.startTransaction(true);
|
for (ConversationClient client : clients) {
|
||||||
try {
|
GroupCount count = client.getGroupCount(txn, contactId);
|
||||||
for (ConversationClient client : clients) {
|
msgCount += count.getMsgCount();
|
||||||
GroupCount count = client.getGroupCount(txn, contactId);
|
unreadCount += count.getUnreadCount();
|
||||||
msgCount += count.getMsgCount();
|
if (count.getLatestMsgTime() > latestTime)
|
||||||
unreadCount += count.getUnreadCount();
|
latestTime = count.getLatestMsgTime();
|
||||||
if (count.getLatestMsgTime() > latestTime)
|
|
||||||
latestTime = count.getLatestMsgTime();
|
|
||||||
}
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
}
|
||||||
return new GroupCount(msgCount, unreadCount, latestTime);
|
return new GroupCount(msgCount, unreadCount, latestTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimestampForOutgoingMessage(Transaction txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
GroupCount gc = getGroupCount(txn, c);
|
||||||
|
return max(now, gc.getLatestMsgTime() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeletionResult deleteAllMessages(ContactId c) throws DbException {
|
public DeletionResult deleteAllMessages(ContactId c) throws DbException {
|
||||||
return db.transactionWithResult(false, txn -> {
|
return db.transactionWithResult(false, txn -> {
|
||||||
@@ -87,8 +101,8 @@ class ConversationManagerImpl implements ConversationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeletionResult deleteMessages(ContactId c, Collection<MessageId> toDelete)
|
public DeletionResult deleteMessages(ContactId c,
|
||||||
throws DbException {
|
Collection<MessageId> toDelete) throws DbException {
|
||||||
return db.transactionWithResult(false, txn -> {
|
return db.transactionWithResult(false, txn -> {
|
||||||
DeletionResult result = new DeletionResult();
|
DeletionResult result = new DeletionResult();
|
||||||
for (ConversationClient client : clients) {
|
for (ConversationClient client : clients) {
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package org.briarproject.briar.messaging;
|
|||||||
|
|
||||||
interface MessagingConstants {
|
interface MessagingConstants {
|
||||||
|
|
||||||
// Metadata keys for groups
|
|
||||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
|
||||||
|
|
||||||
// Metadata keys for messages
|
// Metadata keys for messages
|
||||||
String MSG_KEY_TIMESTAMP = "timestamp";
|
String MSG_KEY_TIMESTAMP = "timestamp";
|
||||||
String MSG_KEY_LOCAL = "local";
|
String MSG_KEY_LOCAL = "local";
|
||||||
@@ -13,4 +10,5 @@ interface MessagingConstants {
|
|||||||
String MSG_KEY_DESCRIPTOR_LENGTH = "descriptorLength";
|
String MSG_KEY_DESCRIPTOR_LENGTH = "descriptorLength";
|
||||||
String MSG_KEY_HAS_TEXT = "hasText";
|
String MSG_KEY_HAS_TEXT = "hasText";
|
||||||
String MSG_KEY_ATTACHMENT_HEADERS = "attachmentHeaders";
|
String MSG_KEY_ATTACHMENT_HEADERS = "attachmentHeaders";
|
||||||
|
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.briarproject.bramble.api.sync.MessageStatus;
|
|||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
|
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
|
||||||
@@ -38,6 +39,7 @@ import org.briarproject.briar.api.messaging.FileTooBigException;
|
|||||||
import org.briarproject.briar.api.messaging.InvalidAttachmentException;
|
import org.briarproject.briar.api.messaging.InvalidAttachmentException;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
|
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||||
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
||||||
@@ -60,11 +62,15 @@ import static java.util.Collections.emptyList;
|
|||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
|
||||||
|
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
||||||
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.GROUP_KEY_CONTACT_ID;
|
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
||||||
@@ -84,18 +90,24 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
private final MessageTracker messageTracker;
|
private final MessageTracker messageTracker;
|
||||||
private final ClientVersioningManager clientVersioningManager;
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
|
private final AutoDeleteManager autoDeleteManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
MessagingManagerImpl(
|
||||||
|
DatabaseComponent db,
|
||||||
|
ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MetadataParser metadataParser, MessageTracker messageTracker,
|
MetadataParser metadataParser,
|
||||||
ContactGroupFactory contactGroupFactory) {
|
MessageTracker messageTracker,
|
||||||
|
ContactGroupFactory contactGroupFactory,
|
||||||
|
AutoDeleteManager autoDeleteManager) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
this.metadataParser = metadataParser;
|
this.metadataParser = metadataParser;
|
||||||
this.messageTracker = messageTracker;
|
this.messageTracker = messageTracker;
|
||||||
this.clientVersioningManager = clientVersioningManager;
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
|
this.autoDeleteManager = autoDeleteManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -133,13 +145,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary d = new BdfDictionary();
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
d.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
|
||||||
try {
|
|
||||||
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
// Initialize the group count with current time
|
// Initialize the group count with current time
|
||||||
messageTracker.initializeGroupCount(txn, g.getId());
|
messageTracker.initializeGroupCount(txn, g.getId());
|
||||||
}
|
}
|
||||||
@@ -196,14 +202,18 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
|
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
|
||||||
boolean local = meta.getBoolean(MSG_KEY_LOCAL);
|
boolean local = meta.getBoolean(MSG_KEY_LOCAL);
|
||||||
boolean read = meta.getBoolean(MSG_KEY_READ);
|
boolean read = meta.getBoolean(MSG_KEY_READ);
|
||||||
|
long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
PrivateMessageHeader header =
|
PrivateMessageHeader header =
|
||||||
new PrivateMessageHeader(m.getId(), groupId, timestamp, local,
|
new PrivateMessageHeader(m.getId(), groupId, timestamp, local,
|
||||||
read, false, false, hasText, headers);
|
read, false, false, hasText, headers, timer);
|
||||||
ContactId contactId = getContactId(txn, groupId);
|
ContactId contactId = getContactId(txn, groupId);
|
||||||
PrivateMessageReceivedEvent event =
|
PrivateMessageReceivedEvent event =
|
||||||
new PrivateMessageReceivedEvent(header, contactId);
|
new PrivateMessageReceivedEvent(header, contactId);
|
||||||
txn.attach(event);
|
txn.attach(event);
|
||||||
messageTracker.trackIncomingMessage(txn, m);
|
messageTracker.trackIncomingMessage(txn, m);
|
||||||
|
autoDeleteManager.receiveAutoDeleteTimer(txn, contactId, timer,
|
||||||
|
timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AttachmentHeader> parseAttachmentHeaders(BdfDictionary meta)
|
private List<AttachmentHeader> parseAttachmentHeaders(BdfDictionary meta)
|
||||||
@@ -228,13 +238,18 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(PrivateMessage m) throws DbException {
|
public void addLocalMessage(PrivateMessage m) throws DbException {
|
||||||
Transaction txn = db.startTransaction(false);
|
db.transaction(false, txn -> addLocalMessage(txn, m));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLocalMessage(Transaction txn, PrivateMessage m)
|
||||||
|
throws DbException {
|
||||||
try {
|
try {
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_TIMESTAMP, m.getMessage().getTimestamp());
|
meta.put(MSG_KEY_TIMESTAMP, m.getMessage().getTimestamp());
|
||||||
meta.put(MSG_KEY_LOCAL, true);
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
meta.put(MSG_KEY_READ, true);
|
meta.put(MSG_KEY_READ, true);
|
||||||
if (!m.isLegacyFormat()) {
|
if (m.getFormat() != TEXT_ONLY) {
|
||||||
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
|
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
|
||||||
meta.put(MSG_KEY_HAS_TEXT, m.hasText());
|
meta.put(MSG_KEY_HAS_TEXT, m.hasText());
|
||||||
BdfList headers = new BdfList();
|
BdfList headers = new BdfList();
|
||||||
@@ -243,6 +258,12 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
BdfList.of(a.getMessageId(), a.getContentType()));
|
BdfList.of(a.getMessageId(), a.getContentType()));
|
||||||
}
|
}
|
||||||
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
|
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
|
||||||
|
if (m.getFormat() == TEXT_IMAGES_AUTO_DELETE) {
|
||||||
|
long timer = m.getAutoDeleteTimer();
|
||||||
|
if (timer != NO_AUTO_DELETE_TIMER) {
|
||||||
|
meta.put(MSG_KEY_AUTO_DELETE_TIMER, timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Mark attachments as shared and permanent now we're ready to send
|
// Mark attachments as shared and permanent now we're ready to send
|
||||||
for (AttachmentHeader a : m.getAttachmentHeaders()) {
|
for (AttachmentHeader a : m.getAttachmentHeaders()) {
|
||||||
@@ -252,11 +273,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
|
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
|
||||||
false);
|
false);
|
||||||
messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
||||||
db.commitTransaction(txn);
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,12 +373,14 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
if (messageType == null) {
|
if (messageType == null) {
|
||||||
headers.add(new PrivateMessageHeader(id, g, timestamp,
|
headers.add(new PrivateMessageHeader(id, g, timestamp,
|
||||||
local, read, s.isSent(), s.isSeen(), true,
|
local, read, s.isSent(), s.isSeen(), true,
|
||||||
emptyList()));
|
emptyList(), NO_AUTO_DELETE_TIMER));
|
||||||
} else {
|
} else {
|
||||||
boolean hasText = meta.getBoolean(MSG_KEY_HAS_TEXT);
|
boolean hasText = meta.getBoolean(MSG_KEY_HAS_TEXT);
|
||||||
|
long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
headers.add(new PrivateMessageHeader(id, g, timestamp,
|
headers.add(new PrivateMessageHeader(id, g, timestamp,
|
||||||
local, read, s.isSent(), s.isSeen(), hasText,
|
local, read, s.isSent(), s.isSeen(), hasText,
|
||||||
parseAttachmentHeaders(meta)));
|
parseAttachmentHeaders(meta), timer));
|
||||||
}
|
}
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -422,12 +442,13 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contactSupportsImages(Transaction txn, ContactId c)
|
public PrivateMessageFormat getContactMessageFormat(Transaction txn,
|
||||||
throws DbException {
|
ContactId c) throws DbException {
|
||||||
int minorVersion = clientVersioningManager
|
int minorVersion = clientVersioningManager
|
||||||
.getClientMinorVersion(txn, c, CLIENT_ID, 0);
|
.getClientMinorVersion(txn, c, CLIENT_ID, 0);
|
||||||
// support was added in 0.1
|
if (minorVersion >= 3) return TEXT_IMAGES_AUTO_DELETE;
|
||||||
return minorVersion > 0;
|
else if (minorVersion >= 1) return TEXT_IMAGES;
|
||||||
|
else return TEXT_ONLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.messaging;
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FeatureFlags;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
@@ -20,6 +19,7 @@ import dagger.Provides;
|
|||||||
|
|
||||||
import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID;
|
import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION;
|
import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingManager.MINOR_VERSION;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class MessagingModule {
|
public class MessagingModule {
|
||||||
@@ -57,17 +57,14 @@ public class MessagingModule {
|
|||||||
ContactManager contactManager, ValidationManager validationManager,
|
ContactManager contactManager, ValidationManager validationManager,
|
||||||
ConversationManager conversationManager,
|
ConversationManager conversationManager,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
FeatureFlags featureFlags, MessagingManagerImpl messagingManager) {
|
MessagingManagerImpl messagingManager) {
|
||||||
lifecycleManager.registerOpenDatabaseHook(messagingManager);
|
lifecycleManager.registerOpenDatabaseHook(messagingManager);
|
||||||
contactManager.registerContactHook(messagingManager);
|
contactManager.registerContactHook(messagingManager);
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||||
messagingManager);
|
messagingManager);
|
||||||
conversationManager.registerConversationClient(messagingManager);
|
conversationManager.registerConversationClient(messagingManager);
|
||||||
// Advertise the current or previous minor version depending on the
|
|
||||||
// feature flag
|
|
||||||
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 2 : 0;
|
|
||||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
minorVersion, messagingManager);
|
MINOR_VERSION, messagingManager);
|
||||||
return messagingManager;
|
return messagingManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
|
|
||||||
@@ -47,21 +48,43 @@ class PrivateMessageFactoryImpl implements PrivateMessageFactory {
|
|||||||
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
@Nullable String text, List<AttachmentHeader> headers)
|
@Nullable String text, List<AttachmentHeader> headers)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
// Validate the arguments
|
validateTextAndAttachmentHeaders(text, headers);
|
||||||
if (text == null) {
|
BdfList attachmentList = serialiseAttachmentHeaders(headers);
|
||||||
if (headers.isEmpty()) throw new IllegalArgumentException();
|
|
||||||
} else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
// Serialise the attachment headers
|
|
||||||
BdfList attachmentList = new BdfList();
|
|
||||||
for (AttachmentHeader a : headers) {
|
|
||||||
attachmentList.add(
|
|
||||||
BdfList.of(a.getMessageId(), a.getContentType()));
|
|
||||||
}
|
|
||||||
// Serialise the message
|
// Serialise the message
|
||||||
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
|
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
|
||||||
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
||||||
return new PrivateMessage(m, text != null, headers);
|
return new PrivateMessage(m, text != null, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
|
@Nullable String text, List<AttachmentHeader> headers,
|
||||||
|
long autoDeleteTimer) throws FormatException {
|
||||||
|
validateTextAndAttachmentHeaders(text, headers);
|
||||||
|
BdfList attachmentList = serialiseAttachmentHeaders(headers);
|
||||||
|
// Serialise the message
|
||||||
|
Long timer = autoDeleteTimer == NO_AUTO_DELETE_TIMER ?
|
||||||
|
null : autoDeleteTimer;
|
||||||
|
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList, timer);
|
||||||
|
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
||||||
|
return new PrivateMessage(m, text != null, headers, autoDeleteTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateTextAndAttachmentHeaders(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
|
if (text == null) {
|
||||||
|
if (headers.isEmpty()) throw new IllegalArgumentException();
|
||||||
|
} else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList serialiseAttachmentHeaders(List<AttachmentHeader> headers) {
|
||||||
|
BdfList attachmentList = new BdfList();
|
||||||
|
for (AttachmentHeader a : headers) {
|
||||||
|
attachmentList.add(
|
||||||
|
BdfList.of(a.getMessageId(), a.getContentType()));
|
||||||
|
}
|
||||||
|
return attachmentList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_L
|
|||||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_BYTES;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_BYTES;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
@@ -34,12 +35,14 @@ import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ
|
|||||||
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
||||||
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
|
||||||
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
|
||||||
|
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -99,7 +102,7 @@ class PrivateMessageValidator implements MessageValidator {
|
|||||||
|
|
||||||
private BdfMessageContext validateLegacyPrivateMessage(Message m,
|
private BdfMessageContext validateLegacyPrivateMessage(Message m,
|
||||||
BdfList body) throws FormatException {
|
BdfList body) throws FormatException {
|
||||||
// Private message text
|
// Client version 0.0: Private message text
|
||||||
checkSize(body, 1);
|
checkSize(body, 1);
|
||||||
String text = body.getString(0);
|
String text = body.getString(0);
|
||||||
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
@@ -113,8 +116,11 @@ class PrivateMessageValidator implements MessageValidator {
|
|||||||
|
|
||||||
private BdfMessageContext validatePrivateMessage(Message m, BdfList body)
|
private BdfMessageContext validatePrivateMessage(Message m, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
// Message type, optional private message text, attachment headers
|
// Client version 0.1 to 0.2: Message type, optional private message
|
||||||
checkSize(body, 3);
|
// text, attachment headers.
|
||||||
|
// Client version 0.3: Message type, optional private message text,
|
||||||
|
// attachment headers, optional auto-delete timer.
|
||||||
|
checkSize(body, 3, 4);
|
||||||
String text = body.getOptionalString(1);
|
String text = body.getOptionalString(1);
|
||||||
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
BdfList headers = body.getList(2);
|
BdfList headers = body.getList(2);
|
||||||
@@ -129,6 +135,10 @@ class PrivateMessageValidator implements MessageValidator {
|
|||||||
String contentType = header.getString(1);
|
String contentType = header.getString(1);
|
||||||
checkLength(contentType, 1, MAX_CONTENT_TYPE_BYTES);
|
checkLength(contentType, 1, MAX_CONTENT_TYPE_BYTES);
|
||||||
}
|
}
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 4) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
|
||||||
|
}
|
||||||
// Return the metadata
|
// Return the metadata
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
|
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
|
||||||
@@ -137,6 +147,9 @@ class PrivateMessageValidator implements MessageValidator {
|
|||||||
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
|
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
|
||||||
meta.put(MSG_KEY_HAS_TEXT, text != null);
|
meta.put(MSG_KEY_HAS_TEXT, text != null);
|
||||||
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
|
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
|
||||||
|
if (timer != NO_AUTO_DELETE_TIMER) {
|
||||||
|
meta.put(MSG_KEY_AUTO_DELETE_TIMER, timer);
|
||||||
|
}
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,23 @@ import org.briarproject.bramble.api.sync.Message;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||||
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
|
import static java.lang.Math.max;
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION;
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
|
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
||||||
@@ -39,7 +41,7 @@ import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class AbstractProtocolEngine<S extends Session>
|
abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||||
implements ProtocolEngine<S> {
|
implements ProtocolEngine<S> {
|
||||||
|
|
||||||
protected final DatabaseComponent db;
|
protected final DatabaseComponent db;
|
||||||
@@ -53,15 +55,23 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
private final IdentityManager identityManager;
|
private final IdentityManager identityManager;
|
||||||
private final MessageParser messageParser;
|
private final MessageParser messageParser;
|
||||||
private final MessageEncoder messageEncoder;
|
private final MessageEncoder messageEncoder;
|
||||||
|
private final AutoDeleteManager autoDeleteManager;
|
||||||
|
private final ConversationManager conversationManager;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
|
AbstractProtocolEngine(
|
||||||
|
DatabaseComponent db,
|
||||||
|
ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
PrivateGroupManager privateGroupManager,
|
PrivateGroupManager privateGroupManager,
|
||||||
PrivateGroupFactory privateGroupFactory,
|
PrivateGroupFactory privateGroupFactory,
|
||||||
GroupMessageFactory groupMessageFactory,
|
GroupMessageFactory groupMessageFactory,
|
||||||
IdentityManager identityManager, MessageParser messageParser,
|
IdentityManager identityManager,
|
||||||
MessageEncoder messageEncoder, MessageTracker messageTracker,
|
MessageParser messageParser,
|
||||||
|
MessageEncoder messageEncoder,
|
||||||
|
MessageTracker messageTracker,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
@@ -73,16 +83,11 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
this.messageParser = messageParser;
|
this.messageParser = messageParser;
|
||||||
this.messageEncoder = messageEncoder;
|
this.messageEncoder = messageEncoder;
|
||||||
this.messageTracker = messageTracker;
|
this.messageTracker = messageTracker;
|
||||||
|
this.autoDeleteManager = autoDeleteManager;
|
||||||
|
this.conversationManager = conversationManager;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
|
|
||||||
contactGroupId);
|
|
||||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isSubscribedPrivateGroup(Transaction txn, GroupId g)
|
boolean isSubscribedPrivateGroup(Transaction txn, GroupId g)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
if (!db.containsGroup(txn, g)) return false;
|
if (!db.containsGroup(txn, g)) return false;
|
||||||
@@ -90,6 +95,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
return group.getClientId().equals(PrivateGroupManager.CLIENT_ID);
|
return group.getClientId().equals(PrivateGroupManager.CLIENT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
boolean isValidDependency(S session, @Nullable MessageId dependency) {
|
boolean isValidDependency(S session, @Nullable MessageId dependency) {
|
||||||
MessageId expected = session.getLastRemoteMessageId();
|
MessageId expected = session.getLastRemoteMessageId();
|
||||||
if (dependency == null) return expected == null;
|
if (dependency == null) return expected == null;
|
||||||
@@ -99,54 +105,108 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
void setPrivateGroupVisibility(Transaction txn, S session,
|
void setPrivateGroupVisibility(Transaction txn, S session,
|
||||||
Visibility preferred) throws DbException, FormatException {
|
Visibility preferred) throws DbException, FormatException {
|
||||||
// Apply min of preferred visibility and client's visibility
|
// Apply min of preferred visibility and client's visibility
|
||||||
ContactId contactId = getContactId(txn, session.getContactGroupId());
|
ContactId contactId =
|
||||||
|
clientHelper.getContactId(txn, session.getContactGroupId());
|
||||||
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
||||||
contactId, CLIENT_ID, MAJOR_VERSION);
|
contactId, PrivateGroupManager.CLIENT_ID,
|
||||||
|
PrivateGroupManager.MAJOR_VERSION);
|
||||||
Visibility min = Visibility.min(preferred, client);
|
Visibility min = Visibility.min(preferred, client);
|
||||||
db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), min);
|
db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), min);
|
||||||
}
|
}
|
||||||
|
|
||||||
Message sendInviteMessage(Transaction txn, S session,
|
Message sendInviteMessage(Transaction txn, S s,
|
||||||
@Nullable String text, long timestamp, byte[] signature)
|
@Nullable String text, long timestamp, byte[] signature,
|
||||||
throws DbException {
|
long timer) throws DbException {
|
||||||
Group g = db.getGroup(txn, session.getPrivateGroupId());
|
Group g = db.getGroup(txn, s.getPrivateGroupId());
|
||||||
PrivateGroup privateGroup;
|
PrivateGroup privateGroup;
|
||||||
try {
|
try {
|
||||||
privateGroup = privateGroupFactory.parsePrivateGroup(g);
|
privateGroup = privateGroupFactory.parsePrivateGroup(g);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e); // Invalid group descriptor
|
throw new DbException(e); // Invalid group descriptor
|
||||||
}
|
}
|
||||||
Message m = messageEncoder.encodeInviteMessage(
|
Message m;
|
||||||
session.getContactGroupId(), privateGroup.getId(),
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
timestamp, privateGroup.getName(), privateGroup.getCreator(),
|
if (contactSupportsAutoDeletion(txn, c)) {
|
||||||
privateGroup.getSalt(), text, signature);
|
m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
|
||||||
sendMessage(txn, m, INVITE, privateGroup.getId(), true);
|
privateGroup.getId(), timestamp, privateGroup.getName(),
|
||||||
|
privateGroup.getCreator(), privateGroup.getSalt(), text,
|
||||||
|
signature, timer);
|
||||||
|
sendMessage(txn, m, INVITE, privateGroup.getId(), true, timer);
|
||||||
|
} else {
|
||||||
|
m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
|
||||||
|
privateGroup.getId(), timestamp, privateGroup.getName(),
|
||||||
|
privateGroup.getCreator(), privateGroup.getSalt(), text,
|
||||||
|
signature);
|
||||||
|
sendMessage(txn, m, INVITE, privateGroup.getId(), true,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message sendJoinMessage(Transaction txn, S session, boolean visibleInUi)
|
Message sendJoinMessage(Transaction txn, S s, boolean visibleInUi)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Message m = messageEncoder.encodeJoinMessage(
|
Message m;
|
||||||
session.getContactGroupId(), session.getPrivateGroupId(),
|
long localTimestamp = visibleInUi
|
||||||
getLocalTimestamp(session), session.getLastLocalMessageId());
|
? getTimestampForVisibleMessage(txn, s)
|
||||||
sendMessage(txn, m, JOIN, session.getPrivateGroupId(), visibleInUi);
|
: getTimestampForInvisibleMessage(s);
|
||||||
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
|
if (contactSupportsAutoDeletion(txn, c)) {
|
||||||
|
// Set auto-delete timer if manually accepting an invitation
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (visibleInUi) {
|
||||||
|
timer = autoDeleteManager
|
||||||
|
.getAutoDeleteTimer(txn, c, localTimestamp);
|
||||||
|
}
|
||||||
|
m = messageEncoder.encodeJoinMessage(s.getContactGroupId(),
|
||||||
|
s.getPrivateGroupId(), localTimestamp,
|
||||||
|
s.getLastLocalMessageId(), timer);
|
||||||
|
sendMessage(txn, m, JOIN, s.getPrivateGroupId(), visibleInUi,
|
||||||
|
timer);
|
||||||
|
} else {
|
||||||
|
m = messageEncoder.encodeJoinMessage(s.getContactGroupId(),
|
||||||
|
s.getPrivateGroupId(), localTimestamp,
|
||||||
|
s.getLastLocalMessageId());
|
||||||
|
sendMessage(txn, m, JOIN, s.getPrivateGroupId(), visibleInUi,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message sendLeaveMessage(Transaction txn, S session, boolean visibleInUi)
|
Message sendLeaveMessage(Transaction txn, S s, boolean visibleInUi)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Message m = messageEncoder.encodeLeaveMessage(
|
Message m;
|
||||||
session.getContactGroupId(), session.getPrivateGroupId(),
|
long localTimestamp = visibleInUi
|
||||||
getLocalTimestamp(session), session.getLastLocalMessageId());
|
? getTimestampForVisibleMessage(txn, s)
|
||||||
sendMessage(txn, m, LEAVE, session.getPrivateGroupId(), visibleInUi);
|
: getTimestampForInvisibleMessage(s);
|
||||||
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
|
if (contactSupportsAutoDeletion(txn, c)) {
|
||||||
|
// Set auto-delete timer if manually accepting an invitation
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (visibleInUi) {
|
||||||
|
timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
|
||||||
|
localTimestamp);
|
||||||
|
}
|
||||||
|
m = messageEncoder.encodeLeaveMessage(s.getContactGroupId(),
|
||||||
|
s.getPrivateGroupId(), localTimestamp,
|
||||||
|
s.getLastLocalMessageId(), timer);
|
||||||
|
sendMessage(txn, m, LEAVE, s.getPrivateGroupId(), visibleInUi,
|
||||||
|
timer);
|
||||||
|
} else {
|
||||||
|
m = messageEncoder.encodeLeaveMessage(s.getContactGroupId(),
|
||||||
|
s.getPrivateGroupId(), localTimestamp,
|
||||||
|
s.getLastLocalMessageId());
|
||||||
|
sendMessage(txn, m, LEAVE, s.getPrivateGroupId(), visibleInUi,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message sendAbortMessage(Transaction txn, S session) throws DbException {
|
Message sendAbortMessage(Transaction txn, S session) throws DbException {
|
||||||
Message m = messageEncoder.encodeAbortMessage(
|
Message m = messageEncoder.encodeAbortMessage(
|
||||||
session.getContactGroupId(), session.getPrivateGroupId(),
|
session.getContactGroupId(), session.getPrivateGroupId(),
|
||||||
getLocalTimestamp(session));
|
getTimestampForInvisibleMessage(session));
|
||||||
sendMessage(txn, m, ABORT, session.getPrivateGroupId(), false);
|
sendMessage(txn, m, ABORT, session.getPrivateGroupId(), false,
|
||||||
|
NO_AUTO_DELETE_TIMER);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +260,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
|
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
|
||||||
invite.getGroupName(), invite.getCreator(), invite.getSalt());
|
invite.getGroupName(), invite.getCreator(), invite.getSalt());
|
||||||
long timestamp =
|
long timestamp =
|
||||||
Math.max(clock.currentTimeMillis(), invite.getTimestamp() + 1);
|
max(clock.currentTimeMillis(), invite.getTimestamp() + 1);
|
||||||
// TODO: Create the join message on the crypto executor
|
// TODO: Create the join message on the crypto executor
|
||||||
LocalAuthor member = identityManager.getLocalAuthor(txn);
|
LocalAuthor member = identityManager.getLocalAuthor(txn);
|
||||||
GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
|
GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
|
||||||
@@ -210,18 +270,49 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
.addPrivateGroup(txn, privateGroup, joinMessage, false);
|
.addPrivateGroup(txn, privateGroup, joinMessage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
long getLocalTimestamp(S session) {
|
/**
|
||||||
return Math.max(clock.currentTimeMillis(),
|
* Returns a timestamp for a visible outgoing message. The timestamp is
|
||||||
Math.max(session.getLocalTimestamp(),
|
* later than the timestamp of any message sent or received so far in the
|
||||||
session.getInviteTimestamp()) + 1);
|
* conversation, and later than the {@link #getSessionTimestamp(Session)
|
||||||
|
* session timestamp}.
|
||||||
|
*/
|
||||||
|
long getTimestampForVisibleMessage(Transaction txn, S s)
|
||||||
|
throws DbException {
|
||||||
|
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||||
|
long conversationTimestamp =
|
||||||
|
conversationManager.getTimestampForOutgoingMessage(txn, c);
|
||||||
|
return max(conversationTimestamp, getSessionTimestamp(s) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a timestamp for an invisible outgoing message. The timestamp is
|
||||||
|
* later than the {@link #getSessionTimestamp(Session) session timestamp}.
|
||||||
|
*/
|
||||||
|
long getTimestampForInvisibleMessage(S s) {
|
||||||
|
return max(clock.currentTimeMillis(), getSessionTimestamp(s) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest timestamp of any message sent so far in the session,
|
||||||
|
* and any invite message sent or received so far in the session.
|
||||||
|
*/
|
||||||
|
private long getSessionTimestamp(S s) {
|
||||||
|
return max(s.getLocalTimestamp(), s.getInviteTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
void receiveAutoDeleteTimer(Transaction txn,
|
||||||
|
DeletableGroupInvitationMessage m) throws DbException {
|
||||||
|
ContactId c = getContactId(txn, m.getContactGroupId());
|
||||||
|
autoDeleteManager.receiveAutoDeleteTimer(txn, c, m.getAutoDeleteTimer(),
|
||||||
|
m.getTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(Transaction txn, Message m, MessageType type,
|
private void sendMessage(Transaction txn, Message m, MessageType type,
|
||||||
GroupId privateGroupId, boolean visibleInConversation)
|
GroupId privateGroupId, boolean visibleInConversation,
|
||||||
throws DbException {
|
long autoDeleteTimer) throws DbException {
|
||||||
BdfDictionary meta = messageEncoder
|
BdfDictionary meta = messageEncoder.encodeMetadata(type,
|
||||||
.encodeMetadata(type, privateGroupId, m.getTimestamp(), true,
|
privateGroupId, m.getTimestamp(), true, true,
|
||||||
true, visibleInConversation, false, false);
|
visibleInConversation, false, false, autoDeleteTimer);
|
||||||
try {
|
try {
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
@@ -229,4 +320,21 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
|
throws DbException {
|
||||||
|
try {
|
||||||
|
return clientHelper.getContactId(txn, contactGroupId);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contactSupportsAutoDeletion(Transaction txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
int minorVersion = clientVersioningManager.getClientMinorVersion(txn, c,
|
||||||
|
GroupInvitationManager.CLIENT_ID,
|
||||||
|
GroupInvitationManager.MAJOR_VERSION);
|
||||||
|
// Auto-delete was added in client version 0.1
|
||||||
|
return minorVersion >= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
import org.briarproject.briar.api.client.SessionId;
|
import org.briarproject.briar.api.client.SessionId;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||||
@@ -23,6 +25,7 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRespons
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED;
|
import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED;
|
||||||
@@ -36,26 +39,34 @@ import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
|
class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
|
||||||
|
|
||||||
CreatorProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
|
CreatorProtocolEngine(
|
||||||
|
DatabaseComponent db,
|
||||||
|
ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
PrivateGroupManager privateGroupManager,
|
PrivateGroupManager privateGroupManager,
|
||||||
PrivateGroupFactory privateGroupFactory,
|
PrivateGroupFactory privateGroupFactory,
|
||||||
GroupMessageFactory groupMessageFactory,
|
GroupMessageFactory groupMessageFactory,
|
||||||
IdentityManager identityManager, MessageParser messageParser,
|
IdentityManager identityManager,
|
||||||
MessageEncoder messageEncoder, MessageTracker messageTracker,
|
MessageParser messageParser,
|
||||||
|
MessageEncoder messageEncoder,
|
||||||
|
MessageTracker messageTracker,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
super(db, clientHelper, clientVersioningManager, privateGroupManager,
|
super(db, clientHelper, clientVersioningManager, privateGroupManager,
|
||||||
privateGroupFactory, groupMessageFactory, identityManager,
|
privateGroupFactory, groupMessageFactory, identityManager,
|
||||||
messageParser, messageEncoder, messageTracker, clock);
|
messageParser, messageEncoder, messageTracker,
|
||||||
|
autoDeleteManager, conversationManager, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CreatorSession onInviteAction(Transaction txn, CreatorSession s,
|
public CreatorSession onInviteAction(Transaction txn, CreatorSession s,
|
||||||
@Nullable String text, long timestamp, byte[] signature)
|
@Nullable String text, long timestamp, byte[] signature,
|
||||||
throws DbException {
|
long autoDeleteTimer) throws DbException {
|
||||||
switch (s.getState()) {
|
switch (s.getState()) {
|
||||||
case START:
|
case START:
|
||||||
return onLocalInvite(txn, s, text, timestamp, signature);
|
return onLocalInvite(txn, s, text, timestamp, signature,
|
||||||
|
autoDeleteTimer);
|
||||||
case INVITED:
|
case INVITED:
|
||||||
case JOINED:
|
case JOINED:
|
||||||
case LEFT:
|
case LEFT:
|
||||||
@@ -145,14 +156,16 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CreatorSession onLocalInvite(Transaction txn, CreatorSession s,
|
private CreatorSession onLocalInvite(Transaction txn, CreatorSession s,
|
||||||
@Nullable String text, long timestamp, byte[] signature)
|
@Nullable String text, long timestamp, byte[] signature,
|
||||||
throws DbException {
|
long autoDeleteTimer) throws DbException {
|
||||||
// Send an INVITE message
|
// Send an INVITE message
|
||||||
Message sent = sendInviteMessage(txn, s, text, timestamp, signature);
|
Message sent = sendInviteMessage(txn, s, text, timestamp, signature,
|
||||||
|
autoDeleteTimer);
|
||||||
// Track the message
|
// Track the message
|
||||||
messageTracker.trackOutgoingMessage(txn, sent);
|
messageTracker.trackOutgoingMessage(txn, sent);
|
||||||
// Move to the INVITED state
|
// Move to the INVITED state
|
||||||
long localTimestamp = Math.max(timestamp, getLocalTimestamp(s));
|
long localTimestamp =
|
||||||
|
max(timestamp, getTimestampForVisibleMessage(txn, s));
|
||||||
return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(),
|
return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(),
|
||||||
sent.getId(), s.getLastRemoteMessageId(), localTimestamp,
|
sent.getId(), s.getLastRemoteMessageId(), localTimestamp,
|
||||||
timestamp, INVITED);
|
timestamp, INVITED);
|
||||||
@@ -188,10 +201,13 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
|
|||||||
// Track the message
|
// Track the message
|
||||||
messageTracker.trackMessage(txn, m.getContactGroupId(),
|
messageTracker.trackMessage(txn, m.getContactGroupId(),
|
||||||
m.getTimestamp(), false);
|
m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
// Share the private group with the contact
|
// Share the private group with the contact
|
||||||
setPrivateGroupVisibility(txn, s, SHARED);
|
setPrivateGroupVisibility(txn, s, SHARED);
|
||||||
// Broadcast an event
|
// Broadcast an event
|
||||||
ContactId contactId = getContactId(txn, m.getContactGroupId());
|
ContactId contactId =
|
||||||
|
clientHelper.getContactId(txn, m.getContactGroupId());
|
||||||
txn.attach(new GroupInvitationResponseReceivedEvent(
|
txn.attach(new GroupInvitationResponseReceivedEvent(
|
||||||
createInvitationResponse(m, true), contactId));
|
createInvitationResponse(m, true), contactId));
|
||||||
// Move to the JOINED state
|
// Move to the JOINED state
|
||||||
@@ -212,8 +228,11 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
|
|||||||
// Track the message
|
// Track the message
|
||||||
messageTracker.trackMessage(txn, m.getContactGroupId(),
|
messageTracker.trackMessage(txn, m.getContactGroupId(),
|
||||||
m.getTimestamp(), false);
|
m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
// Broadcast an event
|
// Broadcast an event
|
||||||
ContactId contactId = getContactId(txn, m.getContactGroupId());
|
ContactId contactId =
|
||||||
|
clientHelper.getContactId(txn, m.getContactGroupId());
|
||||||
txn.attach(new GroupInvitationResponseReceivedEvent(
|
txn.attach(new GroupInvitationResponseReceivedEvent(
|
||||||
createInvitationResponse(m, false), contactId));
|
createInvitationResponse(m, false), contactId));
|
||||||
// Move to the START state
|
// Move to the START state
|
||||||
@@ -253,10 +272,10 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GroupInvitationResponse createInvitationResponse(
|
private GroupInvitationResponse createInvitationResponse(
|
||||||
GroupInvitationMessage m, boolean accept) {
|
DeletableGroupInvitationMessage m, boolean accept) {
|
||||||
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
|
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
|
||||||
return new GroupInvitationResponse(m.getId(), m.getContactGroupId(),
|
return new GroupInvitationResponse(m.getId(), m.getContactGroupId(),
|
||||||
m.getTimestamp(), false, false, false, false, sessionId,
|
m.getTimestamp(), false, false, false, false, sessionId,
|
||||||
accept, m.getPrivateGroupId());
|
accept, m.getPrivateGroupId(), m.getAutoDeleteTimer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,6 @@ package org.briarproject.briar.privategroup.invitation;
|
|||||||
|
|
||||||
interface GroupInvitationConstants {
|
interface GroupInvitationConstants {
|
||||||
|
|
||||||
// Group metadata keys
|
|
||||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
|
||||||
|
|
||||||
// Message metadata keys
|
// Message metadata keys
|
||||||
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
||||||
String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId";
|
String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId";
|
||||||
@@ -13,6 +10,7 @@ interface GroupInvitationConstants {
|
|||||||
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
|
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
|
||||||
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
|
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
|
||||||
String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted";
|
String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted";
|
||||||
|
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
|
||||||
|
|
||||||
// Session keys
|
// Session keys
|
||||||
String SESSION_KEY_IS_SESSION = "isSession";
|
String SESSION_KEY_IS_SESSION = "isSession";
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
||||||
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
|
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
||||||
@@ -123,13 +122,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary meta = new BdfDictionary();
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
|
||||||
try {
|
|
||||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
// If the contact belongs to any private groups, create a peer session
|
// If the contact belongs to any private groups, create a peer session
|
||||||
for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID,
|
for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID,
|
||||||
PrivateGroupManager.MAJOR_VERSION)) {
|
PrivateGroupManager.MAJOR_VERSION)) {
|
||||||
@@ -159,7 +152,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
SessionId sessionId = getSessionId(meta.getPrivateGroupId());
|
SessionId sessionId = getSessionId(meta.getPrivateGroupId());
|
||||||
StoredSession ss = getSession(txn, m.getGroupId(), sessionId);
|
StoredSession ss = getSession(txn, m.getGroupId(), sessionId);
|
||||||
// Handle the message
|
// Handle the message
|
||||||
Session session;
|
Session<?> session;
|
||||||
MessageId storageId;
|
MessageId storageId;
|
||||||
if (ss == null) {
|
if (ss == null) {
|
||||||
session = handleFirstMessage(txn, m, body, meta);
|
session = handleFirstMessage(txn, m, body, meta);
|
||||||
@@ -189,8 +182,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
results.values().iterator().next());
|
results.values().iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Session handleFirstMessage(Transaction txn, Message m, BdfList body,
|
private Session<?> handleFirstMessage(Transaction txn, Message m,
|
||||||
MessageMetadata meta) throws DbException, FormatException {
|
BdfList body, MessageMetadata meta)
|
||||||
|
throws DbException, FormatException {
|
||||||
GroupId privateGroupId = meta.getPrivateGroupId();
|
GroupId privateGroupId = meta.getPrivateGroupId();
|
||||||
MessageType type = meta.getMessageType();
|
MessageType type = meta.getMessageType();
|
||||||
if (type == INVITE) {
|
if (type == INVITE) {
|
||||||
@@ -206,7 +200,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Session handleMessage(Transaction txn, Message m, BdfList body,
|
private Session<?> handleMessage(Transaction txn, Message m, BdfList body,
|
||||||
MessageMetadata meta, BdfDictionary bdfSession)
|
MessageMetadata meta, BdfDictionary bdfSession)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
MessageType type = meta.getMessageType();
|
MessageType type = meta.getMessageType();
|
||||||
@@ -228,7 +222,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S extends Session> S handleMessage(Transaction txn, Message m,
|
private <S extends Session<?>> S handleMessage(Transaction txn, Message m,
|
||||||
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
|
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
if (type == INVITE) {
|
if (type == INVITE) {
|
||||||
@@ -256,15 +250,15 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeSession(Transaction txn, MessageId storageId,
|
private void storeSession(Transaction txn, MessageId storageId,
|
||||||
Session session) throws DbException, FormatException {
|
Session<?> session) throws DbException, FormatException {
|
||||||
BdfDictionary d = sessionEncoder.encodeSession(session);
|
BdfDictionary d = sessionEncoder.encodeSession(session);
|
||||||
clientHelper.mergeMessageMetadata(txn, storageId, d);
|
clientHelper.mergeMessageMetadata(txn, storageId, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendInvitation(GroupId privateGroupId, ContactId c,
|
public void sendInvitation(GroupId privateGroupId, ContactId c,
|
||||||
@Nullable String text, long timestamp, byte[] signature)
|
@Nullable String text, long timestamp, byte[] signature,
|
||||||
throws DbException {
|
long autoDeleteTimer) throws DbException {
|
||||||
SessionId sessionId = getSessionId(privateGroupId);
|
SessionId sessionId = getSessionId(privateGroupId);
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
@@ -287,7 +281,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
// Handle the invite action
|
// Handle the invite action
|
||||||
session = creatorEngine.onInviteAction(txn, session, text,
|
session = creatorEngine.onInviteAction(txn, session, text,
|
||||||
timestamp, signature);
|
timestamp, signature, autoDeleteTimer);
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, storageId, session);
|
storeSession(txn, storageId, session);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -354,7 +348,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S extends Session> S handleAction(Transaction txn,
|
private <S extends Session<?>> S handleAction(Transaction txn,
|
||||||
LocalAction type, S session, ProtocolEngine<S> engine)
|
LocalAction type, S session, ProtocolEngine<S> engine)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
if (type == LocalAction.INVITE) {
|
if (type == LocalAction.INVITE) {
|
||||||
@@ -420,7 +414,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
return new GroupInvitationRequest(m, contactGroupId,
|
return new GroupInvitationRequest(m, contactGroupId,
|
||||||
meta.getTimestamp(), meta.isLocal(), meta.isRead(),
|
meta.getTimestamp(), meta.isLocal(), meta.isRead(),
|
||||||
status.isSent(), status.isSeen(), sessionId, pg,
|
status.isSent(), status.isSeen(), sessionId, pg,
|
||||||
invite.getText(), meta.isAvailableToAnswer(), canBeOpened);
|
invite.getText(), meta.isAvailableToAnswer(), canBeOpened,
|
||||||
|
invite.getAutoDeleteTimer());
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupInvitationResponse parseInvitationResponse(
|
private GroupInvitationResponse parseInvitationResponse(
|
||||||
@@ -430,7 +425,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
return new GroupInvitationResponse(m, contactGroupId,
|
return new GroupInvitationResponse(m, contactGroupId,
|
||||||
meta.getTimestamp(), meta.isLocal(), meta.isRead(),
|
meta.getTimestamp(), meta.isLocal(), meta.isRead(),
|
||||||
status.isSent(), status.isSeen(), sessionId, accept,
|
status.isSent(), status.isSeen(), sessionId, accept,
|
||||||
meta.getPrivateGroupId());
|
meta.getPrivateGroupId(), meta.getAutoDeleteTimer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -508,7 +503,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
SessionId sessionId = getSessionId(privateGroupId);
|
SessionId sessionId = getSessionId(privateGroupId);
|
||||||
StoredSession ss = getSession(txn, contactGroupId, sessionId);
|
StoredSession ss = getSession(txn, contactGroupId, sessionId);
|
||||||
// Create or parse the session
|
// Create or parse the session
|
||||||
Session session;
|
Session<?> session;
|
||||||
MessageId storageId;
|
MessageId storageId;
|
||||||
if (ss == null) {
|
if (ss == null) {
|
||||||
// If there's no session the contact must be a peer,
|
// If there's no session the contact must be a peer,
|
||||||
@@ -543,7 +538,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
StoredSession ss = getSession(txn, contactGroupId, sessionId);
|
StoredSession ss = getSession(txn, contactGroupId, sessionId);
|
||||||
if (ss == null) continue; // No session for this contact
|
if (ss == null) continue; // No session for this contact
|
||||||
// Handle the action
|
// Handle the action
|
||||||
Session session = handleAction(txn, LocalAction.LEAVE,
|
Session<?> session = handleAction(txn, LocalAction.LEAVE,
|
||||||
contactGroupId, ss.bdfSession);
|
contactGroupId, ss.bdfSession);
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, ss.storageId, session);
|
storeSession(txn, ss.storageId, session);
|
||||||
@@ -553,7 +548,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Session handleAction(Transaction txn, LocalAction a,
|
private Session<?> handleAction(Transaction txn, LocalAction a,
|
||||||
GroupId contactGroupId, BdfDictionary bdfSession)
|
GroupId contactGroupId, BdfDictionary bdfSession)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
Role role = sessionParser.getRole(bdfSession);
|
Role role = sessionParser.getRole(bdfSession);
|
||||||
@@ -613,7 +608,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
||||||
Map<GroupId, Visibility> m = new HashMap<>();
|
Map<GroupId, Visibility> m = new HashMap<>();
|
||||||
for (BdfDictionary d : results.values()) {
|
for (BdfDictionary d : results.values()) {
|
||||||
Session s = sessionParser.parseSession(contactGroupId, d);
|
Session<?> s = sessionParser.parseSession(contactGroupId, d);
|
||||||
m.put(s.getPrivateGroupId(), s.getState().getVisibility());
|
m.put(s.getPrivateGroupId(), s.getState().getVisibility());
|
||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
@@ -644,7 +639,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
Map<GroupId, DeletableSession> sessions = new HashMap<>();
|
Map<GroupId, DeletableSession> sessions = new HashMap<>();
|
||||||
for (BdfDictionary d : metadata.values()) {
|
for (BdfDictionary d : metadata.values()) {
|
||||||
if (!sessionParser.isSession(d)) continue;
|
if (!sessionParser.isSession(d)) continue;
|
||||||
Session session;
|
Session<?> session;
|
||||||
try {
|
try {
|
||||||
session = sessionParser.parseSession(g, d);
|
session = sessionParser.parseSession(g, d);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
@@ -673,7 +668,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
getSessionId(messageMetadata.getPrivateGroupId());
|
getSessionId(messageMetadata.getPrivateGroupId());
|
||||||
StoredSession ss = getSession(txn1, g, sessionId);
|
StoredSession ss = getSession(txn1, g, sessionId);
|
||||||
if (ss == null) throw new DbException();
|
if (ss == null) throw new DbException();
|
||||||
Session session = sessionParser
|
Session<?> session = sessionParser
|
||||||
.parseSession(g, metadata.get(ss.storageId));
|
.parseSession(g, metadata.get(ss.storageId));
|
||||||
sessions.put(session.getPrivateGroupId(),
|
sessions.put(session.getPrivateGroupId(),
|
||||||
new DeletableSession(session.getState()));
|
new DeletableSession(session.getState()));
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
|
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_TEXT_LENGTH;
|
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_TEXT_LENGTH;
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||||
@@ -34,6 +35,7 @@ import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
|||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
|
||||||
|
import static org.briarproject.briar.util.ValidationUtils.validateAutoDeleteTimer;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -71,8 +73,11 @@ class GroupInvitationValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
private BdfMessageContext validateInviteMessage(Message m, BdfList body)
|
private BdfMessageContext validateInviteMessage(Message m, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
// Message type, creator, group name, salt, optional text, signature
|
// Client version 0.0: Message type, creator, group name, salt,
|
||||||
checkSize(body, 6);
|
// optional text, signature.
|
||||||
|
// Client version 0.1: Message type, creator, group name, salt,
|
||||||
|
// optional text, signature, optional auto-delete timer.
|
||||||
|
checkSize(body, 6, 7);
|
||||||
BdfList creatorList = body.getList(1);
|
BdfList creatorList = body.getList(1);
|
||||||
String groupName = body.getString(2);
|
String groupName = body.getString(2);
|
||||||
checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH);
|
checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH);
|
||||||
@@ -82,6 +87,10 @@ class GroupInvitationValidator extends BdfMessageValidator {
|
|||||||
checkLength(text, 1, MAX_GROUP_INVITATION_TEXT_LENGTH);
|
checkLength(text, 1, MAX_GROUP_INVITATION_TEXT_LENGTH);
|
||||||
byte[] signature = body.getRaw(5);
|
byte[] signature = body.getRaw(5);
|
||||||
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
|
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 7) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(6));
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the creator and create the private group
|
// Validate the creator and create the private group
|
||||||
Author creator = clientHelper.parseAndValidateAuthor(creatorList);
|
Author creator = clientHelper.parseAndValidateAuthor(creatorList);
|
||||||
@@ -102,20 +111,29 @@ class GroupInvitationValidator extends BdfMessageValidator {
|
|||||||
// Create the metadata
|
// Create the metadata
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(INVITE,
|
BdfDictionary meta = messageEncoder.encodeMetadata(INVITE,
|
||||||
privateGroup.getId(), m.getTimestamp(), false, false, false,
|
privateGroup.getId(), m.getTimestamp(), false, false, false,
|
||||||
false, false);
|
false, false, timer);
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfMessageContext validateJoinMessage(Message m, BdfList body)
|
private BdfMessageContext validateJoinMessage(Message m, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
checkSize(body, 3);
|
// Client version 0.0: Message type, private group ID, optional
|
||||||
|
// previous message ID.
|
||||||
|
// Client version 0.1: Message type, private group ID, optional
|
||||||
|
// previous message ID, optional auto-delete timer.
|
||||||
|
checkSize(body, 3, 4);
|
||||||
byte[] privateGroupId = body.getRaw(1);
|
byte[] privateGroupId = body.getRaw(1);
|
||||||
checkLength(privateGroupId, UniqueId.LENGTH);
|
checkLength(privateGroupId, UniqueId.LENGTH);
|
||||||
byte[] previousMessageId = body.getOptionalRaw(2);
|
byte[] previousMessageId = body.getOptionalRaw(2);
|
||||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 4) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
|
||||||
|
}
|
||||||
|
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(JOIN,
|
BdfDictionary meta = messageEncoder.encodeMetadata(JOIN,
|
||||||
new GroupId(privateGroupId), m.getTimestamp(), false, false,
|
new GroupId(privateGroupId), m.getTimestamp(), false, false,
|
||||||
false, false, false);
|
false, false, false, timer);
|
||||||
if (previousMessageId == null) {
|
if (previousMessageId == null) {
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
} else {
|
} else {
|
||||||
@@ -127,14 +145,23 @@ class GroupInvitationValidator extends BdfMessageValidator {
|
|||||||
|
|
||||||
private BdfMessageContext validateLeaveMessage(Message m, BdfList body)
|
private BdfMessageContext validateLeaveMessage(Message m, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
checkSize(body, 3);
|
// Client version 0.0: Message type, private group ID, optional
|
||||||
|
// previous message ID.
|
||||||
|
// Client version 0.1: Message type, private group ID, optional
|
||||||
|
// previous message ID, optional auto-delete timer.
|
||||||
|
checkSize(body, 3, 4);
|
||||||
byte[] privateGroupId = body.getRaw(1);
|
byte[] privateGroupId = body.getRaw(1);
|
||||||
checkLength(privateGroupId, UniqueId.LENGTH);
|
checkLength(privateGroupId, UniqueId.LENGTH);
|
||||||
byte[] previousMessageId = body.getOptionalRaw(2);
|
byte[] previousMessageId = body.getOptionalRaw(2);
|
||||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||||
|
long timer = NO_AUTO_DELETE_TIMER;
|
||||||
|
if (body.size() == 4) {
|
||||||
|
timer = validateAutoDeleteTimer(body.getOptionalLong(3));
|
||||||
|
}
|
||||||
|
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE,
|
BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE,
|
||||||
new GroupId(privateGroupId), m.getTimestamp(), false, false,
|
new GroupId(privateGroupId), m.getTimestamp(), false, false,
|
||||||
false, false, false);
|
false, false, false, timer);
|
||||||
if (previousMessageId == null) {
|
if (previousMessageId == null) {
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
} else {
|
} else {
|
||||||
@@ -151,7 +178,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
|
|||||||
checkLength(privateGroupId, UniqueId.LENGTH);
|
checkLength(privateGroupId, UniqueId.LENGTH);
|
||||||
BdfDictionary meta = messageEncoder.encodeMetadata(ABORT,
|
BdfDictionary meta = messageEncoder.encodeMetadata(ABORT,
|
||||||
new GroupId(privateGroupId), m.getTimestamp(), false, false,
|
new GroupId(privateGroupId), m.getTimestamp(), false, false,
|
||||||
false, false, false);
|
false, false, false, NO_AUTO_DELETE_TIMER);
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class InviteMessage extends GroupInvitationMessage {
|
class InviteMessage extends DeletableGroupInvitationMessage {
|
||||||
|
|
||||||
private final String groupName;
|
private final String groupName;
|
||||||
private final Author creator;
|
private final Author creator;
|
||||||
@@ -20,8 +20,8 @@ class InviteMessage extends GroupInvitationMessage {
|
|||||||
|
|
||||||
InviteMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
|
InviteMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp, String groupName, Author creator, byte[] salt,
|
long timestamp, String groupName, Author creator, byte[] salt,
|
||||||
@Nullable String text, byte[] signature) {
|
@Nullable String text, byte[] signature, long autoDeleteTimer) {
|
||||||
super(id, contactGroupId, privateGroupId, timestamp);
|
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
|
||||||
this.groupName = groupName;
|
this.groupName = groupName;
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
this.salt = salt;
|
this.salt = salt;
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import org.briarproject.bramble.api.sync.Message;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
import org.briarproject.briar.api.client.SessionId;
|
import org.briarproject.briar.api.client.SessionId;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||||
@@ -41,22 +43,30 @@ import static org.briarproject.briar.privategroup.invitation.InviteeState.START;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
|
class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
|
||||||
|
|
||||||
InviteeProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
|
InviteeProtocolEngine(
|
||||||
|
DatabaseComponent db,
|
||||||
|
ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
PrivateGroupManager privateGroupManager,
|
PrivateGroupManager privateGroupManager,
|
||||||
PrivateGroupFactory privateGroupFactory,
|
PrivateGroupFactory privateGroupFactory,
|
||||||
GroupMessageFactory groupMessageFactory,
|
GroupMessageFactory groupMessageFactory,
|
||||||
IdentityManager identityManager, MessageParser messageParser,
|
IdentityManager identityManager,
|
||||||
MessageEncoder messageEncoder, MessageTracker messageTracker,
|
MessageParser messageParser,
|
||||||
|
MessageEncoder messageEncoder,
|
||||||
|
MessageTracker messageTracker,
|
||||||
|
AutoDeleteManager autoDeleteManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
super(db, clientHelper, clientVersioningManager, privateGroupManager,
|
super(db, clientHelper, clientVersioningManager, privateGroupManager,
|
||||||
privateGroupFactory, groupMessageFactory, identityManager,
|
privateGroupFactory, groupMessageFactory, identityManager,
|
||||||
messageParser, messageEncoder, messageTracker, clock);
|
messageParser, messageEncoder, messageTracker,
|
||||||
|
autoDeleteManager, conversationManager, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InviteeSession onInviteAction(Transaction txn, InviteeSession s,
|
public InviteeSession onInviteAction(Transaction txn, InviteeSession s,
|
||||||
@Nullable String text, long timestamp, byte[] signature) {
|
@Nullable String text, long timestamp, byte[] signature,
|
||||||
|
long autoDeleteTimer) {
|
||||||
throw new UnsupportedOperationException(); // Invalid in this role
|
throw new UnsupportedOperationException(); // Invalid in this role
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +240,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
|
|||||||
// The timestamp must be higher than the last invite message, if any
|
// The timestamp must be higher than the last invite message, if any
|
||||||
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
|
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
|
||||||
// Check that the contact is the creator
|
// Check that the contact is the creator
|
||||||
ContactId contactId = getContactId(txn, s.getContactGroupId());
|
ContactId contactId =
|
||||||
|
clientHelper.getContactId(txn, s.getContactGroupId());
|
||||||
Author contact = db.getContact(txn, contactId).getAuthor();
|
Author contact = db.getContact(txn, contactId).getAuthor();
|
||||||
if (!contact.getId().equals(m.getCreator().getId()))
|
if (!contact.getId().equals(m.getCreator().getId()))
|
||||||
return abort(txn, s);
|
return abort(txn, s);
|
||||||
@@ -240,6 +251,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
|
|||||||
// Track the message
|
// Track the message
|
||||||
messageTracker.trackMessage(txn, m.getContactGroupId(),
|
messageTracker.trackMessage(txn, m.getContactGroupId(),
|
||||||
m.getTimestamp(), false);
|
m.getTimestamp(), false);
|
||||||
|
// Receive the auto-delete timer
|
||||||
|
receiveAutoDeleteTimer(txn, m);
|
||||||
// Broadcast an event
|
// Broadcast an event
|
||||||
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
|
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
|
||||||
m.getGroupName(), m.getCreator(), m.getSalt());
|
m.getGroupName(), m.getCreator(), m.getSalt());
|
||||||
@@ -330,7 +343,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
|
|||||||
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
|
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
|
||||||
return new GroupInvitationRequest(m.getId(), m.getContactGroupId(),
|
return new GroupInvitationRequest(m.getId(), m.getContactGroupId(),
|
||||||
m.getTimestamp(), false, false, false, false, sessionId, pg,
|
m.getTimestamp(), false, false, false, false, sessionId, pg,
|
||||||
m.getText(), true, false);
|
m.getText(), true, false, m.getAutoDeleteTimer());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class JoinMessage extends GroupInvitationMessage {
|
class JoinMessage extends DeletableGroupInvitationMessage {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final MessageId previousMessageId;
|
private final MessageId previousMessageId;
|
||||||
|
|
||||||
JoinMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
|
JoinMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp, @Nullable MessageId previousMessageId) {
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
super(id, contactGroupId, privateGroupId, timestamp);
|
long autoDeleteTimer) {
|
||||||
|
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
|
||||||
this.previousMessageId = previousMessageId;
|
this.previousMessageId = previousMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class LeaveMessage extends GroupInvitationMessage {
|
class LeaveMessage extends DeletableGroupInvitationMessage {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final MessageId previousMessageId;
|
private final MessageId previousMessageId;
|
||||||
|
|
||||||
LeaveMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
|
LeaveMessage(MessageId id, GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp, @Nullable MessageId previousMessageId) {
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
super(id, contactGroupId, privateGroupId, timestamp);
|
long autoDeleteTimer) {
|
||||||
|
super(id, contactGroupId, privateGroupId, timestamp, autoDeleteTimer);
|
||||||
this.previousMessageId = previousMessageId;
|
this.previousMessageId = previousMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface MessageEncoder {
|
|||||||
|
|
||||||
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
|
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
|
||||||
long timestamp, boolean local, boolean read, boolean visible,
|
long timestamp, boolean local, boolean read, boolean visible,
|
||||||
boolean available, boolean accepted);
|
boolean available, boolean accepted, long autoDeleteTimer);
|
||||||
|
|
||||||
void setVisibleInUi(BdfDictionary meta, boolean visible);
|
void setVisibleInUi(BdfDictionary meta, boolean visible);
|
||||||
|
|
||||||
@@ -22,16 +22,49 @@ interface MessageEncoder {
|
|||||||
|
|
||||||
void setInvitationAccepted(BdfDictionary meta, boolean accepted);
|
void setInvitationAccepted(BdfDictionary meta, boolean accepted);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes an invite message without an auto-delete timer.
|
||||||
|
*/
|
||||||
Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId,
|
Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp, String groupName, Author creator, byte[] salt,
|
long timestamp, String groupName, Author creator, byte[] salt,
|
||||||
@Nullable String text, byte[] signature);
|
@Nullable String text, byte[] signature);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes an invite message with an optional auto-delete timer. This
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
|
Message encodeInviteMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
|
long timestamp, String groupName, Author creator, byte[] salt,
|
||||||
|
@Nullable String text, byte[] signature, long autoDeleteTimer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a join message without an auto-delete timer.
|
||||||
|
*/
|
||||||
Message encodeJoinMessage(GroupId contactGroupId, GroupId privateGroupId,
|
Message encodeJoinMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp, @Nullable MessageId previousMessageId);
|
long timestamp, @Nullable MessageId previousMessageId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a join message with an optional auto-delete timer. This
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
|
Message encodeJoinMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
|
long autoDeleteTimer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a leave message without an auto-delete timer.
|
||||||
|
*/
|
||||||
Message encodeLeaveMessage(GroupId contactGroupId, GroupId privateGroupId,
|
Message encodeLeaveMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp, @Nullable MessageId previousMessageId);
|
long timestamp, @Nullable MessageId previousMessageId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a leave message with an optional auto-delete timer. This
|
||||||
|
* requires the contact to support client version 0.1 or higher.
|
||||||
|
*/
|
||||||
|
Message encodeLeaveMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
|
long timestamp, @Nullable MessageId previousMessageId,
|
||||||
|
long autoDeleteTimer);
|
||||||
|
|
||||||
Message encodeAbortMessage(GroupId contactGroupId, GroupId privateGroupId,
|
Message encodeAbortMessage(GroupId contactGroupId, GroupId privateGroupId,
|
||||||
long timestamp);
|
long timestamp);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user