mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Compare commits
59 Commits
sqlite-jdb
...
2085-trans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67593e2ad0 | ||
|
|
d4c1e132f7 | ||
|
|
6b976df6a8 | ||
|
|
3e4db3b9da | ||
|
|
0bf59eec20 | ||
|
|
9f828a2222 | ||
|
|
7be77b8c60 | ||
|
|
d5853e8403 | ||
|
|
32e9bf01ec | ||
|
|
a5ce400341 | ||
|
|
a960bfb2c1 | ||
|
|
847650f280 | ||
|
|
77a3199aac | ||
|
|
9a58b37ce2 | ||
|
|
608e1eac6b | ||
|
|
09de768e7e | ||
|
|
faab80f0ea | ||
|
|
07162cad8b | ||
|
|
ac0fc21e6e | ||
|
|
dab736ce0e | ||
|
|
bcbc96dc2d | ||
|
|
a72e92de24 | ||
|
|
1ddcd6cfff | ||
|
|
5dfd9e3546 | ||
|
|
e05575b956 | ||
|
|
fd810f5c16 | ||
|
|
3f5e131250 | ||
|
|
3ee516599d | ||
|
|
c703d90636 | ||
|
|
e228b9fcbf | ||
|
|
6e6cadd3ad | ||
|
|
9cc8d44778 | ||
|
|
ee6f571c31 | ||
|
|
2ac3bdd3ae | ||
|
|
e35ffe0cf0 | ||
|
|
8a04d8edc4 | ||
|
|
a5fb3bb4a4 | ||
|
|
eae329cdfa | ||
|
|
0ce0551f0d | ||
|
|
a198e7d08e | ||
|
|
bca6f1506e | ||
|
|
e420201b00 | ||
|
|
03248d04e5 | ||
|
|
2c39b02644 | ||
|
|
c9c6f3682c | ||
|
|
8f4a0ef030 | ||
|
|
5fe22bcd57 | ||
|
|
b4880af7e2 | ||
|
|
51d21bd669 | ||
|
|
b8f3728a0d | ||
|
|
bbfd4f137d | ||
|
|
7e3ca76dd1 | ||
|
|
524c8d26f8 | ||
|
|
7eccf7dac1 | ||
|
|
0bc06248ed | ||
|
|
c999f05cc7 | ||
|
|
428269b312 | ||
|
|
588e05ce83 | ||
|
|
f7875c99b6 |
@@ -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,
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.api.plugin;
|
package org.briarproject.bramble.api.plugin.file;
|
||||||
|
|
||||||
public interface FileConstants {
|
public interface FileConstants {
|
||||||
|
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 = """
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user