Compare commits

...

59 Commits

Author SHA1 Message Date
akwizgran
67593e2ad0 Add transport keys for reintroduced contact. 2021-06-22 11:01:40 +01:00
Torsten Grote
d4c1e132f7 Merge branch '2077-anything-to-send' into '1802-sync-via-removable-storage'
Add DB method for checking whether there's anything to send

See merge request briar/briar!1485
2021-06-17 13:30:31 +00:00
akwizgran
6b976df6a8 Add RemovableDriveManager method. 2021-06-17 13:01:33 +01:00
Torsten Grote
3e4db3b9da Merge branch '2045-flexible-sync' into '1802-sync-via-removable-storage'
Make retransmissions in the sync protocol more flexible

See merge request briar/briar!1482
2021-06-16 17:40:25 +00:00
akwizgran
0bf59eec20 Add comment explaining second client versioning message. 2021-06-16 16:26:29 +01:00
akwizgran
9f828a2222 Add DB method for checking whether there's anything to send 2021-06-16 16:25:11 +01:00
Torsten Grote
7be77b8c60 Merge branch '2038-transport-key-agreement-client' into '1802-sync-via-removable-storage'
Add transport key agreement client

See merge request briar/briar!1474
2021-06-16 12:28:00 +00:00
akwizgran
d5853e8403 Add integration test for eager retransmission. 2021-06-16 12:29:49 +01:00
akwizgran
32e9bf01ec Update DB method that gets total size of messages to send. 2021-06-16 12:29:49 +01:00
akwizgran
a5ce400341 Use eager retransmission if the transport is lossy and cheap. 2021-06-16 12:29:49 +01:00
akwizgran
a960bfb2c1 Add tests for eager retransmission. 2021-06-16 12:29:49 +01:00
akwizgran
847650f280 Replace inner classes with lambdas. 2021-06-16 12:29:49 +01:00
akwizgran
77a3199aac Update SimplexOutgoingSession to support sending unacked messages. 2021-06-16 12:29:49 +01:00
akwizgran
9a58b37ce2 Add database methods for sending unacked messages. 2021-06-16 12:29:49 +01:00
Torsten Grote
608e1eac6b Merge branch '2071-removable-drive-task-refactoring' into '1802-sync-via-removable-storage'
Refactor removable drive task management

See merge request briar/briar!1480
2021-06-15 12:23:27 +00:00
akwizgran
09de768e7e Merge branch '2038-key-manager-methods' into '1802-sync-via-removable-storage'
Key manager changes to support transport key agreement client

See merge request briar/briar!1473
2021-06-15 10:55:31 +00:00
akwizgran
faab80f0ea Hold lock while calling notifyObservers(). 2021-06-15 11:47:10 +01:00
akwizgran
07162cad8b Refactor removable drive tasks. 2021-06-15 11:44:10 +01:00
akwizgran
ac0fc21e6e Merge branch '2038-db-methods' into '1802-sync-via-removable-storage'
New DB methods to support transport key agreement client

See merge request briar/briar!1472
2021-06-10 15:28:20 +00:00
akwizgran
dab736ce0e Merge branch '1802-defer-delivery' into '1802-sync-via-removable-storage'
Allow sync clients to defer delivery of messages

See merge request briar/briar!1471
2021-06-10 15:27:08 +00:00
Torsten Grote
bcbc96dc2d Merge branch '2037-create-removabledriveviewmodel' into '1802-sync-via-removable-storage'
Add RemovableDriveViewModel

See merge request briar/briar!1475
2021-06-09 11:32:10 +00:00
akwizgran
a72e92de24 Timestamp isn't needed for deriving root key. 2021-06-09 10:08:07 +01:00
Daniel Lublin
1ddcd6cfff Make pkg private 2021-06-08 20:31:23 +02:00
akwizgran
5dfd9e3546 Make tests more readable. 2021-06-08 17:13:18 +01:00
akwizgran
e05575b956 Add unit tests for addRotationKeys() methods. 2021-06-08 15:51:29 +01:00
Daniel Lublin
fd810f5c16 Move to new removabledrive package 2021-06-08 12:25:09 +02:00
Daniel Lublin
3f5e131250 Use US locale for now 2021-06-08 12:18:33 +02:00
Daniel Lublin
3ee516599d Add initial RemovableDriveViewModel 2021-06-07 13:17:50 +02:00
akwizgran
c703d90636 Remove unused remote timestamp from session. 2021-06-01 14:50:14 +01:00
akwizgran
e228b9fcbf Add transport key agreement client. 2021-06-01 14:18:02 +01:00
akwizgran
6e6cadd3ad Refactor KeyManager startup so managers are created earlier. 2021-06-01 14:17:12 +01:00
akwizgran
9cc8d44778 Add a key manager method for adding a single set of transport keys. 2021-06-01 11:34:27 +01:00
akwizgran
ee6f571c31 Add a DB method for checking whether transport keys exist. 2021-06-01 11:34:26 +01:00
akwizgran
2ac3bdd3ae Add database method for getting transports with keys. 2021-06-01 11:34:26 +01:00
akwizgran
e35ffe0cf0 Add javadocs for message states. 2021-06-01 11:33:06 +01:00
akwizgran
8a04d8edc4 Allow sync clients to defer delivery of messages. 2021-06-01 11:24:55 +01:00
akwizgran
a5fb3bb4a4 Merge branch '2016-2017-2018-removable-drive-reader-writer' into '1802-sync-via-removable-storage'
Create removable drive manager and reader/writer tasks

See merge request briar/briar!1458
2021-05-11 14:01:53 +00:00
akwizgran
eae329cdfa Refactor manager and tasks to remove reliance on files. 2021-05-11 12:19:16 +01:00
akwizgran
0ce0551f0d Update progress of writer task. 2021-05-11 12:19:16 +01:00
akwizgran
a198e7d08e Ensure that observers see the final state even if they're added late. 2021-05-11 12:19:16 +01:00
akwizgran
bca6f1506e Add integration test for syncing via removable drives. 2021-05-11 12:19:16 +01:00
akwizgran
e420201b00 Implement RemovableDriveWriterTask, except for progress updates. 2021-05-11 12:19:16 +01:00
akwizgran
03248d04e5 Fix typo in class names. 2021-05-11 12:19:16 +01:00
akwizgran
2c39b02644 Implement RemovableDriverReaderTask. 2021-05-11 12:19:16 +01:00
akwizgran
c9c6f3682c Add task factory. 2021-05-11 12:19:16 +01:00
akwizgran
8f4a0ef030 Add removable drive manager with placeholder task implementations. 2021-05-11 12:19:14 +01:00
akwizgran
5fe22bcd57 Merge branch '2035-android-removable-drive-plugin' into '1802-sync-via-removable-storage'
Add Android implementation of RemovableDrivePlugin

See merge request briar/briar!1457
2021-05-11 11:13:14 +00:00
akwizgran
b4880af7e2 Add Android implementation of RemovableDrivePlugin. 2021-05-10 14:19:24 +01:00
akwizgran
51d21bd669 Decouple RemovableDrivePlugin from FileConstants. 2021-05-10 13:48:12 +01:00
Torsten Grote
b8f3728a0d Merge branch '2015-removable-drive-plugin' into '1802-sync-via-removable-storage'
Create RemovableDrivePlugin

See merge request briar/briar!1454
2021-05-10 12:47:21 +00:00
akwizgran
bbfd4f137d Merge branch '2013-db-method-for-amount-of-data-to-sync' into '1802-sync-via-removable-storage'
Add DB method for getting amount of data to sync

See merge request briar/briar!1452
2021-05-10 12:00:11 +00:00
Daniel Lublin
7e3ca76dd1 Merge branch '2014-messages-sent-event' into '1802-sync-via-removable-storage'
Update MessagesSentEvent to include amount of data sent

See merge request briar/briar!1453
2021-05-10 11:44:27 +00:00
akwizgran
524c8d26f8 Don't inject default RemovableDrivePluginFactory on Android. 2021-05-07 17:48:39 +01:00
akwizgran
7eccf7dac1 Decouple removable drive plugin from java.io.File for portability. 2021-05-07 17:36:10 +01:00
akwizgran
0bc06248ed Clean up plugin injection code, remove unused module. 2021-05-06 16:59:45 +01:00
akwizgran
c999f05cc7 Configure removable drive plugin for Android. 2021-05-06 16:59:45 +01:00
akwizgran
428269b312 Add removable drive plugin. 2021-05-06 16:59:45 +01:00
akwizgran
588e05ce83 Update MessagesSentEvent to include amount of data sent. 2021-05-06 16:20:15 +01:00
akwizgran
f7875c99b6 Add DB method for getting amount of data to sync. 2021-05-05 17:52:37 +01:00
99 changed files with 4383 additions and 410 deletions

View File

@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
@Inject @Inject
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor, AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
AndroidWakeLockManager wakeLockManager, AndroidWakeLockManager wakeLockManager,

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
private final Application app;
AndroidRemovableDrivePlugin(Application app, int maxLatency) {
super(maxLatency);
this.app = app;
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openInputStream(Uri.parse(uri));
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openOutputStream(Uri.parse(uri));
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class AndroidRemovableDrivePluginFactory implements
SimplexPluginFactory {
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
private final Application app;
@Inject
AndroidRemovableDrivePluginFactory(Application app) {
this.app = app;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new AndroidRemovableDrivePlugin(app, MAX_LATENCY);
}
}

View File

@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private final Application app; private final Application app;
@Inject @Inject
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor, AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
EventBus eventBus, EventBus eventBus,
BackoffFactory backoffFactory, BackoffFactory backoffFactory,

View File

@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private final File torDirectory; private final File torDirectory;
@Inject @Inject
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
Application app, Application app,
NetworkManager networkManager, NetworkManager networkManager,

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface Consumer<T> {
void accept(T t);
}

View File

@@ -32,28 +32,31 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* <p>
* If an unexpected exception occurs while handling data that is assumed
* to be valid (e.g. locally created metadata), it may be sensible to
* rethrow the unexpected exception as a DbException so that delivery is
* attempted again at next startup. This will allow delivery to succeed if
* the unexpected exception was caused by a bug that has subsequently been
* fixed.
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @return Whether or not this message should be shared * @throws DbException if a database error occurs while delivering the
* @throws DbException Should only be used for real database errors. * message. Delivery will be attempted again at next startup. Throwing
* If this is thrown, delivery will be attempted again at next startup, * this exception has the same effect as returning
* whereas if a FormatException is thrown, the message will be permanently * {@link DeliveryAction#DEFER}.
* invalidated. * @throws FormatException if the message is invalid in the context of its
* @throws FormatException Use this for any non-database error * dependencies. The message and any dependents will be marked as invalid
* that occurs while handling remotely created data. * and deleted along with their metadata. Throwing this exception has the
* This includes errors that occur while handling locally created data * same effect as returning {@link DeliveryAction#REJECT}.
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Never rethrow DbException as FormatException!
*/ */
protected abstract boolean incomingMessage(Transaction txn, Message m, protected abstract DeliveryAction incomingMessage(Transaction txn,
BdfList body, BdfDictionary meta) throws DbException, Message m, BdfList body, BdfDictionary meta)
FormatException; throws DbException, FormatException;
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
BdfList body = clientHelper.toList(m); BdfList body = clientHelper.toList(m);
BdfDictionary metaDictionary = metadataParser.parse(meta); BdfDictionary metaDictionary = metadataParser.parse(meta);

View File

@@ -118,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager {
KeySetId addTransportKeys(Transaction txn, PendingContactId p, KeySetId addTransportKeys(Transaction txn, PendingContactId p,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(Transaction txn, ContactId c,
int maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
@@ -150,6 +162,16 @@ public interface DatabaseComponent extends TransactionManager {
boolean containsPendingContact(Transaction txn, PendingContactId p) boolean containsPendingContact(Transaction txn, PendingContactId p)
throws DbException; throws DbException;
/**
* Returns true if the database contains keys for communicating with the
* given contact over the given transport. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t)
throws DbException;
/** /**
* Deletes the message with the given ID. Unlike * Deletes the message with the given ID. Unlike
* {@link #removeMessage(Transaction, MessageId)}, the message ID, * {@link #removeMessage(Transaction, MessageId)}, the message ID,
@@ -180,6 +202,18 @@ public interface DatabaseComponent extends TransactionManager {
Collection<Message> generateBatch(Transaction txn, ContactId c, Collection<Message> generateBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
/**
* Returns a batch of messages for the given contact containing the
* messages with the given IDs, for transmission over a transport with
* the given maximum latency.
* <p/>
* If any of the given messages are not in the database or are not visible
* to the contact, they are omitted from the batch without throwing an
* exception.
*/
Collection<Message> generateBatch(Transaction txn, ContactId c,
Collection<MessageId> ids, int maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
* transport with the given maximum latency, or null if there are no * transport with the given maximum latency, or null if there are no
@@ -426,6 +460,27 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact, together with their raw lengths. This may include
* messages that have already been sent and are not yet due for
* retransmission.
* <p/>
* Read-only.
*/
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages
* that have already been sent and are not yet due for retransmission.
* <p/>
* Read-only.
*/
long getUnackedMessageBytesToSend(Transaction txn, ContactId c)
throws DbException;
/** /**
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE} * message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
@@ -483,6 +538,16 @@ public interface DatabaseComponent extends TransactionManager {
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t) Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
throws DbException; throws DbException;
/**
* Returns the contact IDs and transport IDs for which the DB contains
* at least one set of transport keys. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Transaction txn) throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */

View File

@@ -22,6 +22,11 @@ public interface TransportConnectionWriter {
*/ */
int getMaxIdleTime(); int getMaxIdleTime();
/**
* Returns true if the transport is lossy and cheap.
*/
boolean isLossyAndCheap();
/** /**
* Returns an output stream for writing to the transport connection. * Returns an output stream for writing to the transport connection.
*/ */

View File

@@ -79,6 +79,11 @@ public abstract class AbstractDuplexTransportConnection
return plugin.getMaxIdleTime(); return plugin.getMaxIdleTime();
} }
@Override
public boolean isLossyAndCheap() {
return false;
}
@Override @Override
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
return AbstractDuplexTransportConnection.this.getOutputStream(); return AbstractDuplexTransportConnection.this.getOutputStream();

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin.file;
public interface FileConstants { public interface FileConstants {

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.plugin.TransportId;
public interface RemovableDriveConstants {
TransportId ID = new TransportId("org.briarproject.bramble.drive");
String PROP_PATH = "path";
String PROP_URI = "uri";
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import javax.annotation.Nullable;
@NotNullByDefault
public interface RemovableDriveManager {
/**
* Returns the currently running reader task, or null if no reader task
* is running.
*/
@Nullable
RemovableDriveTask getCurrentReaderTask();
/**
* Returns the currently running writer task, or null if no writer task
* is running.
*/
@Nullable
RemovableDriveTask getCurrentWriterTask();
/**
* Starts and returns a reader task, reading from a stream described by
* the given transport properties. If a reader task is already running,
* it will be returned and the argument will be ignored.
*/
RemovableDriveTask startReaderTask(TransportProperties p);
/**
* Starts and returns a writer task for the given contact, writing to
* a stream described by the given transport properties. If a writer task
* is already running, it will be returned and the arguments will be
* ignored.
*/
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
/**
* Returns true if there is anything to send to the given contact.
*/
boolean isWriterTaskNeeded(ContactId c) throws DbException;
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
public interface RemovableDriveTask extends Runnable {
/**
* Returns the {@link TransportProperties} that were used for creating
* this task.
*/
TransportProperties getTransportProperties();
/**
* Adds an observer to the task. The observer will be notified of state
* changes on the event thread. If the task has already finished, the
* observer will be notified of its final state.
*/
void addObserver(Consumer<State> observer);
/**
* Removes an observer from the task.
*/
void removeObserver(Consumer<State> observer);
class State {
private final long done, total;
private final boolean finished, success;
public State(long done, long total, boolean finished, boolean success) {
this.done = done;
this.total = total;
this.finished = finished;
this.success = success;
}
/**
* Returns the total length in bytes of the messages read or written
* so far, or zero if the total is unknown.
*/
public long getDone() {
return done;
}
/**
* Returns the total length in bytes of the messages that will have
* been read or written when the task is complete, or zero if the
* total is unknown.
*/
public long getTotal() {
return total;
}
public boolean isFinished() {
return finished;
}
public boolean isSuccess() {
return success;
}
}
}

View File

@@ -15,6 +15,12 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface SimplexPlugin extends Plugin { public interface SimplexPlugin extends Plugin {
/**
* Returns true if the transport is likely to lose streams and the cost of
* transmitting redundant copies of data is cheap.
*/
boolean isLossyAndCheap();
/** /**
* Attempts to create and return a reader for the given transport * Attempts to create and return a reader for the given transport
* properties. Returns null if a reader cannot be created. * properties. Returns null if a reader cannot be created.

View File

@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
PriorityHandler handler); PriorityHandler handler);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter); int maxLatency, boolean eager, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter, int maxLatency, int maxIdleTime, StreamWriter streamWriter,

View File

@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
private final ContactId contactId; private final ContactId contactId;
private final Collection<MessageId> messageIds; private final Collection<MessageId> messageIds;
private final long totalLength;
public MessagesSentEvent(ContactId contactId, public MessagesSentEvent(ContactId contactId,
Collection<MessageId> messageIds) { Collection<MessageId> messageIds, long totalLength) {
this.contactId = contactId; this.contactId = contactId;
this.messageIds = messageIds; this.messageIds = messageIds;
this.totalLength = totalLength;
} }
public ContactId getContactId() { public ContactId getContactId() {
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
public Collection<MessageId> getMessageIds() { public Collection<MessageId> getMessageIds() {
return messageIds; return messageIds;
} }
public long getTotalLength() {
return totalLength;
}
} }

View File

@@ -10,23 +10,54 @@ public interface IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* <p>
* If an unexpected exception occurs while handling data that is assumed
* to be valid (e.g. locally created metadata), it may be sensible to
* rethrow the unexpected exception as a DbException so that delivery is
* attempted again at next startup. This will allow delivery to succeed if
* the unexpected exception was caused by a bug that has subsequently been
* fixed.
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @return Whether or not this message should be shared * @throws DbException if a database error occurs while delivering the
* @throws DbException Should only be used for real database errors. * message. Delivery will be attempted again at next startup. Throwing
* If this is thrown, delivery will be attempted again at next startup, * this exception has the same effect as returning
* whereas if an InvalidMessageException is thrown, * {@link DeliveryAction#DEFER}.
* the message will be permanently invalidated. * @throws InvalidMessageException if the message is invalid in the context
* @throws InvalidMessageException for any non-database error * of its dependencies. The message and any dependents will be marked as
* that occurs while handling remotely created data. * invalid and deleted along with their metadata. Throwing this exception
* This includes errors that occur while handling locally created data * has the same effect as returning {@link DeliveryAction#REJECT}.
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Throwing this will delete the incoming message and its metadata
* marking it as invalid in the database.
* Never rethrow DbException as InvalidMessageException!
*/ */
boolean incomingMessage(Transaction txn, Message m, Metadata meta) DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException; throws DbException, InvalidMessageException;
enum DeliveryAction {
/**
* The message and any dependent messages will be moved to the
* {@link MessageState#INVALID INVALID state} and deleted, along with
* their metadata.
*/
REJECT,
/**
* The message will be moved to the
* {@link MessageState#PENDING PENDING state}. Delivery will be
* attempted again at next startup.
*/
DEFER,
/**
* The message will be moved to the
* {@link MessageState#DELIVERED DELIVERED state} and shared.
*/
ACCEPT_SHARE,
/**
* The message will be moved to the
* {@link MessageState#DELIVERED DELIVERED state} and will not be
* shared.
*/
ACCEPT_DO_NOT_SHARE
}
} }

View File

@@ -1,8 +1,33 @@
package org.briarproject.bramble.api.sync.validation; package org.briarproject.bramble.api.sync.validation;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
public enum MessageState { public enum MessageState {
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3); /**
* A remote message that has not yet been validated.
*/
UNKNOWN(0),
/**
* A remote message that has failed validation, has been
* {@link DeliveryAction#REJECT rejected} by the local sync client, or
* depends on another message that has failed validation or been rejected.
*/
INVALID(1),
/**
* A remote message that has passed validation and is awaiting delivery to
* the local sync client. The message will not be delivered until all its
* dependencies have been validated and delivered.
*/
PENDING(2),
/**
* A local message, or a remote message that has passed validation and
* been delivered to the local sync client.
*/
DELIVERED(3);
private final int value; private final int value;

View File

@@ -22,8 +22,24 @@ public interface KeyManager {
/** /**
* Derives and stores a set of rotation mode transport keys for * Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each transport and returns the * communicating with the given contact over the given transport and
* key set IDs. * returns the key set ID, or null if the transport is not supported.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
* @param active Whether the derived keys can be used for outgoing streams
*/
@Nullable
KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t,
SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
/**
* Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each supported transport and
* returns the key set IDs.
* <p/> * <p/>
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.api.transport.agreement;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
@NotNullByDefault
public interface TransportKeyAgreementManager {
/**
* The unique ID of the transport key agreement client.
*/
ClientId CLIENT_ID =
new ClientId("org.briarproject.bramble.transport.agreement");
/**
* The current major version of the transport key agreement client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the transport key agreement client.
*/
int MINOR_VERSION = 0;
}

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
public interface BrambleCoreEagerSingletons { public interface BrambleCoreEagerSingletons {
@@ -33,6 +34,8 @@ public interface BrambleCoreEagerSingletons {
void inject(RendezvousModule.EagerSingletons init); void inject(RendezvousModule.EagerSingletons init);
void inject(TransportKeyAgreementModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init);
void inject(ValidationModule.EagerSingletons init); void inject(ValidationModule.EagerSingletons init);
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
c.inject(new RendezvousModule.EagerSingletons()); c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons());
c.inject(new TransportKeyAgreementModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons());
c.inject(new ValidationModule.EagerSingletons()); c.inject(new ValidationModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons()); c.inject(new VersioningModule.EagerSingletons());

View File

@@ -23,6 +23,7 @@ import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
import dagger.Module; import dagger.Module;
@@ -49,6 +50,7 @@ import dagger.Module;
RendezvousModule.class, RendezvousModule.class,
SettingsModule.class, SettingsModule.class,
SyncModule.class, SyncModule.class,
TransportKeyAgreementModule.class,
TransportModule.class, TransportModule.class,
ValidationModule.class, ValidationModule.class,
VersioningModule.class VersioningModule.class

View File

@@ -71,8 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId()); ContactId c = requireNonNull(ctx.getContactId());
// Use eager retransmission if the transport is lossy and cheap
return syncSessionFactory.createSimplexOutgoingSession(c, return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter); ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
streamWriter);
} }
} }

View File

@@ -162,6 +162,18 @@ interface Database<T> {
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k) KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
throws DbException; throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(T txn, ContactId c, int maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
@@ -215,6 +227,16 @@ interface Database<T> {
*/ */
boolean containsTransport(T txn, TransportId t) throws DbException; boolean containsTransport(T txn, TransportId t) throws DbException;
/**
* Returns true if the database contains keys for communicating with the
* given contact over the given transport. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
boolean containsTransportKeys(T txn, ContactId c, TransportId t)
throws DbException;
/** /**
* Returns true if the database contains the given message, the message is * Returns true if the database contains the given message, the message is
* shared, and the visibility of the message's group to the given contact * shared, and the visibility of the message's group to the given contact
@@ -476,11 +498,37 @@ interface Database<T> {
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact, up to the given total length. * given contact, up to the given total length.
* <p/> * <p/>
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
* does not return messages that have already been sent unless they are
* due for retransmission.
* <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength, Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
int maxLatency) throws DbException; int maxLatency) throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact, together with their raw lengths.
* <p/>
* Unlike {@link #getMessagesToSend(Object, ContactId, int, int)} this
* method may return messages that have already been sent and are not yet
* due for retransmission.
* <p/>
* Read-only.
*/
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages
* that have already been sent and are not yet due for retransmission.
* <p/>
* Read-only.
*/
long getUnackedMessageBytesToSend(T txn, ContactId c) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated.
* <p/> * <p/>
@@ -580,6 +628,16 @@ interface Database<T> {
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t) Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
throws DbException; throws DbException;
/**
* Returns the contact IDs and transport IDs for which the DB contains
* at least one set of transport keys. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(T txn)
throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */

View File

@@ -341,6 +341,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.addTransportKeys(txn, p, k); return db.addTransportKeys(txn, p, k);
} }
@Override
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
int maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsAnythingToSend(txn, c, maxLatency, eager);
}
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId remote, public boolean containsContact(Transaction transaction, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -371,6 +380,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsPendingContact(txn, p); return db.containsPendingContact(txn, p);
} }
@Override
public boolean containsTransportKeys(Transaction transaction, ContactId c,
TransportId t) throws DbException {
T txn = unbox(transaction);
return db.containsTransportKeys(txn, c, t);
}
@Override @Override
public void deleteMessage(Transaction transaction, MessageId m) public void deleteMessage(Transaction transaction, MessageId m)
throws DbException { throws DbException {
@@ -415,14 +431,43 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, c, maxLength, maxLatency); db.getMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
messages.add(db.getMessage(txn, m)); Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids)); transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
}
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, Collection<MessageId> ids, int maxLatency)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
List<MessageId> sentIds = new ArrayList<>(ids.size());
for (MessageId m : ids) {
if (db.containsVisibleMessage(txn, c, m)) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
sentIds.add(m);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
}
if (messages.isEmpty()) return messages;
db.lowerRequestedFlag(txn, c, sentIds);
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
return messages; return messages;
} }
@@ -467,14 +512,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency); db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
messages.add(db.getMessage(txn, m)); Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids)); transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages; return messages;
} }
@@ -692,6 +740,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return status; return status;
} }
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(
Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getUnackedMessagesToSend(txn, c);
}
@Override
public long getUnackedMessageBytesToSend(Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getUnackedMessageBytesToSend(txn, c);
}
@Override @Override
public Map<MessageId, MessageState> getMessageDependencies( public Map<MessageId, MessageState> getMessageDependencies(
Transaction transaction, MessageId m) throws DbException { Transaction transaction, MessageId m) throws DbException {
@@ -765,6 +832,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getTransportKeys(txn, t); return db.getTransportKeys(txn, t);
} }
@Override
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Transaction transaction) throws DbException {
T txn = unbox(transaction);
return db.getTransportsWithKeys(txn);
}
@Override @Override
public void incrementStreamCounter(Transaction transaction, TransportId t, public void incrementStreamCounter(Transaction transaction, TransportId t,
KeySetId k) throws DbException { KeySetId k) throws DbException {

View File

@@ -51,6 +51,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -344,6 +345,11 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp" "CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
+ " ON statuses (contactId, timestamp)"; + " ON statuses (contactId, timestamp)";
private static final String
INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP =
"CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp"
+ " ON statuses (contactId, txCount, timestamp)";
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE = private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline" "CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)"; + " ON messages (cleanupDeadline)";
@@ -570,6 +576,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID); s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE); s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
@@ -1120,6 +1127,55 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsAnythingToSend(Connection txn, ContactId c,
int maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND ack = TRUE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
boolean acksToSend = rs.next();
rs.close();
ps.close();
if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR eta > ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsContact(Connection txn, AuthorId remote, public boolean containsContact(Connection txn, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -1277,6 +1333,29 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsTransportKeys(Connection txn, ContactId c,
TransportId t) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM outgoingKeys"
+ " WHERE contactId = ? AND transportId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setString(2, t.getString());
rs = ps.executeQuery();
boolean found = rs.next();
rs.close();
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsVisibleMessage(Connection txn, ContactId c, public boolean containsVisibleMessage(Connection txn, ContactId c,
MessageId m) throws DbException { MessageId m) throws DbException {
@@ -2205,6 +2284,63 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT length, messageId FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " ORDER BY txCount, timestamp";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
Map<MessageId, Integer> results = new LinkedHashMap<>();
while (rs.next()) {
int length = rs.getInt(1);
MessageId id = new MessageId(rs.getBytes(2));
results.put(id, length);
}
rs.close();
ps.close();
return results;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public long getUnackedMessageBytesToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT SUM(length) FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
rs.next();
long total = rs.getInt(1);
rs.close();
ps.close();
return total;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Connection txn) public Collection<MessageId> getMessagesToValidate(Connection txn)
throws DbException { throws DbException {
@@ -2574,6 +2710,38 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
String sql = "SELECT DISTINCT contactId, transportId"
+ " FROM outgoingKeys";
s = txn.createStatement();
rs = s.executeQuery(sql);
Map<ContactId, Collection<TransportId>> ids = new HashMap<>();
while (rs.next()) {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getString(2));
Collection<TransportId> transportIds = ids.get(c);
if (transportIds == null) {
transportIds = new ArrayList<>();
ids.put(c, transportIds);
}
transportIds.add(t);
}
rs.close();
s.close();
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void incrementStreamCounter(Connection txn, TransportId t, public void incrementStreamCounter(Connection txn, TransportId t,
KeySetId k) throws DbException { KeySetId k) throws DbException {

View File

@@ -0,0 +1,119 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@NotNullByDefault
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
private static final Logger LOG =
getLogger(AbstractRemovableDrivePlugin.class.getName());
private final int maxLatency;
abstract InputStream openInputStream(TransportProperties p)
throws IOException;
abstract OutputStream openOutputStream(TransportProperties p)
throws IOException;
AbstractRemovableDrivePlugin(int maxLatency) {
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Unused for simplex transports
throw new UnsupportedOperationException();
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public State getState() {
return ACTIVE;
}
@Override
public int getReasonsDisabled() {
return 0;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public boolean isLossyAndCheap() {
return true;
}
@Override
public TransportConnectionReader createReader(TransportProperties p) {
try {
return new TransportInputStreamReader(openInputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
@Override
public TransportConnectionWriter createWriter(TransportProperties p) {
try {
return new TransportOutputStreamWriter(this, openOutputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
}

View File

@@ -15,8 +15,8 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;

View File

@@ -36,6 +36,11 @@ class FileTransportWriter implements TransportConnectionWriter {
return plugin.getMaxIdleTime(); return plugin.getMaxIdleTime();
} }
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return out; return out;

View File

@@ -0,0 +1,102 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY;
@ThreadSafe
@NotNullByDefault
class RemovableDriveManagerImpl
implements RemovableDriveManager, RemovableDriveTaskRegistry {
private final Executor ioExecutor;
private final DatabaseComponent db;
private final RemovableDriveTaskFactory taskFactory;
private final Object lock = new Object();
@GuardedBy("lock")
private RemovableDriveTask reader = null;
@GuardedBy("lock")
private RemovableDriveTask writer = null;
@Inject
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db, RemovableDriveTaskFactory taskFactory) {
this.ioExecutor = ioExecutor;
this.db = db;
this.taskFactory = taskFactory;
}
@Nullable
@Override
public RemovableDriveTask getCurrentReaderTask() {
synchronized (lock) {
return reader;
}
}
@Nullable
@Override
public RemovableDriveTask getCurrentWriterTask() {
synchronized (lock) {
return writer;
}
}
@Override
public RemovableDriveTask startReaderTask(TransportProperties p) {
RemovableDriveTask created;
synchronized (lock) {
if (reader != null) return reader;
reader = created = taskFactory.createReader(this, p);
}
ioExecutor.execute(created);
return created;
}
@Override
public RemovableDriveTask startWriterTask(ContactId c,
TransportProperties p) {
RemovableDriveTask created;
synchronized (lock) {
if (writer != null) return writer;
writer = created = taskFactory.createWriter(this, c, p);
}
ioExecutor.execute(created);
return created;
}
@Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn ->
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
}
@Override
public void removeReader(RemovableDriveTask task) {
synchronized (lock) {
if (reader == task) reader = null;
}
}
@Override
public void removeWriter(RemovableDriveTask task) {
synchronized (lock) {
if (writer == task) writer = null;
}
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class RemovableDriveModule {
@Provides
@Singleton
RemovableDriveManager provideRemovableDriveManager(
RemovableDriveManagerImpl removableDriveManager) {
return removableDriveManager;
}
@Provides
RemovableDriveTaskFactory provideTaskFactory(
RemovableDriveTaskFactoryImpl taskFactory) {
return taskFactory;
}
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class RemovableDrivePlugin extends AbstractRemovableDrivePlugin {
RemovableDrivePlugin(int maxLatency) {
super(maxLatency);
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileInputStream(path);
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileOutputStream(path);
}
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
static final int MAX_LATENCY = (int) DAYS.toMillis(14);
@Inject
RemovableDrivePluginFactory() {
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new RemovableDrivePlugin(MAX_LATENCY);
}
}

View File

@@ -0,0 +1,69 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@NotNullByDefault
class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
private final static Logger LOG =
getLogger(RemovableDriveReaderTask.class.getName());
RemovableDriveReaderTask(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, transportProperties);
}
@Override
public void run() {
TransportConnectionReader r =
getPlugin().createReader(transportProperties);
if (r == null) {
LOG.warning("Failed to create reader");
registry.removeReader(this);
setSuccess(false);
return;
}
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
}
private class DecoratedReader implements TransportConnectionReader {
private final TransportConnectionReader delegate;
private DecoratedReader(TransportConnectionReader delegate) {
this.delegate = delegate;
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public void dispose(boolean exception, boolean recognised)
throws IOException {
delegate.dispose(exception, recognised);
registry.removeReader(RemovableDriveReaderTask.this);
setSuccess(!exception && recognised);
}
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
interface RemovableDriveTaskFactory {
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
TransportProperties p);
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p);
}

View File

@@ -0,0 +1,55 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
private final DatabaseComponent db;
private final Executor eventExecutor;
private final PluginManager pluginManager;
private final ConnectionManager connectionManager;
private final EventBus eventBus;
@Inject
RemovableDriveTaskFactoryImpl(
DatabaseComponent db,
@EventExecutor Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus) {
this.db = db;
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
}
@Override
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
TransportProperties p) {
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
connectionManager, eventBus, registry, p);
}
@Override
public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p) {
return new RemovableDriveWriterTask(db, eventExecutor, pluginManager,
connectionManager, eventBus, registry, c, p);
}
}

View File

@@ -0,0 +1,116 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Math.min;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@ThreadSafe
@NotNullByDefault
abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
private final Executor eventExecutor;
private final PluginManager pluginManager;
final ConnectionManager connectionManager;
final EventBus eventBus;
final RemovableDriveTaskRegistry registry;
final TransportProperties transportProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<Consumer<State>> observers = new ArrayList<>();
@GuardedBy("lock")
private State state = new State(0, 0, false, false);
RemovableDriveTaskImpl(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
TransportProperties transportProperties) {
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
this.registry = registry;
this.transportProperties = transportProperties;
}
@Override
public TransportProperties getTransportProperties() {
return transportProperties;
}
@Override
public void addObserver(Consumer<State> o) {
State state;
synchronized (lock) {
observers.add(o);
state = this.state;
}
if (state.isFinished()) {
eventExecutor.execute(() -> o.accept(state));
}
}
@Override
public void removeObserver(Consumer<State> o) {
synchronized (lock) {
observers.remove(o);
}
}
SimplexPlugin getPlugin() {
return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
}
void setTotal(long total) {
synchronized (lock) {
state = new State(state.getDone(), total, state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void addDone(long done) {
synchronized (lock) {
// Done and total come from different sources; make them consistent
done = min(state.getDone() + done, state.getTotal());
state = new State(done, state.getTotal(), state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void setSuccess(boolean success) {
synchronized (lock) {
state = new State(state.getDone(), state.getTotal(), true, success);
notifyObservers();
}
}
@GuardedBy("lock")
private void notifyObservers() {
List<Consumer<State>> observers = new ArrayList<>(this.observers);
State state = this.state;
eventExecutor.execute(() -> {
for (Consumer<State> o : observers) o.accept(state);
});
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@NotNullByDefault
interface RemovableDriveTaskRegistry {
void removeReader(RemovableDriveTask task);
void removeWriter(RemovableDriveTask task);
}

View File

@@ -0,0 +1,126 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class RemovableDriveWriterTask extends RemovableDriveTaskImpl
implements EventListener {
private static final Logger LOG =
getLogger(RemovableDriveWriterTask.class.getName());
private final DatabaseComponent db;
private final ContactId contactId;
RemovableDriveWriterTask(
DatabaseComponent db,
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
ContactId contactId,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, transportProperties);
this.db = db;
this.contactId = contactId;
}
@Override
public void run() {
SimplexPlugin plugin = getPlugin();
TransportConnectionWriter w = plugin.createWriter(transportProperties);
if (w == null) {
LOG.warning("Failed to create writer");
registry.removeWriter(this);
setSuccess(false);
return;
}
try {
setTotal(db.transactionWithResult(true, txn ->
db.getUnackedMessageBytesToSend(txn, contactId)));
} catch (DbException e) {
logException(LOG, WARNING, e);
registry.removeWriter(this);
setSuccess(false);
return;
}
eventBus.addListener(this);
connectionManager.manageOutgoingConnection(contactId, ID,
new DecoratedWriter(w));
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessagesSentEvent) {
MessagesSentEvent m = (MessagesSentEvent) e;
if (contactId.equals(m.getContactId())) {
if (LOG.isLoggable(INFO)) {
LOG.info(m.getMessageIds().size() + " messages sent");
}
addDone(m.getTotalLength());
}
}
}
private class DecoratedWriter implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private DecoratedWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public int getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return delegate.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
registry.removeWriter(RemovableDriveWriterTask.this);
eventBus.removeListener(RemovableDriveWriterTask.this);
setSuccess(!exception);
}
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.InputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportInputStreamReader implements TransportConnectionReader {
private static final Logger LOG =
getLogger(TransportInputStreamReader.class.getName());
private final InputStream in;
TransportInputStreamReader(InputStream in) {
this.in = in;
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public void dispose(boolean exception, boolean recognised) {
tryToClose(in, LOG, WARNING);
}
}

View File

@@ -0,0 +1,52 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportOutputStreamWriter implements TransportConnectionWriter {
private static final Logger LOG =
getLogger(TransportOutputStreamWriter.class.getName());
private final SimplexPlugin plugin;
private final OutputStream out;
TransportOutputStreamWriter(SimplexPlugin plugin, OutputStream out) {
this.plugin = plugin;
this.out = out;
}
@Override
public int getMaxLatency() {
return plugin.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return plugin.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public void dispose(boolean exception) {
tryToClose(out, LOG, WARNING);
}
}

View File

@@ -44,6 +44,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable @Immutable
@@ -115,8 +116,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
// Find the latest update for this transport, if any // Find the latest update for this transport, if any
BdfDictionary d = metadataParser.parse(meta); BdfDictionary d = metadataParser.parse(meta);
@@ -131,14 +132,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
// We've already received a newer update - delete this one // We've already received a newer update - delete this one
db.deleteMessage(txn, m.getId()); db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId());
return false; return ACCEPT_DO_NOT_SHARE;
} }
} }
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t)); txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
return false; return ACCEPT_DO_NOT_SHARE;
} }
@Override @Override

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions; import org.briarproject.bramble.api.sync.Versions;
@@ -22,7 +23,11 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -61,6 +66,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final int maxLatency; private final int maxLatency;
private final boolean eager;
private final StreamWriter streamWriter; private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final AtomicInteger outstandingQueries; private final AtomicInteger outstandingQueries;
@@ -70,7 +76,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, TransportId transportId, EventBus eventBus, ContactId contactId, TransportId transportId,
int maxLatency, StreamWriter streamWriter, int maxLatency, boolean eager, StreamWriter streamWriter,
SyncRecordWriter recordWriter) { SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
@@ -78,6 +84,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.eager = eager;
this.streamWriter = streamWriter; this.streamWriter = streamWriter;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record outstandingQueries = new AtomicInteger(2); // One per type of record
@@ -92,8 +99,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
// Send our supported protocol versions // Send our supported protocol versions
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS)); recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
// Start a query for each type of record // Start a query for each type of record
dbExecutor.execute(new GenerateAck()); dbExecutor.execute(this::generateAck);
dbExecutor.execute(new GenerateBatch()); if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
else dbExecutor.execute(this::generateBatch);
// Write records until interrupted or no more records to write // Write records until interrupted or no more records to write
try { try {
while (!interrupted) { while (!interrupted) {
@@ -138,81 +146,110 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
} }
} }
private class GenerateAck implements Runnable { @DatabaseExecutor
private void loadUnackedMessageIds() {
@DatabaseExecutor if (interrupted) return;
@Override try {
public void run() { Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
if (interrupted) return; db.getUnackedMessagesToSend(txn, contactId));
try { if (LOG.isLoggable(INFO)) {
Ack a = db.transactionWithNullableResult(false, txn -> LOG.info(ids.size() + " unacked messages to send");
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if (a == null) decrementOutstandingQueries();
else writerTasks.add(new WriteAck(a));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
} }
private class WriteAck implements ThrowingRunnable<IOException> { @DatabaseExecutor
private void generateEagerBatch(Map<MessageId, Integer> ids) {
private final Ack ack; if (interrupted) return;
// Take some message IDs from `ids` to form a batch
private WriteAck(Ack ack) { Collection<MessageId> batchIds = new ArrayList<>();
this.ack = ack; long totalLength = 0;
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
while (it.hasNext()) {
// Check whether the next message will fit in the batch
Entry<MessageId, Integer> e = it.next();
int length = e.getValue();
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
// Add the message to the batch
it.remove();
batchIds.add(e.getKey());
totalLength += length;
} }
if (batchIds.isEmpty()) throw new AssertionError();
@IoExecutor try {
@Override Collection<Message> batch =
public void run() throws IOException { db.transactionWithResult(false, txn ->
if (interrupted) return; db.generateBatch(txn, contactId, batchIds,
recordWriter.writeAck(ack); maxLatency));
LOG.info("Sent ack"); writerTasks.add(() -> writeEagerBatch(batch, ids));
dbExecutor.execute(new GenerateAck()); } catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
} }
private class GenerateBatch implements Runnable { @IoExecutor
private void writeEagerBatch(Collection<Message> batch,
Map<MessageId, Integer> ids) throws IOException {
if (interrupted) return;
for (Message m : batch) recordWriter.writeMessage(m);
LOG.info("Sent eager batch");
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
}
@DatabaseExecutor @DatabaseExecutor
@Override private void generateAck() {
public void run() { if (interrupted) return;
if (interrupted) return; try {
try { Ack a = db.transactionWithNullableResult(false, txn ->
Collection<Message> b = db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
db.transactionWithNullableResult(false, txn -> if (LOG.isLoggable(INFO))
db.generateBatch(txn, contactId, LOG.info("Generated ack: " + (a != null));
MAX_RECORD_PAYLOAD_BYTES, maxLatency)); if (a == null) decrementOutstandingQueries();
if (LOG.isLoggable(INFO)) else writerTasks.add(() -> writeAck(a));
LOG.info("Generated batch: " + (b != null)); } catch (DbException e) {
if (b == null) decrementOutstandingQueries(); logException(LOG, WARNING, e);
else writerTasks.add(new WriteBatch(b)); interrupt();
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
} }
} }
private class WriteBatch implements ThrowingRunnable<IOException> { @IoExecutor
private void writeAck(Ack ack) throws IOException {
if (interrupted) return;
recordWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(this::generateAck);
}
private final Collection<Message> batch; @DatabaseExecutor
private void generateBatch() {
private WriteBatch(Collection<Message> batch) { if (interrupted) return;
this.batch = batch; try {
Collection<Message> b =
db.transactionWithNullableResult(false, txn ->
db.generateBatch(txn, contactId,
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if (b == null) decrementOutstandingQueries();
else writerTasks.add(() -> writeBatch(b));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
}
@IoExecutor @IoExecutor
@Override private void writeBatch(Collection<Message> batch) throws IOException {
public void run() throws IOException { if (interrupted) return;
if (interrupted) return; for (Message m : batch) recordWriter.writeMessage(m);
for (Message m : batch) recordWriter.writeMessage(m); LOG.info("Sent batch");
LOG.info("Sent batch"); dbExecutor.execute(this::generateBatch);
dbExecutor.execute(new GenerateBatch());
}
} }
} }

View File

@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter) { int maxLatency, boolean eager, StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream(); OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter = SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out); recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t, return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
maxLatency, streamWriter, recordWriter); maxLatency, eager, streamWriter, recordWriter);
} }
@Override @Override

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.sync.validation.MessageValidator; import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.api.sync.validation.ValidationManager; import org.briarproject.bramble.api.sync.validation.ValidationManager;
@@ -40,6 +41,10 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
@@ -185,16 +190,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
int majorVersion = g.getMajorVersion(); int majorVersion = g.getMajorVersion();
Metadata meta = Metadata meta =
db.getMessageMetadataForValidator(txn, id); db.getMessageMetadataForValidator(txn, id);
DeliveryResult result = DeliveryAction action =
deliverMessage(txn, m, c, majorVersion, meta); deliverMessage(txn, m, c, majorVersion, meta);
if (result.valid) { if (action == REJECT) {
addPendingDependents(txn, id, pending); invalidateMessage(txn, id);
if (result.share) {
db.setMessageShared(txn, id);
toShare.addAll(states.keySet());
}
} else {
addDependentsToInvalidate(txn, id, invalidate); addDependentsToInvalidate(txn, id, invalidate);
} else if (action == ACCEPT_SHARE) {
db.setMessageState(txn, m.getId(), DELIVERED);
addPendingDependents(txn, id, pending);
db.setMessageShared(txn, id);
toShare.addAll(states.keySet());
} else if (action == ACCEPT_DO_NOT_SHARE) {
db.setMessageState(txn, m.getId(), DELIVERED);
addPendingDependents(txn, id, pending);
} }
} }
} }
@@ -275,16 +283,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
Metadata meta = context.getMetadata(); Metadata meta = context.getMetadata();
db.mergeMessageMetadata(txn, id, meta); db.mergeMessageMetadata(txn, id, meta);
if (allDelivered) { if (allDelivered) {
DeliveryResult result = DeliveryAction action =
deliverMessage(txn, m, c, majorVersion, meta); deliverMessage(txn, m, c, majorVersion, meta);
if (result.valid) { if (action == REJECT) {
addPendingDependents(txn, id, pending); invalidateMessage(txn, id);
if (result.share) {
db.setMessageShared(txn, id);
toShare.addAll(dependencies);
}
} else {
addDependentsToInvalidate(txn, id, invalidate); addDependentsToInvalidate(txn, id, invalidate);
} else if (action == DEFER) {
db.setMessageState(txn, id, PENDING);
} else if (action == ACCEPT_SHARE) {
db.setMessageState(txn, id, DELIVERED);
addPendingDependents(txn, id, pending);
db.setMessageShared(txn, id);
toShare.addAll(dependencies);
} else if (action == ACCEPT_DO_NOT_SHARE) {
db.setMessageState(txn, id, DELIVERED);
addPendingDependents(txn, id, pending);
} }
} else { } else {
db.setMessageState(txn, id, PENDING); db.setMessageState(txn, id, PENDING);
@@ -304,23 +317,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
@DatabaseExecutor @DatabaseExecutor
private DeliveryResult deliverMessage(Transaction txn, Message m, private DeliveryAction deliverMessage(Transaction txn, Message m,
ClientId c, int majorVersion, Metadata meta) throws DbException { ClientId c, int majorVersion, Metadata meta) {
// Deliver the message to the client if it's registered a hook // Deliver the message to the client if it has registered a hook
boolean shareMsg = false;
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion); ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
IncomingMessageHook hook = hooks.get(cv); IncomingMessageHook hook = hooks.get(cv);
if (hook != null) { if (hook == null) return ACCEPT_DO_NOT_SHARE;
try { try {
shareMsg = hook.incomingMessage(txn, m, meta); return hook.incomingMessage(txn, m, meta);
} catch (InvalidMessageException e) { } catch (DbException e) {
logException(LOG, INFO, e); logException(LOG, INFO, e);
invalidateMessage(txn, m.getId()); return DEFER;
return new DeliveryResult(false, false); } catch (InvalidMessageException e) {
} logException(LOG, INFO, e);
return REJECT;
} }
db.setMessageState(txn, m.getId(), DELIVERED);
return new DeliveryResult(true, shareMsg);
} }
@DatabaseExecutor @DatabaseExecutor
@@ -447,14 +458,4 @@ class ValidationManagerImpl implements ValidationManager, Service,
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
private static class DeliveryResult {
private final boolean valid, share;
private DeliveryResult(boolean valid, boolean share) {
this.valid = valid;
this.share = share;
}
}
} }

View File

@@ -51,7 +51,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
private final PluginConfig pluginConfig; private final PluginConfig pluginConfig;
private final TransportKeyManagerFactory transportKeyManagerFactory;
private final TransportCrypto transportCrypto; private final TransportCrypto transportCrypto;
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers; private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
@@ -61,34 +60,39 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
KeyManagerImpl(DatabaseComponent db, KeyManagerImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
PluginConfig pluginConfig, PluginConfig pluginConfig,
TransportKeyManagerFactory transportKeyManagerFactory, TransportCrypto transportCrypto,
TransportCrypto transportCrypto) { TransportKeyManagerFactory transportKeyManagerFactory) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.pluginConfig = pluginConfig; this.pluginConfig = pluginConfig;
this.transportKeyManagerFactory = transportKeyManagerFactory;
this.transportCrypto = transportCrypto; this.transportCrypto = transportCrypto;
managers = new ConcurrentHashMap<>(); managers = new ConcurrentHashMap<>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
TransportKeyManager m = transportKeyManagerFactory.
createTransportKeyManager(f.getId(), f.getMaxLatency());
managers.put(f.getId(), m);
}
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) {
TransportKeyManager m = transportKeyManagerFactory.
createTransportKeyManager(f.getId(), f.getMaxLatency());
managers.put(f.getId(), m);
}
} }
@Override @Override
public void startService() throws ServiceException { public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
Map<TransportId, Integer> transports = new HashMap<>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
transports.put(f.getId(), f.getMaxLatency());
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
transports.put(f.getId(), f.getMaxLatency());
try { try {
db.transaction(false, txn -> { db.transaction(false, txn -> {
for (Entry<TransportId, Integer> e : transports.entrySet()) for (SimplexPluginFactory f :
db.addTransport(txn, e.getKey(), e.getValue()); pluginConfig.getSimplexFactories()) {
for (Entry<TransportId, Integer> e : transports.entrySet()) { db.addTransport(txn, f.getId(), f.getMaxLatency());
TransportKeyManager m = transportKeyManagerFactory managers.get(f.getId()).start(txn);
.createTransportKeyManager(e.getKey(), }
e.getValue()); for (DuplexPluginFactory f :
managers.put(e.getKey(), m); pluginConfig.getDuplexFactories()) {
m.start(txn); db.addTransport(txn, f.getId(), f.getMaxLatency());
managers.get(f.getId()).start(txn);
} }
}); });
} catch (DbException e) { } catch (DbException e) {
@@ -101,9 +105,17 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public Map<TransportId, KeySetId> addRotationKeys( public KeySetId addRotationKeys(Transaction txn, ContactId c,
Transaction txn, ContactId c, SecretKey rootKey, long timestamp, TransportId t, SecretKey rootKey, long timestamp, boolean alice,
boolean alice, boolean active) throws DbException { boolean active) throws DbException {
return withManager(t, m ->
m.addRotationKeys(txn, c, rootKey, timestamp, alice, active));
}
@Override
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
@@ -137,7 +149,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair) PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException { throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto SecretKey staticMasterKey = transportCrypto
.deriveStaticMasterKey(theirPublicKey, ourKeyPair); .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey = SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true); transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair); boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);

View File

@@ -0,0 +1,22 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
@NotNullByDefault
interface MessageEncoder {
Message encodeKeyMessage(GroupId contactGroupId,
TransportId transportId, PublicKey publicKey);
Message encodeActivateMessage(GroupId contactGroupId,
TransportId transportId, MessageId previousMessageId);
BdfDictionary encodeMessageMetadata(TransportId transportId,
MessageType type, boolean local);
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
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 javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
@Immutable
@NotNullByDefault
class MessageEncoderImpl implements MessageEncoder {
private final ClientHelper clientHelper;
private final Clock clock;
@Inject
MessageEncoderImpl(ClientHelper clientHelper, Clock clock) {
this.clientHelper = clientHelper;
this.clock = clock;
}
@Override
public Message encodeKeyMessage(GroupId contactGroupId,
TransportId transportId, PublicKey publicKey) {
BdfList body = BdfList.of(
KEY.getValue(),
transportId.getString(),
publicKey.getEncoded());
return encodeMessage(contactGroupId, body);
}
@Override
public Message encodeActivateMessage(GroupId contactGroupId,
TransportId transportId, MessageId previousMessageId) {
BdfList body = BdfList.of(
ACTIVATE.getValue(),
transportId.getString(),
previousMessageId);
return encodeMessage(contactGroupId, body);
}
@Override
public BdfDictionary encodeMessageMetadata(TransportId transportId,
MessageType type, boolean local) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_IS_SESSION, false),
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()),
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
new BdfEntry(MSG_KEY_LOCAL, local));
}
private Message encodeMessage(GroupId contactGroupId, BdfList body) {
try {
return clientHelper.createMessage(contactGroupId,
clock.currentTimeMillis(), clientHelper.toByteArray(body));
} catch (FormatException e) {
throw new AssertionError();
}
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum MessageType {
KEY(0),
ACTIVATE(1);
private final int value;
MessageType(int value) {
this.value = value;
}
int getValue() {
return value;
}
static MessageType fromValue(int value) throws FormatException {
for (MessageType t : values()) if (t.value == value) return t;
throw new FormatException();
}
}

View File

@@ -0,0 +1,58 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class Session {
private final State state;
@Nullable
private final MessageId lastLocalMessageId;
@Nullable
private final KeyPair localKeyPair;
@Nullable
private final Long localTimestamp;
@Nullable
private final KeySetId keySetId;
Session(State state, @Nullable MessageId lastLocalMessageId,
@Nullable KeyPair localKeyPair, @Nullable Long localTimestamp,
@Nullable KeySetId keySetId) {
this.state = state;
this.lastLocalMessageId = lastLocalMessageId;
this.localKeyPair = localKeyPair;
this.localTimestamp = localTimestamp;
this.keySetId = keySetId;
}
State getState() {
return state;
}
@Nullable
MessageId getLastLocalMessageId() {
return lastLocalMessageId;
}
@Nullable
KeyPair getLocalKeyPair() {
return localKeyPair;
}
@Nullable
Long getLocalTimestamp() {
return localTimestamp;
}
@Nullable
KeySetId getKeySetId() {
return keySetId;
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@NotNullByDefault
interface SessionEncoder {
BdfDictionary encodeSession(Session s, TransportId transportId);
BdfDictionary getSessionQuery(TransportId transportId);
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionEncoderImpl implements SessionEncoder {
@Inject
SessionEncoderImpl() {
}
@Override
public BdfDictionary encodeSession(Session s, TransportId transportId) {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_IS_SESSION, true);
meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString());
meta.put(SESSION_KEY_STATE, s.getState().getValue());
putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
s.getLastLocalMessageId());
KeyPair localKeyPair = s.getLocalKeyPair();
if (localKeyPair == null) {
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE);
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE);
} else {
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY,
localKeyPair.getPublic().getEncoded());
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY,
localKeyPair.getPrivate().getEncoded());
}
putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
KeySetId keySetId = s.getKeySetId();
if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE);
else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt());
return meta;
}
@Override
public BdfDictionary getSessionQuery(TransportId transportId) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_IS_SESSION, true),
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()));
}
private void putNullable(BdfDictionary meta, String key,
@Nullable Object o) {
meta.put(key, o == null ? NULL_VALUE : o);
}
}

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SessionParser {
Session parseSession(BdfDictionary meta) throws FormatException;
}

View File

@@ -0,0 +1,67 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.KeyPair;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionParserImpl implements SessionParser {
private final TransportKeyAgreementCrypto crypto;
@Inject
SessionParserImpl(TransportKeyAgreementCrypto crypto) {
this.crypto = crypto;
}
@Override
public Session parseSession(BdfDictionary meta) throws FormatException {
State state =
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
MessageId lastLocalMessageId = null;
byte[] lastLocalMessageIdBytes =
meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
if (lastLocalMessageIdBytes != null) {
lastLocalMessageId = new MessageId(lastLocalMessageIdBytes);
}
KeyPair localKeyPair = null;
byte[] localPublicKeyBytes =
meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY);
byte[] localPrivateKeyBytes =
meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY);
if (localPublicKeyBytes != null && localPrivateKeyBytes != null) {
PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes);
PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes);
localKeyPair = new KeyPair(pub, priv);
}
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
KeySetId keySetId = null;
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
if (keySetIdLong != null) {
keySetId = new KeySetId(keySetIdLong.intValue());
}
return new Session(state, lastLocalMessageId, localKeyPair,
localTimestamp, keySetId);
}
}

View File

@@ -0,0 +1,43 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum State {
/**
* We've sent a key message and are awaiting the contact's key message.
*/
AWAIT_KEY(0),
/**
* We've exchanged key messages, derived the transport keys and sent an
* activate message, and now we're awaiting the contact's activate message.
*/
AWAIT_ACTIVATE(1),
/**
* We've exchanged key messages and activate messages, and have derived and
* activated the transport keys. This is the end state.
*/
ACTIVATED(2);
private final int value;
State(int value) {
this.value = value;
}
int getValue() {
return value;
}
static State fromValue(int value) throws FormatException {
for (State s : values()) if (s.value == value) return s;
throw new FormatException();
}
}

View File

@@ -0,0 +1,27 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface TransportKeyAgreementConstants {
String MSG_KEY_IS_SESSION = "isSession";
String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_TRANSPORT_ID = "transportId";
String MSG_KEY_PUBLIC_KEY = "publicKey";
String MSG_KEY_LOCAL = "local";
String SESSION_KEY_STATE = "state";
String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey";
String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey";
String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
String SESSION_KEY_KEY_SET_ID = "keySetId";
/**
* Label for deriving the root key from key pairs.
*/
String ROOT_KEY_LABEL =
"org.briarproject.bramble.transport.agreement/ROOT_KEY";
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@NotNullByDefault
interface TransportKeyAgreementCrypto {
KeyPair generateKeyPair();
SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey)
throws GeneralSecurityException;
PublicKey parsePublicKey(byte[] encoded) throws FormatException;
PrivateKey parsePrivateKey(byte[] encoded) throws FormatException;
}

View File

@@ -0,0 +1,66 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.Bytes.compare;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.ROOT_KEY_LABEL;
@Immutable
@NotNullByDefault
class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto {
private final CryptoComponent crypto;
@Inject
TransportKeyAgreementCryptoImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
@Override
public KeyPair generateKeyPair() {
return crypto.generateAgreementKeyPair();
}
@Override
public SecretKey deriveRootKey(KeyPair localKeyPair,
PublicKey remotePublicKey) throws GeneralSecurityException {
byte[] theirPublic = remotePublicKey.getEncoded();
byte[] ourPublic = localKeyPair.getPublic().getEncoded();
boolean alice = compare(ourPublic, theirPublic) < 0;
byte[][] inputs = {
alice ? ourPublic : theirPublic,
alice ? theirPublic : ourPublic
};
return crypto.deriveSharedSecret(ROOT_KEY_LABEL, remotePublicKey,
localKeyPair, inputs);
}
@Override
public PublicKey parsePublicKey(byte[] encoded) throws FormatException {
try {
return crypto.getAgreementKeyParser().parsePublicKey(encoded);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
@Override
public PrivateKey parsePrivateKey(byte[] encoded) throws FormatException {
try {
return crypto.getAgreementKeyParser().parsePrivateKey(encoded);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
}

View File

@@ -0,0 +1,409 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
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.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
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.transport.KeyManager;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.Bytes.compare;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
@Immutable
@NotNullByDefault
class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
implements TransportKeyAgreementManager, OpenDatabaseHook, ContactHook,
ClientVersioningHook {
private static final Logger LOG =
getLogger(TransportKeyAgreementManagerImpl.class.getName());
private final ContactGroupFactory contactGroupFactory;
private final ClientVersioningManager clientVersioningManager;
private final IdentityManager identityManager;
private final KeyManager keyManager;
private final MessageEncoder messageEncoder;
private final SessionEncoder sessionEncoder;
private final SessionParser sessionParser;
private final TransportKeyAgreementCrypto crypto;
private final List<TransportId> transports;
private final Group localGroup;
@Inject
TransportKeyAgreementManagerImpl(
DatabaseComponent db,
ClientHelper clientHelper,
MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory,
ClientVersioningManager clientVersioningManager,
IdentityManager identityManager,
KeyManager keyManager,
MessageEncoder messageEncoder,
SessionEncoder sessionEncoder,
SessionParser sessionParser,
TransportKeyAgreementCrypto crypto,
PluginConfig config) {
super(db, clientHelper, metadataParser);
this.contactGroupFactory = contactGroupFactory;
this.clientVersioningManager = clientVersioningManager;
this.identityManager = identityManager;
this.keyManager = keyManager;
this.messageEncoder = messageEncoder;
this.sessionEncoder = sessionEncoder;
this.sessionParser = sessionParser;
this.crypto = crypto;
transports = new ArrayList<>();
for (DuplexPluginFactory duplex : config.getDuplexFactories()) {
transports.add(duplex.getId());
}
for (SimplexPluginFactory simplex : config.getSimplexFactories()) {
transports.add(simplex.getId());
}
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
}
@Override
public void onDatabaseOpened(Transaction txn) throws DbException {
Collection<Contact> contacts = db.getContacts(txn);
if (!db.containsGroup(txn, localGroup.getId())) {
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : contacts) addingContact(txn, c);
}
// Find any contacts and transports that need keys
Map<ContactId, Collection<TransportId>> transportsWithKeys =
db.getTransportsWithKeys(txn);
for (Contact c : contacts) {
Collection<TransportId> withKeys =
transportsWithKeys.get(c.getId());
for (TransportId t : transports) {
if (withKeys == null || !withKeys.contains(t)) {
// We need keys for this contact and transport
GroupId contactGroupId = getContactGroup(c).getId();
SavedSession ss = loadSession(txn, contactGroupId, t);
if (ss == null) {
// Start a session by sending our key message
startSession(txn, contactGroupId, t);
}
}
}
}
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact
Group g = getContactGroup(c);
db.addGroup(txn, g);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
// Apply the client's visibility to the contact group
Visibility client = clientVersioningManager.getClientVisibility(txn,
c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c));
}
@Override
public void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException {
// Apply the client's visibility to the contact group
Group g = getContactGroup(c);
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
}
@Override
protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfList body, BdfDictionary meta)
throws DbException, FormatException {
MessageType type = MessageType.fromValue(
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
if (LOG.isLoggable(INFO)) {
LOG.info("Received " + type + " message for " + t);
}
if (!transports.contains(t)) {
// Defer handling the message until we support the transport
return DEFER;
}
SavedSession ss = loadSession(txn, m.getGroupId(), t);
if (type == KEY) return handleKeyMessage(txn, t, m, meta, ss);
else if (type == ACTIVATE) return handleActivateMessage(txn, t, ss);
else throw new AssertionError();
}
private DeliveryAction handleKeyMessage(Transaction txn, TransportId t,
Message m, BdfDictionary meta, @Nullable SavedSession ss)
throws DbException, FormatException {
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
boolean haveKeys = db.containsTransportKeys(txn, c, t);
if (ss == null) {
if (haveKeys) {
// We have keys but no session, so we must have derived keys
// when adding the contact. If the contact didn't support
// the transport when they added us, they wouldn't have
// derived keys at that time. If they later added support for
// the transport then they would have started a session, so a
// key message is valid in this case
return handleKeyMessageForNewSession(txn, c, t, m, meta);
} else {
// We don't have keys, so we should have created a session at
// startup
throw new IllegalStateException();
}
} else if (ss.session.getState() == AWAIT_KEY) {
if (haveKeys) {
// We have keys, so we shouldn't be in the AWAIT_KEY state,
// even if the contact didn't derive keys when adding us and
// later started a session
throw new IllegalStateException();
} else {
// This is the key message we're waiting for
return handleKeyMessageForExistingSession(txn, c, t, m, meta,
ss);
}
} else {
return REJECT; // Not valid in this state
}
}
private DeliveryAction handleActivateMessage(Transaction txn,
TransportId t, @Nullable SavedSession ss) throws DbException {
if (ss != null && ss.session.getState() == AWAIT_ACTIVATE) {
// Activate the keys and finish the session
KeySetId keySetId = requireNonNull(ss.session.getKeySetId());
keyManager.activateKeys(txn, singletonMap(t, keySetId));
Session session = new Session(ACTIVATED,
ss.session.getLastLocalMessageId(), null, null, null);
saveSession(txn, t, ss.storageId, session);
return ACCEPT_DO_NOT_SHARE;
} else {
return REJECT; // Not valid in this state
}
}
private DeliveryAction handleKeyMessageForNewSession(Transaction txn,
ContactId c, TransportId t, Message m, BdfDictionary meta)
throws DbException, FormatException {
KeyPair localKeyPair = crypto.generateKeyPair();
PublicKey remotePublicKey =
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
Message keyMessage = sendKeyMessage(txn, m.getGroupId(), t,
localKeyPair.getPublic());
long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp());
SecretKey rootKey;
try {
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
} catch (GeneralSecurityException e) {
return REJECT; // Invalid public key
}
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
minTimestamp, alice, false);
Message activateMessage =
sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId());
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
null, null, keySetId);
saveNewSession(txn, m.getGroupId(), t, session);
return ACCEPT_DO_NOT_SHARE;
}
private DeliveryAction handleKeyMessageForExistingSession(Transaction txn,
ContactId c, TransportId t, Message m, BdfDictionary meta,
SavedSession ss) throws DbException, FormatException {
KeyPair localKeyPair = requireNonNull(ss.session.getLocalKeyPair());
PublicKey remotePublicKey =
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
long localTimestamp = requireNonNull(ss.session.getLocalTimestamp());
long minTimestamp = min(localTimestamp, m.getTimestamp());
SecretKey rootKey;
try {
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
} catch (GeneralSecurityException e) {
return REJECT; // Invalid public key
}
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
minTimestamp, alice, false);
MessageId previousMessageId =
requireNonNull(ss.session.getLastLocalMessageId());
Message activateMessage =
sendActivateMessage(txn, m.getGroupId(), t, previousMessageId);
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
null, null, keySetId);
saveSession(txn, t, ss.storageId, session);
return ACCEPT_DO_NOT_SHARE;
}
private void startSession(Transaction txn, GroupId contactGroupId,
TransportId t) throws DbException {
KeyPair localKeyPair = crypto.generateKeyPair();
Message keyMessage = sendKeyMessage(txn, contactGroupId, t,
localKeyPair.getPublic());
Session session = new Session(AWAIT_KEY, keyMessage.getId(),
localKeyPair, keyMessage.getTimestamp(), null);
saveNewSession(txn, contactGroupId, t, session);
}
@Nullable
private SavedSession loadSession(Transaction txn, GroupId contactGroupId,
TransportId t) throws DbException {
try {
BdfDictionary query = sessionEncoder.getSessionQuery(t);
Collection<MessageId> ids =
clientHelper.getMessageIds(txn, contactGroupId, query);
if (ids.size() > 1) throw new DbException();
if (ids.isEmpty()) {
if (LOG.isLoggable(INFO)) LOG.info("No session for " + t);
return null;
}
MessageId storageId = ids.iterator().next();
BdfDictionary bdfSession =
clientHelper.getMessageMetadataAsDictionary(txn, storageId);
Session session = sessionParser.parseSession(bdfSession);
if (LOG.isLoggable(INFO)) {
LOG.info("Loaded session in state " + session.getState()
+ " for " + t);
}
return new SavedSession(session, storageId);
} catch (FormatException e) {
throw new DbException(e);
}
}
private void saveNewSession(Transaction txn, GroupId contactGroupId,
TransportId t, Session session) throws DbException {
Message m =
clientHelper.createMessageForStoringMetadata(contactGroupId);
db.addLocalMessage(txn, m, new Metadata(), false, false);
MessageId storageId = m.getId();
saveSession(txn, t, storageId, session);
}
private void saveSession(Transaction txn, TransportId t,
MessageId storageId, Session session) throws DbException {
if (LOG.isLoggable(INFO)) {
LOG.info("Saving session in state " + session.getState()
+ " for " + t);
}
BdfDictionary meta = sessionEncoder.encodeSession(session, t);
try {
clientHelper.mergeMessageMetadata(txn, storageId, meta);
} catch (FormatException e) {
throw new AssertionError();
}
}
private Message sendKeyMessage(Transaction txn, GroupId contactGroupId,
TransportId t, PublicKey publicKey) throws DbException {
Message m = messageEncoder.encodeKeyMessage(contactGroupId, t,
publicKey);
sendMessage(txn, t, m, KEY);
return m;
}
private Message sendActivateMessage(Transaction txn,
GroupId contactGroupId, TransportId t, MessageId previousMessageId)
throws DbException {
Message m = messageEncoder.encodeActivateMessage(contactGroupId, t,
previousMessageId);
sendMessage(txn, t, m, ACTIVATE);
return m;
}
private void sendMessage(Transaction txn, TransportId t, Message m,
MessageType type) throws DbException {
BdfDictionary meta =
messageEncoder.encodeMessageMetadata(t, type, true);
try {
clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) {
throw new AssertionError();
}
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c);
}
private boolean isLocalPartyAlice(Transaction txn, Contact c)
throws DbException {
Author local = identityManager.getLocalAuthor(txn);
Author remote = c.getAuthor();
return compare(local.getId().getBytes(), remote.getId().getBytes()) < 0;
}
private static class SavedSession {
private final Session session;
private final MessageId storageId;
private SavedSession(Session session, MessageId storageId) {
this.session = session;
this.storageId = storageId;
}
}
}

View File

@@ -0,0 +1,83 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MINOR_VERSION;
@Module
public class TransportKeyAgreementModule {
public static class EagerSingletons {
@Inject
TransportKeyAgreementManager transportKeyAgreementManager;
@Inject
TransportKeyAgreementValidator transportKeyAgreementValidator;
}
@Provides
@Singleton
TransportKeyAgreementManager provideTransportKeyAgreementManager(
LifecycleManager lifecycleManager,
ValidationManager validationManager,
ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
TransportKeyAgreementManagerImpl transportKeyAgreementManager) {
lifecycleManager.registerOpenDatabaseHook(transportKeyAgreementManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
MAJOR_VERSION, transportKeyAgreementManager);
contactManager.registerContactHook(transportKeyAgreementManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, transportKeyAgreementManager);
return transportKeyAgreementManager;
}
@Provides
@Singleton
TransportKeyAgreementValidator provideTransportKeyAgreementValidator(
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock, MessageEncoder messageEncoder,
ValidationManager validationManager) {
TransportKeyAgreementValidator validator =
new TransportKeyAgreementValidator(clientHelper,
metadataEncoder, clock, messageEncoder);
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validator);
return validator;
}
@Provides
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
return messageEncoder;
}
@Provides
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
return sessionEncoder;
}
@Provides
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
return sessionParser;
}
@Provides
TransportKeyAgreementCrypto provideTransportKeyAgreementCrypto(
TransportKeyAgreementCryptoImpl transportKeyAgreementCrypto) {
return transportKeyAgreementCrypto;
}
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
class TransportKeyAgreementValidator extends BdfMessageValidator {
private final MessageEncoder messageEncoder;
TransportKeyAgreementValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock,
MessageEncoder messageEncoder) {
super(clientHelper, metadataEncoder, clock);
this.messageEncoder = messageEncoder;
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
if (type == KEY) return validateKeyMessage(body);
else if (type == ACTIVATE) return validateActivateMessage(body);
else throw new AssertionError();
}
private BdfMessageContext validateKeyMessage(BdfList body)
throws FormatException {
// Message type, transport ID, public key
checkSize(body, 3);
String transportId = body.getString(1);
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
byte[] publicKey = body.getRaw(2);
checkLength(publicKey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
new TransportId(transportId), KEY, false);
meta.put(MSG_KEY_PUBLIC_KEY, publicKey);
return new BdfMessageContext(meta);
}
private BdfMessageContext validateActivateMessage(BdfList body)
throws FormatException {
// Message type, transport ID, previous message ID
checkSize(body, 3);
String transportId = body.getString(1);
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
byte[] previousMessageId = body.getRaw(2);
checkLength(previousMessageId, MessageId.LENGTH);
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
new TransportId(transportId), ACTIVATE, false);
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta, singletonList(dependency));
}
}

View File

@@ -50,6 +50,7 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -173,8 +174,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
} }
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
// Parse the new remote update // Parse the new remote update
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m)); Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
@@ -187,7 +188,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
&& latest.remote.updateVersion > newRemoteUpdateVersion) { && latest.remote.updateVersion > newRemoteUpdateVersion) {
db.deleteMessage(txn, m.getId()); db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId());
return false; return ACCEPT_DO_NOT_SHARE;
} }
// Load and parse the latest local update // Load and parse the latest local update
if (latest.local == null) throw new DbException(); if (latest.local == null) throw new DbException();
@@ -241,7 +242,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
return false; return ACCEPT_DO_NOT_SHARE;
} }
private void storeClientVersions(Transaction txn, private void storeClientVersions(Transaction txn,

View File

@@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(18).of(database).startTransaction(); exactly(19).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(18).of(database).containsContact(txn, contactId); exactly(19).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
exactly(18).of(database).abortTransaction(txn); exactly(19).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
@@ -349,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
try { try {
db.transaction(false, transaction -> db.transaction(true, transaction ->
db.getContact(transaction, contactId)); db.getContact(transaction, contactId));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -357,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
try { try {
db.transaction(false, transaction -> db.transaction(true, transaction ->
db.getUnackedMessageBytesToSend(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getMessageStatus(transaction, contactId, groupId)); db.getMessageStatus(transaction, contactId, groupId));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -365,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
try { try {
db.transaction(false, transaction -> db.transaction(true, transaction ->
db.getMessageStatus(transaction, contactId, messageId)); db.getMessageStatus(transaction, contactId, messageId));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -373,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
try { try {
db.transaction(false, transaction -> db.transaction(true, transaction ->
db.getGroupVisibility(transaction, contactId, groupId)); db.getGroupVisibility(transaction, contactId, groupId));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -381,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
try { try {
db.transaction(false, transaction -> db.transaction(true, transaction ->
db.getSyncVersions(transaction, contactId)); db.getSyncVersions(transaction, contactId));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {

View File

@@ -57,6 +57,7 @@ import java.util.concurrent.atomic.AtomicLong;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@@ -222,18 +223,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// The contact has not seen the message, so it should be sendable // The contact has not seen the message, so it should be sendable
Collection<MessageId> ids = assertOneMessageToSendEagerly(db, txn);
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendLazily(db, txn);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// Changing the status to seen = true should make the message unsendable // Changing the status to seen = true should make the message unsendable
db.raiseSeenFlag(txn, contactId, messageId); db.raiseSeenFlag(txn, contactId, messageId);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendLazily(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -253,32 +249,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, UNKNOWN, true, false, null); db.addMessage(txn, message, UNKNOWN, true, false, null);
// The message has not been validated, so it should not be sendable // The message has not been validated, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertNothingToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Marking the message delivered should make it sendable // Marking the message delivered should make it sendable
db.setMessageState(txn, messageId, DELIVERED); db.setMessageState(txn, messageId, DELIVERED);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendLazily(db, txn);
assertEquals(singletonList(messageId), ids); assertOneMessageToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// Marking the message invalid should make it unsendable // Marking the message invalid should make it unsendable
db.setMessageState(txn, messageId, INVALID); db.setMessageState(txn, messageId, INVALID);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Marking the message pending should make it unsendable // Marking the message pending should make it unsendable
db.setMessageState(txn, messageId, PENDING); db.setMessageState(txn, messageId, PENDING);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -297,39 +284,28 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// The group is invisible, so the message should not be sendable // The group is invisible, so the message should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertNothingToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Making the group visible should not make the message sendable // Making the group visible should not make the message sendable
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Sharing the group should make the message sendable // Sharing the group should make the message sendable
db.setGroupVisibility(txn, contactId, groupId, true); db.setGroupVisibility(txn, contactId, groupId, true);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendEagerly(db, txn);
assertEquals(singletonList(messageId), ids); assertOneMessageToSendLazily(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// Unsharing the group should make the message unsendable // Unsharing the group should make the message unsendable
db.setGroupVisibility(txn, contactId, groupId, false); db.setGroupVisibility(txn, contactId, groupId, false);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Making the group invisible should make the message unsendable // Making the group invisible should make the message unsendable
db.removeGroupVisibility(txn, contactId, groupId); db.removeGroupVisibility(txn, contactId, groupId);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -349,18 +325,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, false, false, null); db.addMessage(txn, message, DELIVERED, false, false, null);
// The message is not shared, so it should not be sendable // The message is not shared, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertNothingToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Sharing the message should make it sendable // Sharing the message should make it sendable
db.setMessageShared(txn, messageId, true); db.setMessageShared(txn, messageId, true);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendLazily(db, txn);
assertEquals(singletonList(messageId), ids); assertOneMessageToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -380,10 +351,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// The message is sendable, but too large to send // The message is sendable, but too large to send
assertOneMessageToSendLazily(db, txn);
assertOneMessageToSendEagerly(db, txn);
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1, db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
MAX_LATENCY); MAX_LATENCY);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
// The message is just the right size to send // The message is just the right size to send
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
MAX_LATENCY); MAX_LATENCY);
@@ -405,6 +379,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
// Initially there should be nothing to send
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
// Add some messages to ack // Add some messages to ack
Message message1 = getMessage(groupId); Message message1 = getMessage(groupId);
MessageId messageId1 = message1.getId(); MessageId messageId1 = message1.getId();
@@ -412,6 +392,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message1, DELIVERED, true, false, contactId); db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -419,6 +403,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
// Both message IDs should have been removed // Both message IDs should have been removed
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
assertEquals(emptyList(), db.getMessagesToAck(txn, assertEquals(emptyList(), db.getMessagesToAck(txn,
contactId, 1234)); contactId, 1234));
@@ -427,6 +415,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.raiseAckFlag(txn, contactId, messageId1); db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
ids = db.getMessagesToAck(txn, contactId, 1234); ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -447,22 +439,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// Retrieve the message from the database and mark it as sent // The message should be sendable via lazy or eager retransmission
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertOneMessageToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendEagerly(db, txn);
assertEquals(singletonList(messageId), ids);
// Mark the message as sent
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY); db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
// The message should no longer be sendable // The message should no longer be sendable via lazy retransmission,
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); // but it should still be sendable via eager retransmission
assertTrue(ids.isEmpty()); assertNothingToSendLazily(db, txn);
assertOneMessageToSendEagerly(db, txn);
// Pretend that the message was acked // Mark the message as acked
db.raiseSeenFlag(txn, contactId, messageId); db.raiseSeenFlag(txn, contactId, messageId);
// The message still should not be sendable // The message still should not be sendable via lazy or eager
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); // retransmission
assertTrue(ids.isEmpty()); assertNothingToSendLazily(db, txn);
assertNothingToSendEagerly(db, txn);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -676,7 +671,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Initially there should be no transport keys in the database // Initially there should be no transport keys in the database
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
// Add the contact, the transport and the transport keys // Add the contact, the transport and the transport keys
db.addIdentity(txn, identity); db.addIdentity(txn, identity);
@@ -687,6 +684,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1)); assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
// Retrieve the transport keys // Retrieve the transport keys
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
Collection<TransportKeySet> allKeys = Collection<TransportKeySet> allKeys =
db.getTransportKeys(txn, transportId); db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
@@ -699,6 +697,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertKeysEquals(keys1, ks.getKeys()); assertKeysEquals(keys1, ks.getKeys());
} }
} }
assertEquals(singletonMap(contactId, singletonList(transportId)),
db.getTransportsWithKeys(txn));
// Update the transport keys // Update the transport keys
TransportKeys updated = createTransportKeys(timePeriod + 1, active); TransportKeys updated = createTransportKeys(timePeriod + 1, active);
@@ -710,6 +710,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
null, updated1)); null, updated1));
// Retrieve the transport keys again // Retrieve the transport keys again
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
allKeys = db.getTransportKeys(txn, transportId); allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (TransportKeySet ks : allKeys) { for (TransportKeySet ks : allKeys) {
@@ -721,10 +722,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertKeysEquals(updated1, ks.getKeys()); assertKeysEquals(updated1, ks.getKeys());
} }
} }
assertEquals(singletonMap(contactId, singletonList(transportId)),
db.getTransportsWithKeys(txn));
// Removing the contact should remove the transport keys // Removing the contact should remove the transport keys
db.removeContact(txn, contactId); db.removeContact(txn, contactId);
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -1925,11 +1930,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
// The message should be sendable // The message should be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertOneMessageToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendEagerly(db, txn);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// The message should be available // The message should be available
Message m = db.getMessage(txn, messageId); Message m = db.getMessage(txn, messageId);
@@ -1945,10 +1947,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
// The message should not be sendable // The message should not be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Requesting the message should throw an exception // Requesting the message should throw an exception
try { try {
@@ -2577,6 +2577,50 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
deleteTestDirectory(testDir); deleteTestDirectory(testDir);
} }
private void assertNothingToSendLazily(Database<Connection> db,
Connection txn) throws Exception {
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
}
private void assertOneMessageToSendLazily(Database<Connection> db,
Connection txn) throws Exception {
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
}
private void assertNothingToSendEagerly(Database<Connection> db,
Connection txn) throws Exception {
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Map<MessageId, Integer> unacked =
db.getUnackedMessagesToSend(txn, contactId);
assertTrue(unacked.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
}
private void assertOneMessageToSendEagerly(Database<Connection> db,
Connection txn) throws Exception {
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Map<MessageId, Integer> unacked =
db.getUnackedMessagesToSend(txn, contactId);
assertEquals(singleton(messageId), unacked.keySet());
assertEquals(message.getRawLength(), unacked.get(messageId).intValue());
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
}
private static class StoppedClock implements Clock { private static class StoppedClock implements Clock {
private final long time; private final long time;

View File

@@ -0,0 +1,168 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertTrue;
public class RemovableDriveIntegrationTest extends BrambleTestCase {
private static final int TIMEOUT_MS = 5_000;
private final File testDir = getTestDirectory();
private final File aliceDir = new File(testDir, "alice");
private final File bobDir = new File(testDir, "bob");
private final SecretKey rootKey = getSecretKey();
private final long timestamp = System.currentTimeMillis();
private RemovableDriveIntegrationTestComponent alice, bob;
@Before
public void setUp() {
assertTrue(testDir.mkdirs());
alice = DaggerRemovableDriveIntegrationTestComponent.builder()
.testDatabaseConfigModule(
new TestDatabaseConfigModule(aliceDir)).build();
RemovableDriveIntegrationTestComponent.Helper
.injectEagerSingletons(alice);
bob = DaggerRemovableDriveIntegrationTestComponent.builder()
.testDatabaseConfigModule(
new TestDatabaseConfigModule(bobDir)).build();
RemovableDriveIntegrationTestComponent.Helper
.injectEagerSingletons(bob);
}
@Test
public void testWriteAndRead() throws Exception {
// Create the identities
Identity aliceIdentity =
alice.getIdentityManager().createIdentity("Alice");
Identity bobIdentity = bob.getIdentityManager().createIdentity("Bob");
// Set up the devices and get the contact IDs
ContactId bobId = setUp(alice, aliceIdentity,
bobIdentity.getLocalAuthor(), true);
ContactId aliceId = setUp(bob, bobIdentity,
aliceIdentity.getLocalAuthor(), false);
// Sync Alice's client versions and transport properties
read(bob, write(alice, bobId), 2);
// Sync Bob's client versions and transport properties
read(alice, write(bob, aliceId), 2);
}
private ContactId setUp(RemovableDriveIntegrationTestComponent device,
Identity local, Author remote, boolean alice) throws Exception {
// Add an identity for the user
IdentityManager identityManager = device.getIdentityManager();
identityManager.registerIdentity(local);
// Start the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.startServices(getSecretKey());
lifecycleManager.waitForStartup();
// Add the other user as a contact
ContactManager contactManager = device.getContactManager();
return contactManager.addContact(remote, local.getId(), rootKey,
timestamp, alice, true, true);
}
@SuppressWarnings("SameParameterValue")
private void read(RemovableDriveIntegrationTestComponent device,
File file, int deliveries) throws Exception {
// Listen for message deliveries
MessageDeliveryListener listener =
new MessageDeliveryListener(deliveries);
device.getEventBus().addListener(listener);
// Read the incoming stream
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, file.getAbsolutePath());
RemovableDriveTask reader =
device.getRemovableDriveManager().startReaderTask(p);
CountDownLatch disposedLatch = new CountDownLatch(1);
reader.addObserver(state -> {
if (state.isFinished()) disposedLatch.countDown();
});
// Wait for the messages to be delivered
assertTrue(listener.delivered.await(TIMEOUT_MS, MILLISECONDS));
// Clean up the listener
device.getEventBus().removeListener(listener);
// Wait for the reader to be disposed
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
}
private File write(RemovableDriveIntegrationTestComponent device,
ContactId contactId) throws Exception {
// Write the outgoing stream to a file
File file = File.createTempFile("sync", ".tmp", testDir);
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, file.getAbsolutePath());
RemovableDriveTask writer = device.getRemovableDriveManager()
.startWriterTask(contactId, p);
CountDownLatch disposedLatch = new CountDownLatch(1);
writer.addObserver(state -> {
if (state.isFinished()) disposedLatch.countDown();
});
// Wait for the writer to be disposed
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
// Return the file containing the stream
return file;
}
private void tearDown(RemovableDriveIntegrationTestComponent device)
throws Exception {
// Stop the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.stopServices();
lifecycleManager.waitForShutdown();
}
@After
public void tearDown() throws Exception {
// Tear down the devices
tearDown(alice);
tearDown(bob);
deleteTestDirectory(testDir);
}
@NotNullByDefault
private static class MessageDeliveryListener implements EventListener {
private final CountDownLatch delivered;
private MessageDeliveryListener(int deliveries) {
delivered = new CountDownLatch(deliveries);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
if (m.getState().equals(DELIVERED)) delivered.countDown();
}
}
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
import org.briarproject.bramble.event.DefaultEventExecutorModule;
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestSecureRandomModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
BrambleCoreModule.class,
DefaultBatteryManagerModule.class,
DefaultEventExecutorModule.class,
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
RemovableDriveIntegrationTestModule.class,
RemovableDriveModule.class,
TestSecureRandomModule.class,
TimeTravelModule.class
})
interface RemovableDriveIntegrationTestComponent
extends BrambleCoreEagerSingletons {
ContactManager getContactManager();
EventBus getEventBus();
IdentityManager getIdentityManager();
LifecycleManager getLifecycleManager();
RemovableDriveManager getRemovableDriveManager();
class Helper {
public static void injectEagerSingletons(
RemovableDriveIntegrationTestComponent c) {
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
}
}
}

View File

@@ -1,61 +1,81 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
@Module @Module
public class DesktopPluginModule extends PluginModule { class RemovableDriveIntegrationTestModule {
@Provides @Provides
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth, @Singleton
ModemPluginFactory modem, LanTcpPluginFactory lan, PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
WanTcpPluginFactory wan) {
@NotNullByDefault @NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() { PluginConfig pluginConfig = new PluginConfig() {
@Override @Override
public Collection<DuplexPluginFactory> getDuplexFactories() { public Collection<DuplexPluginFactory> getDuplexFactories() {
return asList(bluetooth, modem, lan, wan);
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return emptyList(); return emptyList();
} }
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return singletonList(drive);
}
@Override @Override
public boolean shouldPoll() { public boolean shouldPoll() {
return true; return false;
} }
@Override @Override
public Map<TransportId, List<TransportId>> getTransportPreferences() { public Map<TransportId, List<TransportId>> getTransportPreferences() {
// Prefer LAN to Bluetooth return emptyMap();
return singletonMap(BluetoothConstants.ID,
singletonList(LanTcpConstants.ID));
} }
}; };
return pluginConfig; return pluginConfig;
} }
@Provides
FeatureFlags provideFeatureFlags() {
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return true;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
@Override
public boolean shouldEnableDisappearingMessages() {
return true;
}
@Override
public boolean shouldEnableConnectViaBluetooth() {
return true;
}
};
}
} }

View File

@@ -43,6 +43,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
@@ -230,7 +231,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
assertFalse(t.incomingMessage(txn, message, meta)); assertEquals(ACCEPT_DO_NOT_SHARE,
t.incomingMessage(txn, message, meta));
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class)); assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
} }
@@ -269,7 +271,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
assertFalse(t.incomingMessage(txn, message, meta)); assertEquals(ACCEPT_DO_NOT_SHARE,
t.incomingMessage(txn, message, meta));
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class)); assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
} }
@@ -308,7 +311,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
assertFalse(t.incomingMessage(txn, message, meta)); assertEquals(ACCEPT_DO_NOT_SHARE,
t.incomingMessage(txn, message, meta));
assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class)); assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
} }

View File

@@ -17,9 +17,14 @@ import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
import org.junit.Test; import org.junit.Test;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
@@ -39,14 +44,19 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
private final Executor dbExecutor = new ImmediateExecutor(); private final Executor dbExecutor = new ImmediateExecutor();
private final ContactId contactId = getContactId(); private final ContactId contactId = getContactId();
private final TransportId transportId = getTransportId(); private final TransportId transportId = getTransportId();
private final Message message = getMessage(new GroupId(getRandomId())); private final Ack ack =
private final MessageId messageId = message.getId(); new Ack(singletonList(new MessageId(getRandomId())));
private final Message message = getMessage(new GroupId(getRandomId()),
MAX_MESSAGE_BODY_LENGTH);
private final Message message1 = getMessage(new GroupId(getRandomId()),
MAX_MESSAGE_BODY_LENGTH);
@Test @Test
public void testNothingToSend() throws Exception { public void testNothingToSend() throws Exception {
SimplexOutgoingSession session = new SimplexOutgoingSession(db, SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter); false, streamWriter, recordWriter);
Transaction noAckTxn = new Transaction(null, false); Transaction noAckTxn = new Transaction(null, false);
Transaction noMsgTxn = new Transaction(null, false); Transaction noMsgTxn = new Transaction(null, false);
@@ -63,8 +73,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
// No messages to send // No messages to send
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(noMsgTxn)); withNullableDbCallable(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId), oneOf(db).generateBatch(noMsgTxn, contactId,
with(any(int.class)), with(MAX_LATENCY)); MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
will(returnValue(null)); will(returnValue(null));
// Send the end of stream marker // Send the end of stream marker
oneOf(streamWriter).sendEndOfStream(); oneOf(streamWriter).sendEndOfStream();
@@ -76,11 +86,44 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
} }
@Test @Test
public void testSomethingToSend() throws Exception { public void testNothingToSendEagerly() throws Exception {
Ack ack = new Ack(singletonList(messageId));
SimplexOutgoingSession session = new SimplexOutgoingSession(db, SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter); true, streamWriter, recordWriter);
Transaction noAckTxn = new Transaction(null, false);
Transaction noIdsTxn = new Transaction(null, true);
context.checking(new DbExpectations() {{
// Add listener
oneOf(eventBus).addListener(session);
// Send the protocol versions
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
// No acks to send
oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null));
// No messages to send
oneOf(db).transactionWithResult(with(true),
withDbCallable(noIdsTxn));
oneOf(db).getUnackedMessagesToSend(noIdsTxn, contactId);
will(returnValue(emptyMap()));
// Send the end of stream marker
oneOf(streamWriter).sendEndOfStream();
// Remove listener
oneOf(eventBus).removeListener(session);
}});
session.run();
}
@Test
public void testSomethingToSend() throws Exception {
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
false, streamWriter, recordWriter);
Transaction ackTxn = new Transaction(null, false); Transaction ackTxn = new Transaction(null, false);
Transaction noAckTxn = new Transaction(null, false); Transaction noAckTxn = new Transaction(null, false);
Transaction msgTxn = new Transaction(null, false); Transaction msgTxn = new Transaction(null, false);
@@ -100,8 +143,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
// One message to send // One message to send
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(msgTxn)); withNullableDbCallable(msgTxn));
oneOf(db).generateBatch(with(msgTxn), with(contactId), oneOf(db).generateBatch(msgTxn, contactId,
with(any(int.class)), with(MAX_LATENCY)); MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
will(returnValue(singletonList(message))); will(returnValue(singletonList(message)));
oneOf(recordWriter).writeMessage(message); oneOf(recordWriter).writeMessage(message);
// No more acks // No more acks
@@ -112,8 +155,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
// No more messages // No more messages
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(noMsgTxn)); withNullableDbCallable(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId), oneOf(db).generateBatch(noMsgTxn, contactId,
with(any(int.class)), with(MAX_LATENCY)); MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
will(returnValue(null)); will(returnValue(null));
// Send the end of stream marker // Send the end of stream marker
oneOf(streamWriter).sendEndOfStream(); oneOf(streamWriter).sendEndOfStream();
@@ -123,4 +166,63 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
session.run(); session.run();
} }
@Test
public void testSomethingToSendEagerly() throws Exception {
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
true, streamWriter, recordWriter);
Map<MessageId, Integer> unacked = new LinkedHashMap<>();
unacked.put(message.getId(), message.getRawLength());
unacked.put(message1.getId(), message1.getRawLength());
Transaction ackTxn = new Transaction(null, false);
Transaction noAckTxn = new Transaction(null, false);
Transaction idsTxn = new Transaction(null, true);
Transaction msgTxn = new Transaction(null, false);
Transaction msgTxn1 = new Transaction(null, false);
context.checking(new DbExpectations() {{
// Add listener
oneOf(eventBus).addListener(session);
// Send the protocol versions
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
// One ack to send
oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(ackTxn));
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(ack));
oneOf(recordWriter).writeAck(ack);
// No more acks
oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null));
// Two messages to send
oneOf(db).transactionWithResult(with(true), withDbCallable(idsTxn));
oneOf(db).getUnackedMessagesToSend(idsTxn, contactId);
will(returnValue(unacked));
// Send the first message
oneOf(db).transactionWithResult(with(false),
withDbCallable(msgTxn));
oneOf(db).generateBatch(msgTxn, contactId,
singletonList(message.getId()), MAX_LATENCY);
will(returnValue(singletonList(message)));
oneOf(recordWriter).writeMessage(message);
// Send the second message
oneOf(db).transactionWithResult(with(false),
withDbCallable(msgTxn1));
oneOf(db).generateBatch(msgTxn1, contactId,
singletonList(message1.getId()), MAX_LATENCY);
will(returnValue(singletonList(message1)));
oneOf(recordWriter).writeMessage(message1);
// Send the end of stream marker
oneOf(streamWriter).sendEndOfStream();
// Remove listener
oneOf(eventBus).removeListener(session);
}});
session.run();
}
} }

View File

@@ -31,6 +31,8 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
@@ -111,7 +113,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
// Deliver the first message // Deliver the first message
oneOf(hook).incomingMessage(txn1, message, metadata); oneOf(hook).incomingMessage(txn1, message, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn1, messageId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId); oneOf(db).getMessageDependents(txn1, messageId);
@@ -167,7 +169,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
will(returnValue(new Metadata())); will(returnValue(new Metadata()));
// Deliver the message // Deliver the message
oneOf(hook).incomingMessage(txn, message, metadata); oneOf(hook).incomingMessage(txn, message, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn, messageId, DELIVERED); oneOf(db).setMessageState(txn, messageId, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn, messageId); oneOf(db).getMessageDependents(txn, messageId);
@@ -187,7 +189,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
will(returnValue(metadata)); will(returnValue(metadata));
// Deliver the dependent // Deliver the dependent
oneOf(hook).incomingMessage(txn1, message2, metadata); oneOf(hook).incomingMessage(txn1, message2, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn1, messageId2, DELIVERED); oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId2); oneOf(db).getMessageDependents(txn1, messageId2);
@@ -247,7 +249,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
// Deliver the message // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata); oneOf(hook).incomingMessage(txn1, message, metadata);
will(returnValue(true)); will(returnValue(ACCEPT_SHARE));
oneOf(db).setMessageState(txn1, messageId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId); oneOf(db).getMessageDependents(txn1, messageId);
@@ -367,7 +369,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
// Deliver the message // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata); oneOf(hook).incomingMessage(txn1, message, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn1, messageId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId); oneOf(db).getMessageDependents(txn1, messageId);
@@ -432,7 +434,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
// Deliver the message // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata); oneOf(hook).incomingMessage(txn1, message, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn1, messageId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId); oneOf(db).getMessageDependents(txn1, messageId);
@@ -602,7 +604,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
// Deliver the message // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata); oneOf(hook).incomingMessage(txn1, message, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn1, messageId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// The message has two pending dependents: 1 and 2 // The message has two pending dependents: 1 and 2
oneOf(db).getMessageDependents(txn1, messageId); oneOf(db).getMessageDependents(txn1, messageId);
@@ -622,7 +624,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
will(returnValue(metadata)); will(returnValue(metadata));
// Deliver message 1 // Deliver message 1
oneOf(hook).incomingMessage(txn2, message1, metadata); oneOf(hook).incomingMessage(txn2, message1, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn2, messageId1, DELIVERED); oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
// Message 1 has one pending dependent: 3 // Message 1 has one pending dependent: 3
oneOf(db).getMessageDependents(txn2, messageId1); oneOf(db).getMessageDependents(txn2, messageId1);
@@ -642,7 +644,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
will(returnValue(metadata)); will(returnValue(metadata));
// Deliver message 2 // Deliver message 2
oneOf(hook).incomingMessage(txn3, message2, metadata); oneOf(hook).incomingMessage(txn3, message2, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn3, messageId2, DELIVERED); oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
// Message 2 has one pending dependent: 3 (same dependent as 1) // Message 2 has one pending dependent: 3 (same dependent as 1)
oneOf(db).getMessageDependents(txn3, messageId2); oneOf(db).getMessageDependents(txn3, messageId2);
@@ -662,6 +664,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
will(returnValue(metadata)); will(returnValue(metadata));
// Deliver message 3 // Deliver message 3
oneOf(hook).incomingMessage(txn4, message3, metadata); oneOf(hook).incomingMessage(txn4, message3, metadata);
will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn4, messageId3, DELIVERED); oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
// Message 3 has one pending dependent: 4 // Message 3 has one pending dependent: 4
oneOf(db).getMessageDependents(txn4, messageId3); oneOf(db).getMessageDependents(txn4, messageId3);
@@ -685,7 +688,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
will(returnValue(metadata)); will(returnValue(metadata));
// Deliver message 4 // Deliver message 4
oneOf(hook).incomingMessage(txn6, message4, metadata); oneOf(hook).incomingMessage(txn6, message4, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn6, messageId4, DELIVERED); oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
// Message 4 has no pending dependents // Message 4 has no pending dependents
oneOf(db).getMessageDependents(txn6, messageId4); oneOf(db).getMessageDependents(txn6, messageId4);
@@ -717,7 +720,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
// Deliver the message // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata); oneOf(hook).incomingMessage(txn1, message, metadata);
will(returnValue(false)); will(returnValue(ACCEPT_DO_NOT_SHARE));
oneOf(db).setMessageState(txn1, messageId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// Get any pending dependents // Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId); oneOf(db).getMessageDependents(txn1, messageId);

View File

@@ -25,7 +25,7 @@ public class TestDuplexTransportConnection
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public TestDuplexTransportConnection(InputStream in, OutputStream out) { public TestDuplexTransportConnection(InputStream in, OutputStream out) {
reader = new TestTransportConnectionReader(in); reader = new TestTransportConnectionReader(in);
writer = new TestTransportConnectionWriter(out); writer = new TestTransportConnectionWriter(out, false);
} }
@Override @Override

View File

@@ -15,10 +15,13 @@ public class TestTransportConnectionWriter
implements TransportConnectionWriter { implements TransportConnectionWriter {
private final OutputStream out; private final OutputStream out;
private final boolean lossyAndCheap;
private final CountDownLatch disposed = new CountDownLatch(1); private final CountDownLatch disposed = new CountDownLatch(1);
public TestTransportConnectionWriter(OutputStream out) { public TestTransportConnectionWriter(OutputStream out,
boolean lossyAndCheap) {
this.out = out; this.out = out;
this.lossyAndCheap = lossyAndCheap;
} }
public CountDownLatch getDisposedLatch() { public CountDownLatch getDisposedLatch() {
@@ -35,6 +38,11 @@ public class TestTransportConnectionWriter
return 60_000; return 60_000;
} }
@Override
public boolean isLossyAndCheap() {
return lossyAndCheap;
}
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return out; return out;

View File

@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
@@ -71,8 +72,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
private final SecretKey rootKey = getSecretKey(); private final SecretKey rootKey = getSecretKey();
private final Random random = new Random(); private final Random random = new Random();
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor, private KeyManagerImpl keyManager;
pluginConfig, transportKeyManagerFactory, transportCrypto);
@Before @Before
public void testStartService() throws Exception { public void testStartService() throws Exception {
@@ -83,18 +83,25 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
singletonList(pluginFactory); singletonList(pluginFactory);
int maxLatency = 1337; int maxLatency = 1337;
context.checking(new DbExpectations() {{ context.checking(new Expectations() {{
oneOf(pluginConfig).getSimplexFactories(); allowing(pluginConfig).getSimplexFactories();
will(returnValue(factories)); will(returnValue(factories));
oneOf(pluginFactory).getId(); allowing(pluginFactory).getId();
will(returnValue(transportId)); will(returnValue(transportId));
oneOf(pluginFactory).getMaxLatency(); allowing(pluginFactory).getMaxLatency();
will(returnValue(maxLatency)); will(returnValue(maxLatency));
oneOf(db).addTransport(txn, transportId, maxLatency); allowing(pluginConfig).getDuplexFactories();
will(returnValue(emptyList()));
oneOf(transportKeyManagerFactory) oneOf(transportKeyManagerFactory)
.createTransportKeyManager(transportId, maxLatency); .createTransportKeyManager(transportId, maxLatency);
will(returnValue(transportKeyManager)); will(returnValue(transportKeyManager));
oneOf(pluginConfig).getDuplexFactories(); }});
keyManager = new KeyManagerImpl(db, executor,
pluginConfig, transportCrypto, transportKeyManagerFactory);
context.checking(new DbExpectations() {{
oneOf(db).addTransport(txn, transportId, maxLatency);
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(transportKeyManager).start(txn); oneOf(transportKeyManager).start(txn);
}}); }});
@@ -235,4 +242,37 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
keyManager.eventOccurred(event); keyManager.eventOccurred(event);
executor.runUntilIdle(); executor.runUntilIdle();
} }
@Test
public void testAddMultipleRotationKeySets() throws Exception {
long timestamp = System.currentTimeMillis();
boolean alice = random.nextBoolean();
boolean active = random.nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addRotationKeys(txn, contactId,
rootKey, timestamp, alice, active);
will(returnValue(keySetId));
}});
assertEquals(singletonMap(transportId, keySetId),
keyManager.addRotationKeys(txn, contactId, rootKey, timestamp,
alice, active));
}
@Test
public void testAddSingleRotationKeySet() throws Exception {
long timestamp = System.currentTimeMillis();
boolean alice = random.nextBoolean();
boolean active = random.nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addRotationKeys(txn, contactId,
rootKey, timestamp, alice, active);
will(returnValue(keySetId));
}});
assertEquals(keySetId, keyManager.addRotationKeys(txn, contactId,
transportId, rootKey, timestamp, alice, active));
}
} }

View File

@@ -0,0 +1,554 @@
package org.briarproject.bramble.transport.agreement;
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.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
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.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicReference;
import static java.lang.Math.min;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.Bytes.compare;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final MetadataParser metadataParser =
context.mock(MetadataParser.class);
private final ContactGroupFactory contactGroupFactory =
context.mock(ContactGroupFactory.class);
private final ClientVersioningManager clientVersioningManager =
context.mock(ClientVersioningManager.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final KeyManager keyManager = context.mock(KeyManager.class);
private final MessageEncoder messageEncoder =
context.mock(MessageEncoder.class);
private final SessionEncoder sessionEncoder =
context.mock(SessionEncoder.class);
private final SessionParser sessionParser =
context.mock(SessionParser.class);
private final TransportKeyAgreementCrypto crypto =
context.mock(TransportKeyAgreementCrypto.class);
private final PluginConfig pluginConfig = context.mock(PluginConfig.class);
private final SimplexPluginFactory simplexFactory =
context.mock(SimplexPluginFactory.class);
private final DuplexPluginFactory duplexFactory =
context.mock(DuplexPluginFactory.class);
private final TransportId simplexTransportId = getTransportId();
private final TransportId duplexTransportId = getTransportId();
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Contact contact = getContact();
private final LocalAuthor localAuthor = getLocalAuthor();
private final boolean alice = compare(localAuthor.getId().getBytes(),
contact.getAuthor().getId().getBytes()) < 0;
private final KeyPair localKeyPair =
new KeyPair(getAgreementPublicKey(), getAgreementPrivateKey());
private final PublicKey remotePublicKey = getAgreementPublicKey();
private final SecretKey rootKey = getSecretKey();
private final KeySetId keySetId = new KeySetId(123);
private final Message storageMessage = getMessage(contactGroup.getId());
private final Message localKeyMessage = getMessage(contactGroup.getId());
private final Message localActivateMessage =
getMessage(contactGroup.getId());
private final Message remoteKeyMessage = getMessage(contactGroup.getId());
private final Message remoteActivateMessage =
getMessage(contactGroup.getId());
private final long localTimestamp = localKeyMessage.getTimestamp();
private final long remoteTimestamp = remoteKeyMessage.getTimestamp();
// These query and metadata dictionaries are handled by the manager without
// inspecting their contents, so we can use empty dictionaries for testing
private final BdfDictionary sessionQuery = new BdfDictionary();
private final BdfDictionary sessionMeta = new BdfDictionary();
private final BdfDictionary localKeyMeta = new BdfDictionary();
private final BdfDictionary localActivateMeta = new BdfDictionary();
// The manager doesn't use the incoming message body, so it can be empty
private final BdfList remoteMessageBody = new BdfList();
private final BdfDictionary remoteKeyMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_MESSAGE_TYPE, KEY.getValue()),
new BdfEntry(MSG_KEY_TRANSPORT_ID,
simplexTransportId.getString()),
new BdfEntry(MSG_KEY_PUBLIC_KEY, remotePublicKey.getEncoded()));
private final BdfDictionary remoteActivateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_MESSAGE_TYPE, ACTIVATE.getValue()),
new BdfEntry(MSG_KEY_TRANSPORT_ID,
simplexTransportId.getString()));
private TransportKeyAgreementManagerImpl manager;
@Before
public void setUp() {
context.checking(new Expectations() {{
oneOf(pluginConfig).getSimplexFactories();
will(returnValue(singletonList(simplexFactory)));
oneOf(simplexFactory).getId();
will(returnValue(simplexTransportId));
oneOf(pluginConfig).getDuplexFactories();
will(returnValue(singletonList(duplexFactory)));
oneOf(duplexFactory).getId();
will(returnValue(duplexTransportId));
oneOf(contactGroupFactory)
.createLocalGroup(CLIENT_ID, MAJOR_VERSION);
will(returnValue(localGroup));
}});
manager = new TransportKeyAgreementManagerImpl(db, clientHelper,
metadataParser, contactGroupFactory, clientVersioningManager,
identityManager, keyManager, messageEncoder, sessionEncoder,
sessionParser, crypto, pluginConfig);
}
@Test
public void testCreatesContactGroupAtStartupIfLocalGroupDoesNotExist()
throws Exception {
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
// The local group doesn't exist so we need to create contact groups
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, localGroup);
// Create the contact group and set it up
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
oneOf(db).addGroup(txn, contactGroup);
oneOf(clientHelper)
.setContactId(txn, contactGroup.getId(), contact.getId());
oneOf(clientVersioningManager).getClientVisibility(txn,
contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(VISIBLE));
oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), VISIBLE);
// We already have keys for both transports
oneOf(db).getTransportsWithKeys(txn);
will(returnValue(singletonMap(contact.getId(),
asList(simplexTransportId, duplexTransportId))));
}});
manager.onDatabaseOpened(txn);
}
@Test
public void testDoesNotCreateContactGroupAtStartupIfLocalGroupExists()
throws Exception {
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
// The local group exists so we don't need to create contact groups
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(true));
// We already have keys for both transports
oneOf(db).getTransportsWithKeys(txn);
will(returnValue(singletonMap(contact.getId(),
asList(simplexTransportId, duplexTransportId))));
}});
manager.onDatabaseOpened(txn);
}
@Test
public void testStartsSessionAtStartup() throws Exception {
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
// The local group exists so we don't need to create contact groups
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(true));
// We need keys for the simplex transport
oneOf(db).getTransportsWithKeys(txn);
will(returnValue(singletonMap(contact.getId(),
singletonList(duplexTransportId))));
// Get the contact group ID
oneOf(contactGroupFactory)
.createContactGroup(CLIENT_ID, MAJOR_VERSION, contact);
will(returnValue(contactGroup));
}});
// Check whether a session exists - it doesn't
expectSessionDoesNotExist(txn);
// Generate the local key pair
expectGenerateLocalKeyPair();
// Send a key message
expectSendKeyMessage(txn);
// Save the session
expectCreateStorageMessage(txn);
AtomicReference<Session> savedSession = expectSaveSession(txn);
manager.onDatabaseOpened(txn);
assertEquals(AWAIT_KEY, savedSession.get().getState());
assertEquals(localKeyMessage.getId(),
savedSession.get().getLastLocalMessageId());
assertEquals(localKeyPair, savedSession.get().getLocalKeyPair());
assertEquals(Long.valueOf(localTimestamp),
savedSession.get().getLocalTimestamp());
assertNull(savedSession.get().getKeySetId());
}
@Test
public void testDefersMessageIfTransportIsNotSupported() throws Exception {
Transaction txn = new Transaction(null, false);
TransportId unknownTransportId = getTransportId();
BdfDictionary meta = new BdfDictionary(remoteKeyMeta);
meta.put(MSG_KEY_TRANSPORT_ID, unknownTransportId.getString());
assertEquals(DEFER, manager.incomingMessage(txn, remoteKeyMessage,
remoteMessageBody, meta));
}
@Test
public void testAcceptsKeyMessageInAwaitKeyState() throws Exception {
Transaction txn = new Transaction(null, false);
Session loadedSession = new Session(AWAIT_KEY,
localKeyMessage.getId(), localKeyPair, localTimestamp, null);
// Check whether a session exists - it does
expectLoadSession(txn, loadedSession);
// Load the contact ID
expectLoadContactId(txn);
// Check whether we already have keys - we don't
expectKeysExist(txn, false);
// Parse the remote public key
expectParseRemotePublicKey();
// Derive and store the transport keys
expectDeriveAndStoreTransportKeys(txn);
// Send an activate message
expectSendActivateMessage(txn);
// Save the session
AtomicReference<Session> savedSession = expectSaveSession(txn);
assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn,
remoteKeyMessage, remoteMessageBody, remoteKeyMeta));
assertEquals(AWAIT_ACTIVATE, savedSession.get().getState());
assertEquals(localActivateMessage.getId(),
savedSession.get().getLastLocalMessageId());
assertNull(savedSession.get().getLocalKeyPair());
assertNull(savedSession.get().getLocalTimestamp());
assertEquals(keySetId, savedSession.get().getKeySetId());
}
@Test
public void testAcceptsKeyMessageIfWeHaveTransportKeysButNoSession()
throws Exception {
Transaction txn = new Transaction(null, false);
// Check whether a session exists - it doesn't
expectSessionDoesNotExist(txn);
// Load the contact ID
expectLoadContactId(txn);
// Check whether we already have keys - we do
expectKeysExist(txn, true);
// Generate the local key pair
expectGenerateLocalKeyPair();
// Parse the remote public key
expectParseRemotePublicKey();
// Send a key message
expectSendKeyMessage(txn);
// Derive and store the transport keys
expectDeriveAndStoreTransportKeys(txn);
// Send an activate message
expectSendActivateMessage(txn);
// Save the session
expectCreateStorageMessage(txn);
AtomicReference<Session> savedSession = expectSaveSession(txn);
assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn,
remoteKeyMessage, remoteMessageBody, remoteKeyMeta));
assertEquals(AWAIT_ACTIVATE, savedSession.get().getState());
assertEquals(localActivateMessage.getId(),
savedSession.get().getLastLocalMessageId());
assertNull(savedSession.get().getLocalKeyPair());
assertNull(savedSession.get().getLocalTimestamp());
assertEquals(keySetId, savedSession.get().getKeySetId());
}
@Test
public void testRejectsKeyMessageInAwaitActivateState() throws Exception {
Session loadedSession = new Session(AWAIT_ACTIVATE,
localActivateMessage.getId(), null, null, keySetId);
testRejectsKeyMessageWithExistingSession(loadedSession);
}
@Test
public void testRejectsKeyMessageInActivatedState() throws Exception {
Session loadedSession = new Session(ACTIVATED,
localActivateMessage.getId(), null, null, null);
testRejectsKeyMessageWithExistingSession(loadedSession);
}
private void testRejectsKeyMessageWithExistingSession(Session loadedSession)
throws Exception {
Transaction txn = new Transaction(null, false);
// Check whether a session exists - it does
expectLoadSession(txn, loadedSession);
// Load the contact ID
expectLoadContactId(txn);
// Check whether we already have keys - we don't
expectKeysExist(txn, false);
assertEquals(REJECT, manager.incomingMessage(txn,
remoteKeyMessage, remoteMessageBody, remoteKeyMeta));
}
@Test
public void testAcceptsActivateMessageInAwaitActivateState()
throws Exception {
Transaction txn = new Transaction(null, false);
Session loadedSession = new Session(AWAIT_ACTIVATE,
localActivateMessage.getId(), null, null, keySetId);
// Check whether a session exists - it does
expectLoadSession(txn, loadedSession);
// Activate the transport keys
context.checking(new Expectations() {{
oneOf(keyManager).activateKeys(txn,
singletonMap(simplexTransportId, keySetId));
}});
// Save the session
AtomicReference<Session> savedSession = expectSaveSession(txn);
assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn,
remoteActivateMessage, remoteMessageBody, remoteActivateMeta));
assertEquals(ACTIVATED, savedSession.get().getState());
assertEquals(localActivateMessage.getId(),
savedSession.get().getLastLocalMessageId());
assertNull(savedSession.get().getLocalKeyPair());
assertNull(savedSession.get().getLocalTimestamp());
assertNull(savedSession.get().getKeySetId());
}
@Test
public void testRejectsActivateMessageWithNoSession() throws Exception {
Transaction txn = new Transaction(null, false);
// Check whether a session exists - it doesn't
expectSessionDoesNotExist(txn);
assertEquals(REJECT, manager.incomingMessage(txn,
remoteActivateMessage, remoteMessageBody, remoteActivateMeta));
}
@Test
public void testRejectsActivateMessageInAwaitKeyState() throws Exception {
Session loadedSession = new Session(AWAIT_KEY,
localKeyMessage.getId(), localKeyPair, localTimestamp, null);
testRejectsActivateMessageWithExistingSession(loadedSession);
}
@Test
public void testRejectsActivateMessageInActivatedState() throws Exception {
Session loadedSession = new Session(ACTIVATED,
localActivateMessage.getId(), null, null, null);
testRejectsActivateMessageWithExistingSession(loadedSession);
}
private void testRejectsActivateMessageWithExistingSession(
Session loadedSession) throws Exception {
Transaction txn = new Transaction(null, false);
// Check whether a session exists - it does
expectLoadSession(txn, loadedSession);
assertEquals(REJECT, manager.incomingMessage(txn,
remoteActivateMessage, remoteMessageBody, remoteActivateMeta));
}
private void expectSessionDoesNotExist(Transaction txn) throws Exception {
context.checking(new Expectations() {{
oneOf(sessionEncoder).getSessionQuery(simplexTransportId);
will(returnValue(sessionQuery));
oneOf(clientHelper)
.getMessageIds(txn, contactGroup.getId(), sessionQuery);
will(returnValue(emptyList()));
}});
}
private void expectLoadSession(Transaction txn, Session loadedSession)
throws Exception {
context.checking(new Expectations() {{
oneOf(sessionEncoder).getSessionQuery(simplexTransportId);
will(returnValue(sessionQuery));
oneOf(clientHelper)
.getMessageIds(txn, contactGroup.getId(), sessionQuery);
will(returnValue(singletonList(storageMessage.getId())));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
storageMessage.getId());
will(returnValue(sessionMeta));
oneOf(sessionParser).parseSession(sessionMeta);
will(returnValue(loadedSession));
}});
}
private void expectSendKeyMessage(Transaction txn) throws Exception {
context.checking(new Expectations() {{
oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(),
simplexTransportId, localKeyPair.getPublic());
will(returnValue(localKeyMessage));
oneOf(messageEncoder)
.encodeMessageMetadata(simplexTransportId, KEY, true);
will(returnValue(localKeyMeta));
oneOf(clientHelper).addLocalMessage(txn, localKeyMessage,
localKeyMeta, true, false);
}});
}
private void expectSendActivateMessage(Transaction txn) throws Exception {
context.checking(new Expectations() {{
oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(),
simplexTransportId, localKeyMessage.getId());
will(returnValue(localActivateMessage));
oneOf(messageEncoder)
.encodeMessageMetadata(simplexTransportId, ACTIVATE, true);
will(returnValue(localActivateMeta));
oneOf(clientHelper).addLocalMessage(txn, localActivateMessage,
localActivateMeta, true, false);
}});
}
private void expectCreateStorageMessage(Transaction txn) throws Exception {
context.checking(new Expectations() {{
oneOf(clientHelper)
.createMessageForStoringMetadata(contactGroup.getId());
will(returnValue(storageMessage));
oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(),
false, false);
}});
}
private AtomicReference<Session> expectSaveSession(Transaction txn)
throws Exception {
AtomicReference<Session> savedSession = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(sessionEncoder).encodeSession(with(any(Session.class)),
with(simplexTransportId));
will(doAll(
new CaptureArgumentAction<>(savedSession, Session.class, 0),
returnValue(sessionMeta)));
oneOf(clientHelper).mergeMessageMetadata(txn,
storageMessage.getId(), sessionMeta);
}});
return savedSession;
}
private void expectLoadContactId(Transaction txn) throws Exception {
context.checking(new Expectations() {{
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
}});
}
private void expectGenerateLocalKeyPair() {
context.checking(new Expectations() {{
oneOf(crypto).generateKeyPair();
will(returnValue(localKeyPair));
}});
}
private void expectParseRemotePublicKey() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded());
will(returnValue(remotePublicKey));
}});
}
private void expectDeriveAndStoreTransportKeys(Transaction txn)
throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey);
will(returnValue(rootKey));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(keyManager).addRotationKeys(txn, contact.getId(),
simplexTransportId, rootKey,
min(localTimestamp, remoteTimestamp), alice, false);
will(returnValue(keySetId));
}});
}
private void expectKeysExist(Transaction txn, boolean exist)
throws Exception {
context.checking(new Expectations() {{
oneOf(db).containsTransportKeys(txn, contact.getId(),
simplexTransportId);
will(returnValue(exist));
}});
}
}

View File

@@ -31,6 +31,7 @@ import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
@@ -41,7 +42,6 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class ClientVersioningManagerImplTest extends BrambleMockTestCase { public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
@@ -419,7 +419,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook); c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); assertEquals(ACCEPT_DO_NOT_SHARE,
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
} }
@Test @Test
@@ -464,7 +465,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook); c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); assertEquals(ACCEPT_DO_NOT_SHARE,
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
} }
@Test @Test
@@ -496,7 +498,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook); c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); assertEquals(ACCEPT_DO_NOT_SHARE,
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
} }
@Test @Test
@@ -579,7 +582,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook); c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); assertEquals(ACCEPT_DO_NOT_SHARE,
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
} }
@Test @Test
@@ -649,7 +653,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook); c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); assertEquals(ACCEPT_DO_NOT_SHARE,
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
} }
@Test @Test

View File

@@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
private final File torDirectory; private final File torDirectory;
@Inject @Inject
public UnixTorPluginFactory(@IoExecutor Executor ioExecutor, UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
@IoExecutor Executor wakefulIoExecutor, @IoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager, NetworkManager networkManager,
LocationUtils locationUtils, LocationUtils locationUtils,

View File

@@ -28,6 +28,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager; import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule; import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
@@ -83,7 +84,8 @@ import dagger.Component;
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
ClockModule.class, ClockModule.class,
MediaModule.class MediaModule.class,
RemovableDriveModule.class
}) })
public interface AndroidComponent public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons, extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,

View File

@@ -24,6 +24,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevConfig; import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory; import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
@@ -67,7 +69,6 @@ import dagger.Provides;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
@@ -92,6 +93,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
GroupListModule.class, GroupListModule.class,
GroupConversationModule.class, GroupConversationModule.class,
SharingModule.class, SharingModule.class,
RemovableDriveModule.class
}) })
public class AppModule { public class AppModule {
@@ -149,8 +151,10 @@ public class AppModule {
} }
@Provides @Provides
@Singleton
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth, PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) { AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
AndroidRemovableDrivePluginFactory drive) {
@NotNullByDefault @NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() { PluginConfig pluginConfig = new PluginConfig() {
@@ -161,7 +165,7 @@ public class AppModule {
@Override @Override
public Collection<SimplexPluginFactory> getSimplexFactories() { public Collection<SimplexPluginFactory> getSimplexFactories() {
return emptyList(); return singletonList(drive);
} }
@Override @Override

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.removabledrive;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public interface RemovableDriveModule {
@Binds
@IntoMap
@ViewModelKey(RemovableDriveViewModel.class)
ViewModel bindRemovableDriveViewModel(RemovableDriveViewModel removableDriveViewModel);
}

View File

@@ -0,0 +1,101 @@
package org.briarproject.briar.android.removabledrive;
import android.app.Application;
import android.net.Uri;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.Locale.US;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
@NotNullByDefault
class RemovableDriveViewModel extends AndroidViewModel {
private static final Logger LOG =
getLogger(RemovableDriveViewModel.class.getName());
private final RemovableDriveManager manager;
private final ConcurrentHashMap<Consumer<State>, RemovableDriveTask>
observers = new ConcurrentHashMap<>();
@Inject
RemovableDriveViewModel(Application app,
RemovableDriveManager removableDriveManager) {
super(app);
this.manager = removableDriveManager;
}
String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", US);
return sdf.format(new Date());
}
LiveData<State> write(ContactId contactId, Uri uri) {
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
return observe(manager.startWriterTask(contactId, p));
}
LiveData<State> read(Uri uri) {
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
return observe(manager.startReaderTask(p));
}
@Nullable
LiveData<State> ongoingWrite() {
RemovableDriveTask task = manager.getCurrentWriterTask();
if (task == null) {
return null;
}
return observe(task);
}
@Nullable
LiveData<State> ongoingRead() {
RemovableDriveTask task = manager.getCurrentReaderTask();
if (task == null) {
return null;
}
return observe(task);
}
private LiveData<State> observe(RemovableDriveTask task) {
MutableLiveData<State> state = new MutableLiveData<>();
Consumer<State> observer = state::postValue;
task.addObserver(observer);
observers.put(observer, task);
return state;
}
@Override
protected void onCleared() {
for (Map.Entry<Consumer<State>, RemovableDriveTask> entry
: observers.entrySet()) {
entry.getValue().removeObserver(entry.getKey());
}
}
}

View File

@@ -40,6 +40,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE; import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION; import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
@@ -124,8 +125,8 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
} }
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
Group ourGroup = getOurGroup(txn); Group ourGroup = getOurGroup(txn);
if (m.getGroupId().equals(ourGroup.getId())) { if (m.getGroupId().equals(ourGroup.getId())) {
throw new InvalidMessageException( throw new InvalidMessageException(
@@ -144,7 +145,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
// We've already received a newer update - delete this one // We've already received a newer update - delete this one
db.deleteMessage(txn, m.getId()); db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId());
return false; // don't broadcast update return ACCEPT_DO_NOT_SHARE;
} }
} }
ContactId contactId = getContactId(txn, m.getGroupId()); ContactId contactId = getContactId(txn, m.getGroupId());
@@ -155,7 +156,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
return false; return ACCEPT_DO_NOT_SHARE;
} }
@Override @Override

View File

@@ -50,6 +50,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR; import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT; import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
@@ -109,8 +111,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList list, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfDictionary meta) throws DbException, FormatException { BdfList list, BdfDictionary meta)
throws DbException, FormatException {
GroupId groupId = m.getGroupId(); GroupId groupId = m.getGroupId();
MessageType type = getMessageType(meta); MessageType type = getMessageType(meta);
@@ -138,7 +141,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
txn.attach(event); txn.attach(event);
// shares message and its dependencies // shares message and its dependencies
return true; return ACCEPT_SHARE;
} else if (type == WRAPPED_COMMENT) { } else if (type == WRAPPED_COMMENT) {
// Check that the original message ID in the dependency's metadata // Check that the original message ID in the dependency's metadata
// matches the original parent ID of the wrapped comment // matches the original parent ID of the wrapped comment
@@ -153,7 +156,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
} }
// don't share message until parent arrives // don't share message until parent arrives
return false; return ACCEPT_DO_NOT_SHARE;
} }
@Override @Override

View File

@@ -45,6 +45,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR; import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL; import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT; import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
@@ -75,8 +76,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
} }
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfDictionary meta) throws DbException, FormatException { BdfList body, BdfDictionary meta)
throws DbException, FormatException {
messageTracker.trackIncomingMessage(txn, m); messageTracker.trackIncomingMessage(txn, m);
@@ -86,8 +88,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
new ForumPostReceivedEvent(m.getGroupId(), header, text); new ForumPostReceivedEvent(m.getGroupId(), header, text);
txn.attach(event); txn.attach(event);
// share message return ACCEPT_SHARE;
return true;
} }
@Override @Override

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
@@ -449,31 +450,26 @@ class IntroduceeProtocolEngine
s.getRemote().acceptTimestamp); s.getRemote().acceptTimestamp);
if (timestamp == -1) throw new AssertionError(); if (timestamp == -1) throw new AssertionError();
Map<TransportId, KeySetId> keys = null; ContactId contactId;
try { try {
contactManager.addContact(txn, s.getRemote().author, contactId = contactManager.addContact(txn, s.getRemote().author,
localAuthor.getId(), false); localAuthor.getId(), false);
// Only add transport properties and keys when the contact was added
// This will be changed once we have a way to reset state for peers
// that were contacts already at some point in the past.
Contact c = contactManager.getContact(txn,
s.getRemote().author.getId(), localAuthor.getId());
// add the keys to the new contact
keys = keyManager.addRotationKeys(txn, c.getId(),
new SecretKey(s.getMasterKey()), timestamp,
s.getLocal().alice, false);
// add signed transport properties for the contact // add signed transport properties for the contact
transportPropertyManager.addRemoteProperties(txn, c.getId(), transportPropertyManager.addRemoteProperties(txn, contactId,
s.getRemote().transportProperties); s.getRemote().transportProperties);
} catch (ContactExistsException e) { } catch (ContactExistsException e) {
// Ignore this, because the other introducee might have deleted us. // The other introducee might have deleted us and been
// So we still want updated transport properties // reintroduced. In that case we can ignore the transport
// and new transport keys. // properties, but we still want the new transport keys as the
// contact will no longer have the old keys
contactId = contactManager.getContact(txn,
s.getRemote().author.getId(), localAuthor.getId()).getId();
} }
Map<TransportId, KeySetId> keys = keyManager.addRotationKeys(txn,
contactId, new SecretKey(s.getMasterKey()), timestamp,
s.getLocal().alice, false);
// send ACTIVATE message with a MAC // send ACTIVATE message with a MAC
byte[] mac = crypto.activateMac(s); byte[] mac = crypto.activateMac(s);
long localTimestamp = getTimestampForInvisibleMessage(s); long localTimestamp = getTimestampForInvisibleMessage(s);

View File

@@ -56,6 +56,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@@ -171,8 +172,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfDictionary bdfMeta) throws DbException, FormatException { BdfList body, BdfDictionary bdfMeta)
throws DbException, FormatException {
// Parse the metadata // Parse the metadata
MessageMetadata meta = messageParser.parseMetadata(bdfMeta); MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
// set the clean-up timer that will be started when message gets read // set the clean-up timer that will be started when message gets read
@@ -213,7 +215,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
// Store the updated session // Store the updated session
storeSession(txn, storageId, session); storeSession(txn, storageId, session);
return false; return ACCEPT_DO_NOT_SHARE;
} }
private IntroduceeSession createNewIntroduceeSession(Transaction txn, private IntroduceeSession createNewIntroduceeSession(Transaction txn,

View File

@@ -63,6 +63,7 @@ import static java.util.Collections.emptyList;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -172,8 +173,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
} }
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
BdfDictionary metaDict = metadataParser.parse(meta); BdfDictionary metaDict = metadataParser.parse(meta);
// Message type is null for version 0.0 private messages // Message type is null for version 0.0 private messages
@@ -193,8 +194,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
// Don't share message return ACCEPT_DO_NOT_SHARE;
return false;
} }
private void incomingPrivateMessage(Transaction txn, Message m, private void incomingPrivateMessage(Transaction txn, Message m,

View File

@@ -55,6 +55,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED; import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.briar.api.privategroup.MessageType.JOIN; import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
@@ -518,18 +519,19 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
} }
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfDictionary meta) throws DbException, FormatException { BdfList body, BdfDictionary meta)
throws DbException, FormatException {
MessageType type = MessageType type =
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue()); MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
switch (type) { switch (type) {
case JOIN: case JOIN:
handleJoinMessage(txn, m, meta); handleJoinMessage(txn, m, meta);
return true; return ACCEPT_SHARE;
case POST: case POST:
handleGroupMessage(txn, m, meta); handleGroupMessage(txn, m, meta);
return true; return ACCEPT_SHARE;
default: default:
// the validator should only let valid types pass // the validator should only let valid types pass
throw new RuntimeException("Unknown MessageType"); throw new RuntimeException("Unknown MessageType");

View File

@@ -54,6 +54,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.privategroup.invitation.CreatorState.START; import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
@@ -147,8 +148,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfDictionary bdfMeta) throws DbException, FormatException { BdfList body, BdfDictionary bdfMeta)
throws DbException, FormatException {
// Parse the metadata // Parse the metadata
MessageMetadata meta = messageParser.parseMetadata(bdfMeta); MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
// set the clean-up timer that will be started when message gets read // set the clean-up timer that will be started when message gets read
@@ -171,7 +173,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
// Store the updated session // Store the updated session
storeSession(txn, storageId, session); storeSession(txn, storageId, session);
return false; return ACCEPT_DO_NOT_SHARE;
} }
private SessionId getSessionId(GroupId privateGroupId) { private SessionId getSessionId(GroupId privateGroupId) {

View File

@@ -49,6 +49,7 @@ import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.sharing.MessageType.ABORT; import static org.briarproject.briar.sharing.MessageType.ABORT;
import static org.briarproject.briar.sharing.MessageType.ACCEPT; import static org.briarproject.briar.sharing.MessageType.ACCEPT;
@@ -133,8 +134,8 @@ abstract class SharingManagerImpl<S extends Shareable>
} }
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfDictionary d) throws DbException, FormatException { BdfList body, BdfDictionary d) throws DbException, FormatException {
// Parse the metadata // Parse the metadata
MessageMetadata meta = messageParser.parseMetadata(d); MessageMetadata meta = messageParser.parseMetadata(d);
// set the clean-up timer that will be started when message gets read // set the clean-up timer that will be started when message gets read
@@ -157,7 +158,7 @@ abstract class SharingManagerImpl<S extends Shareable>
} }
// Store the updated session // Store the updated session
storeSession(txn, storageId, session); storeSession(txn, storageId, session);
return false; return ACCEPT_DO_NOT_SHARE;
} }
/** /**

View File

@@ -44,6 +44,7 @@ import javax.annotation.Nullable;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
@@ -59,7 +60,6 @@ import static org.briarproject.briar.api.avatar.AvatarManager.MAJOR_VERSION;
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION; import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class AvatarManagerImplTest extends BrambleMockTestCase { public class AvatarManagerImplTest extends BrambleMockTestCase {
@@ -196,7 +196,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
null); null);
expectGetContactId(txn, contactGroupId, contact.getId()); expectGetContactId(txn, contactGroupId, contact.getId());
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta)); assertEquals(ACCEPT_DO_NOT_SHARE,
avatarManager.incomingMessage(txn, contactMsg, meta));
assertEquals(1, txn.getActions().size()); assertEquals(1, txn.getActions().size());
Event event = ((EventAction) txn.getActions().get(0)).getEvent(); Event event = ((EventAction) txn.getActions().get(0)).getEvent();
AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event; AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event;
@@ -230,7 +231,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
expectFindLatest(txn, contactGroupId, latestMsgId, latest); expectFindLatest(txn, contactGroupId, latestMsgId, latest);
expectGetContactId(txn, contactGroupId, contact.getId()); expectGetContactId(txn, contactGroupId, contact.getId());
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta)); assertEquals(ACCEPT_DO_NOT_SHARE,
avatarManager.incomingMessage(txn, contactMsg, meta));
// event to broadcast // event to broadcast
assertEquals(1, txn.getActions().size()); assertEquals(1, txn.getActions().size());
@@ -260,7 +262,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
}}); }});
expectFindLatest(txn, contactGroupId, latestMsgId, latest); expectFindLatest(txn, contactGroupId, latestMsgId, latest);
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta)); assertEquals(ACCEPT_DO_NOT_SHARE,
avatarManager.incomingMessage(txn, contactMsg, meta));
// no event to broadcast // no event to broadcast
assertEquals(0, txn.getActions().size()); assertEquals(0, txn.getActions().size());
@@ -271,7 +274,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
throws DbException, InvalidMessageException { throws DbException, InvalidMessageException {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectGetOurGroup(txn); expectGetOurGroup(txn);
avatarManager.incomingMessage(txn, ourMsg, meta); assertEquals(ACCEPT_DO_NOT_SHARE,
avatarManager.incomingMessage(txn, ourMsg, meta));
} }
@Test @Test

View File

@@ -33,6 +33,7 @@ import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
@@ -184,7 +185,8 @@ public class BlogManagerImplTest extends BriarTestCase {
will(returnValue(verifiedInfo)); will(returnValue(verifiedInfo));
}}); }});
blogManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_SHARE,
blogManager.incomingMessage(txn, message, body, meta));
context.assertIsSatisfied(); context.assertIsSatisfied();
assertEquals(1, txn.getActions().size()); assertEquals(1, txn.getActions().size());
@@ -225,7 +227,8 @@ public class BlogManagerImplTest extends BriarTestCase {
will(returnValue(rssLocalAuthor)); will(returnValue(rssLocalAuthor));
}}); }});
blogManager.incomingMessage(txn, rssMessage, body, meta); assertEquals(ACCEPT_SHARE,
blogManager.incomingMessage(txn, rssMessage, body, meta));
context.assertIsSatisfied(); context.assertIsSatisfied();
assertEquals(1, txn.getActions().size()); assertEquals(1, txn.getActions().size());

View File

@@ -1667,6 +1667,126 @@ public class IntroductionIntegrationTest
assertTrue(deleteMessages0From1(emptySet()).allDeleted()); assertTrue(deleteMessages0From1(emptySet()).allDeleted());
} }
@Test
public void testReintroductionAfterBothIntroduceesDeleteEachOther()
throws Exception {
addListeners(true, true);
// Introduce 1 to 2
makeIntroduction(true);
updateContactIdsFor1And2();
// Sync client versions and transport properties - this shows that
// 1 and 2 share a set of transport keys
sync1To2(1, true);
sync2To1(1, true);
sync1To2(2, true);
sync2To1(1, true);
ack1To2(1);
// Both introducees delete each other
contactManager1.removeContact(contactId2From1);
contactManager2.removeContact(contactId1From2);
// Introduce 1 to 2 again
makeIntroduction(true);
updateContactIdsFor1And2();
// Sync client versions and transport properties again
sync1To2(1, true);
sync2To1(1, true);
sync1To2(2, true);
sync2To1(1, true);
ack1To2(1);
}
@Test
public void testReintroductionAfterOneIntroduceeDeletesTheOther()
throws Exception {
addListeners(true, true);
// Introduce 1 to 2
makeIntroduction(true);
updateContactIdsFor1And2();
// Sync client versions and transport properties - this shows that
// 1 and 2 share a set of transport keys
sync1To2(1, true);
sync2To1(1, true);
sync1To2(2, true);
sync2To1(1, true);
ack1To2(1);
// 1 deletes 2, but not vice versa
contactManager1.removeContact(contactId2From1);
// Introduce 1 to 2 again
makeIntroduction(false);
updateContactIdsFor1And2();
// Sync client versioning update from 1 to 2 only - this shows that
// although 1 and 2 share transport keys, the connection won't be
// fully functional until we have a way for 1 to reset the state it's
// holding about 2
sync1To2(1, true);
ack2To1(1);
}
private void makeIntroduction(boolean expectAddContact2From1)
throws Exception {
// Make introduction
Contact introducee1 = contact1From0;
Contact introducee2 = contact2From0;
introductionManager0.makeIntroduction(introducee1, introducee2, "Hi!");
// Request to 1
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// Request to 2
sync0To2(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.requestReceived);
// Response from 1, forwarded
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received);
sync0To2(1, true);
// Response and auth from 2, forwarded
sync2To0(2, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
sync0To1(2, true);
if (expectAddContact2From1) {
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.succeeded);
}
assertTrue(contactManager1
.contactExists(author2.getId(), author1.getId()));
// Auth and activate from 1, forwarded
sync1To0(2, true);
sync0To2(2, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.succeeded);
assertTrue(contactManager2
.contactExists(author1.getId(), author2.getId()));
// Activate from 2, forwarded
sync2To0(1, true);
sync0To1(1, true);
}
private void updateContactIdsFor1And2() throws Exception {
contactId2From1 = contactManager1.getContact(author2.getId(),
author1.getId()).getId();
contactId1From2 = contactManager2.getContact(author1.getId(),
author2.getId()).getId();
}
private DeletionResult deleteAllMessages1From0() throws DbException { private DeletionResult deleteAllMessages1From0() throws DbException {
return db0.transactionWithResult(false, txn -> introductionManager0 return db0.transactionWithResult(false, txn -> introductionManager0
.deleteAllMessages(txn, contactId1From0)); .deleteAllMessages(txn, contactId1From0));

View File

@@ -11,7 +11,9 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestTransportConnectionReader; import org.briarproject.bramble.test.TestTransportConnectionReader;
import org.briarproject.bramble.test.TestTransportConnectionWriter; import org.briarproject.bramble.test.TestTransportConnectionWriter;
@@ -71,7 +73,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
} }
@Test @Test
public void testWriteAndRead() throws Exception { public void testWriteAndReadWithLazyRetransmission() throws Exception {
testWriteAndRead(false);
}
@Test
public void testWriteAndReadWithEagerRetransmission() throws Exception {
testWriteAndRead(true);
}
private void testWriteAndRead(boolean eager) throws Exception {
// Create the identities // Create the identities
Identity aliceIdentity = Identity aliceIdentity =
alice.getIdentityManager().createIdentity("Alice"); alice.getIdentityManager().createIdentity("Alice");
@@ -86,16 +97,21 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
bob.getEventBus().addListener(listener); bob.getEventBus().addListener(listener);
// Alice sends a private message to Bob // Alice sends a private message to Bob
sendMessage(alice, bobId); sendMessage(alice, bobId);
// Sync Alice's client versions and transport properties // Sync Alice's client versions
read(bob, write(alice, bobId), 2); read(bob, write(alice, bobId, eager, 1), 1);
// Sync Bob's client versions and transport properties // Sync Bob's client versions
read(alice, write(bob, aliceId), 2); read(alice, write(bob, aliceId, eager, 1), 1);
// Sync the private message and the attachment // Sync Alice's second client versioning update (with the active flag
read(bob, write(alice, bobId), 2); // raised), the private message and the attachment
read(bob, write(alice, bobId, eager, 3), 3);
// Bob should have received the private message // Bob should have received the private message
assertTrue(listener.messageAdded); assertTrue(listener.messageAdded);
// Bob should have received the attachment // Bob should have received the attachment
assertTrue(listener.attachmentAdded); assertTrue(listener.attachmentAdded);
// Sync messages from Alice to Bob again. If using eager
// retransmission, the three unacked messages should be sent again.
// They're all duplicates, so no further deliveries should occur
read(bob, write(alice, bobId, eager, eager ? 3 : 0), 0);
} }
private ContactId setUp(SimplexMessagingIntegrationTestComponent device, private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
@@ -149,15 +165,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
} }
private byte[] write(SimplexMessagingIntegrationTestComponent device, private byte[] write(SimplexMessagingIntegrationTestComponent device,
ContactId contactId) throws Exception { ContactId contactId, boolean eager, int transmissions)
throws Exception {
// Listen for message transmissions
MessageTransmissionListener listener =
new MessageTransmissionListener(transmissions);
device.getEventBus().addListener(listener);
// Write the outgoing stream // Write the outgoing stream
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
TestTransportConnectionWriter writer = TestTransportConnectionWriter writer =
new TestTransportConnectionWriter(out); new TestTransportConnectionWriter(out, eager);
device.getConnectionManager().manageOutgoingConnection(contactId, device.getConnectionManager().manageOutgoingConnection(contactId,
SIMPLEX_TRANSPORT_ID, writer); SIMPLEX_TRANSPORT_ID, writer);
// Wait for the writer to be disposed // Wait for the writer to be disposed
writer.getDisposedLatch().await(TIMEOUT_MS, MILLISECONDS); writer.getDisposedLatch().await(TIMEOUT_MS, MILLISECONDS);
// Check that the expected number of messages were sent
assertTrue(listener.sent.await(TIMEOUT_MS, MILLISECONDS));
// Clean up the listener
device.getEventBus().removeListener(listener);
// Return the contents of the stream // Return the contents of the stream
return out.toByteArray(); return out.toByteArray();
} }
@@ -178,6 +203,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
deleteTestDirectory(testDir); deleteTestDirectory(testDir);
} }
@NotNullByDefault
private static class MessageTransmissionListener implements EventListener {
private final CountDownLatch sent;
private MessageTransmissionListener(int transmissions) {
sent = new CountDownLatch(transmissions);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessagesSentEvent) {
MessagesSentEvent m = (MessagesSentEvent) e;
for (MessageId ignored : m.getMessageIds()) sent.countDown();
}
}
}
@NotNullByDefault @NotNullByDefault
private static class MessageDeliveryListener implements EventListener { private static class MessageDeliveryListener implements EventListener {
@@ -191,7 +234,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent m = (MessageStateChangedEvent) e; MessageStateChangedEvent m = (MessageStateChangedEvent) e;
if (m.getState().equals(DELIVERED)) delivered.countDown(); if (!m.isLocal() && m.getState().equals(DELIVERED)) {
delivered.countDown();
}
} }
} }
} }

View File

@@ -47,6 +47,7 @@ import javax.annotation.Nullable;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static junit.framework.TestCase.fail; import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
@@ -275,43 +276,50 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testIncomingFirstInviteMessage() throws Exception { public void testIncomingFirstInviteMessage() throws Exception {
expectFirstIncomingMessage(Role.INVITEE, INVITE); expectFirstIncomingMessage(Role.INVITEE, INVITE);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
@Test @Test
public void testIncomingFirstJoinMessage() throws Exception { public void testIncomingFirstJoinMessage() throws Exception {
expectFirstIncomingMessage(Role.PEER, JOIN); expectFirstIncomingMessage(Role.PEER, JOIN);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
@Test @Test
public void testIncomingInviteMessage() throws Exception { public void testIncomingInviteMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, INVITE); expectIncomingMessage(Role.INVITEE, INVITE);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
@Test @Test
public void testIncomingJoinMessage() throws Exception { public void testIncomingJoinMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, JOIN); expectIncomingMessage(Role.INVITEE, JOIN);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
@Test @Test
public void testIncomingJoinMessageForCreator() throws Exception { public void testIncomingJoinMessageForCreator() throws Exception {
expectIncomingMessage(Role.CREATOR, JOIN); expectIncomingMessage(Role.CREATOR, JOIN);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
@Test @Test
public void testIncomingLeaveMessage() throws Exception { public void testIncomingLeaveMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, LEAVE); expectIncomingMessage(Role.INVITEE, LEAVE);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
@Test @Test
public void testIncomingAbortMessage() throws Exception { public void testIncomingAbortMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, ABORT); expectIncomingMessage(Role.INVITEE, ABORT);
groupInvitationManager.incomingMessage(txn, message, body, meta); assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
.incomingMessage(txn, message, body, meta));
} }
private void expectFirstIncomingMessage(Role role, MessageType type) private void expectFirstIncomingMessage(Role role, MessageType type)

View File

@@ -429,7 +429,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
// Write the messages to a transport stream // Write the messages to a transport stream
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
TestTransportConnectionWriter writer = TestTransportConnectionWriter writer =
new TestTransportConnectionWriter(out); new TestTransportConnectionWriter(out, false);
fromComponent.getConnectionManager().manageOutgoingConnection(toId, fromComponent.getConnectionManager().manageOutgoingConnection(toId,
SIMPLEX_TRANSPORT_ID, writer); SIMPLEX_TRANSPORT_ID, writer);
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS); writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
@@ -487,7 +487,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
// start outgoing connection // start outgoing connection
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
TestTransportConnectionWriter writer = TestTransportConnectionWriter writer =
new TestTransportConnectionWriter(out); new TestTransportConnectionWriter(out, false);
fromComponent.getConnectionManager().manageOutgoingConnection(toId, fromComponent.getConnectionManager().manageOutgoingConnection(toId,
SIMPLEX_TRANSPORT_ID, writer); SIMPLEX_TRANSPORT_ID, writer);
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS); writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);

View File

@@ -74,6 +74,7 @@ internal class HeadlessModule(private val appDir: File) {
} }
@Provides @Provides
@Singleton
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig { internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig {
val duplex: List<DuplexPluginFactory> = val duplex: List<DuplexPluginFactory> =
if (isLinux() || isMac()) listOf(tor) else emptyList() if (isLinux() || isMac()) listOf(tor) else emptyList()

View File

@@ -64,6 +64,7 @@ internal class HeadlessTestModule(private val appDir: File) {
} }
@Provides @Provides
@Singleton
internal fun providePluginConfig(): PluginConfig { internal fun providePluginConfig(): PluginConfig {
return object : PluginConfig { return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = emptyList() override fun getDuplexFactories(): Collection<DuplexPluginFactory> = emptyList()

View File

@@ -136,7 +136,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
val messageId1 = MessageId(getRandomId()) val messageId1 = MessageId(getRandomId())
val messageId2 = MessageId(getRandomId()) val messageId2 = MessageId(getRandomId())
val messageIds = listOf(messageId1, messageId2) val messageIds = listOf(messageId1, messageId2)
val event = MessagesSentEvent(contact.id, messageIds) val event = MessagesSentEvent(contact.id, messageIds, 1234)
every { every {
webSocketController.sendEvent( webSocketController.sendEvent(
@@ -274,7 +274,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
val messageId1 = MessageId(getRandomId()) val messageId1 = MessageId(getRandomId())
val messageId2 = MessageId(getRandomId()) val messageId2 = MessageId(getRandomId())
val messageIds = listOf(messageId1, messageId2) val messageIds = listOf(messageId1, messageId2)
val event = MessagesSentEvent(contact.id, messageIds) val event = MessagesSentEvent(contact.id, messageIds, 1234)
val json = """ val json = """
{ {