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