mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +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;
|
||||
|
||||
@Inject
|
||||
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
AndroidExecutor androidExecutor,
|
||||
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;
|
||||
|
||||
@Inject
|
||||
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
EventBus eventBus,
|
||||
BackoffFactory backoffFactory,
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
||||
private final File torDirectory;
|
||||
|
||||
@Inject
|
||||
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
Application app,
|
||||
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.
|
||||
* <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
|
||||
* @return Whether or not this message should be shared
|
||||
* @throws DbException Should only be used for real database errors.
|
||||
* If this is thrown, delivery will be attempted again at next startup,
|
||||
* whereas if a FormatException is thrown, the message will be permanently
|
||||
* invalidated.
|
||||
* @throws FormatException Use this for any non-database error
|
||||
* that occurs while handling remotely created data.
|
||||
* This includes errors that occur while handling locally created data
|
||||
* 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!
|
||||
* @throws DbException if a database error occurs while delivering the
|
||||
* message. Delivery will be attempted again at next startup. Throwing
|
||||
* this exception has the same effect as returning
|
||||
* {@link DeliveryAction#DEFER}.
|
||||
* @throws FormatException if the message is invalid in the context of its
|
||||
* dependencies. The message and any dependents will be marked as invalid
|
||||
* and deleted along with their metadata. Throwing this exception has the
|
||||
* same effect as returning {@link DeliveryAction#REJECT}.
|
||||
*/
|
||||
protected abstract boolean incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta) throws DbException,
|
||||
FormatException;
|
||||
protected abstract DeliveryAction incomingMessage(Transaction txn,
|
||||
Message m, BdfList body, BdfDictionary meta)
|
||||
throws DbException, FormatException;
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
BdfList body = clientHelper.toList(m);
|
||||
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
||||
|
||||
@@ -118,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
||||
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
|
||||
* local pseudonym.
|
||||
@@ -150,6 +162,16 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
||||
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
|
||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
||||
@@ -180,6 +202,18 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||
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
|
||||
* 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)
|
||||
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
|
||||
* 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)
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,11 @@ public interface TransportConnectionWriter {
|
||||
*/
|
||||
int getMaxIdleTime();
|
||||
|
||||
/**
|
||||
* Returns true if the transport is lossy and cheap.
|
||||
*/
|
||||
boolean isLossyAndCheap();
|
||||
|
||||
/**
|
||||
* Returns an output stream for writing to the transport connection.
|
||||
*/
|
||||
|
||||
@@ -79,6 +79,11 @@ public abstract class AbstractDuplexTransportConnection
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLossyAndCheap() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return AbstractDuplexTransportConnection.this.getOutputStream();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
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
|
||||
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
|
||||
* properties. Returns null if a reader cannot be created.
|
||||
|
||||
@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
|
||||
PriorityHandler handler);
|
||||
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
int maxLatency, StreamWriter streamWriter);
|
||||
int maxLatency, boolean eager, StreamWriter streamWriter);
|
||||
|
||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||
|
||||
@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final Collection<MessageId> messageIds;
|
||||
private final long totalLength;
|
||||
|
||||
public MessagesSentEvent(ContactId contactId,
|
||||
Collection<MessageId> messageIds) {
|
||||
Collection<MessageId> messageIds, long totalLength) {
|
||||
this.contactId = contactId;
|
||||
this.messageIds = messageIds;
|
||||
this.totalLength = totalLength;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
|
||||
public Collection<MessageId> getMessageIds() {
|
||||
return messageIds;
|
||||
}
|
||||
|
||||
public long getTotalLength() {
|
||||
return totalLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,54 @@ public interface IncomingMessageHook {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return Whether or not this message should be shared
|
||||
* @throws DbException Should only be used for real database errors.
|
||||
* If this is thrown, delivery will be attempted again at next startup,
|
||||
* whereas if an InvalidMessageException is thrown,
|
||||
* the message will be permanently invalidated.
|
||||
* @throws InvalidMessageException for any non-database error
|
||||
* that occurs while handling remotely created data.
|
||||
* This includes errors that occur while handling locally created data
|
||||
* 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!
|
||||
* @throws DbException if a database error occurs while delivering the
|
||||
* message. Delivery will be attempted again at next startup. Throwing
|
||||
* this exception has the same effect as returning
|
||||
* {@link DeliveryAction#DEFER}.
|
||||
* @throws InvalidMessageException if the message is invalid in the context
|
||||
* of its dependencies. The message and any dependents will be marked as
|
||||
* invalid and deleted along with their metadata. Throwing this exception
|
||||
* has the same effect as returning {@link DeliveryAction#REJECT}.
|
||||
*/
|
||||
boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
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;
|
||||
|
||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -22,8 +22,24 @@ public interface KeyManager {
|
||||
|
||||
/**
|
||||
* Derives and stores a set of rotation mode transport keys for
|
||||
* communicating with the given contact over each transport and returns the
|
||||
* key set IDs.
|
||||
* communicating with the given contact over the given transport and
|
||||
* 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/>
|
||||
* {@link StreamContext StreamContexts} for the contact can be created
|
||||
* 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.sync.validation.ValidationModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
public interface BrambleCoreEagerSingletons {
|
||||
@@ -33,6 +34,8 @@ public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(RendezvousModule.EagerSingletons init);
|
||||
|
||||
void inject(TransportKeyAgreementModule.EagerSingletons init);
|
||||
|
||||
void inject(TransportModule.EagerSingletons init);
|
||||
|
||||
void inject(ValidationModule.EagerSingletons init);
|
||||
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
|
||||
c.inject(new RendezvousModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
c.inject(new TransportKeyAgreementModule.EagerSingletons());
|
||||
c.inject(new TransportModule.EagerSingletons());
|
||||
c.inject(new ValidationModule.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.validation.ValidationModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -49,6 +50,7 @@ import dagger.Module;
|
||||
RendezvousModule.class,
|
||||
SettingsModule.class,
|
||||
SyncModule.class,
|
||||
TransportKeyAgreementModule.class,
|
||||
TransportModule.class,
|
||||
ValidationModule.class,
|
||||
VersioningModule.class
|
||||
|
||||
@@ -71,8 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||
w.getOutputStream(), ctx);
|
||||
ContactId c = requireNonNull(ctx.getContactId());
|
||||
// Use eager retransmission if the transport is lossy and cheap
|
||||
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)
|
||||
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
|
||||
* local pseudonym.
|
||||
@@ -215,6 +227,16 @@ interface Database<T> {
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* given contact, up to the given total length.
|
||||
* <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.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
||||
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.
|
||||
* <p/>
|
||||
@@ -580,6 +628,16 @@ interface Database<T> {
|
||||
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -341,6 +341,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public boolean containsContact(Transaction transaction, AuthorId remote,
|
||||
AuthorId local) throws DbException {
|
||||
@@ -371,6 +380,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public void deleteMessage(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
@@ -415,14 +431,43 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
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);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -467,14 +512,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
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);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
transaction.attach(new MessagesSentEvent(c, ids));
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -692,6 +740,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public Map<MessageId, MessageState> getMessageDependencies(
|
||||
Transaction transaction, MessageId m) throws DbException {
|
||||
@@ -765,6 +832,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
|
||||
@@ -51,6 +51,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -344,6 +345,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||
+ " 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 =
|
||||
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
||||
+ " ON messages (cleanupDeadline)";
|
||||
@@ -570,6 +576,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_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_TX_COUNT_TIMESTAMP);
|
||||
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
||||
s.close();
|
||||
} 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
|
||||
public boolean containsContact(Connection txn, AuthorId remote,
|
||||
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
|
||||
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
||||
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
|
||||
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
||||
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
|
||||
public void incrementStreamCounter(Connection txn, TransportId t,
|
||||
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.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.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ class FileTransportWriter implements TransportConnectionWriter {
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLossyAndCheap() {
|
||||
return plugin.isLossyAndCheap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
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_VERSION;
|
||||
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;
|
||||
|
||||
@Immutable
|
||||
@@ -115,8 +116,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
// Find the latest update for this transport, if any
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
@@ -131,14 +132,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
// We've already received a newer update - delete this one
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
}
|
||||
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@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.sync.Ack;
|
||||
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.SyncSession;
|
||||
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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
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.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -61,6 +66,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final int maxLatency;
|
||||
private final boolean eager;
|
||||
private final StreamWriter streamWriter;
|
||||
private final SyncRecordWriter recordWriter;
|
||||
private final AtomicInteger outstandingQueries;
|
||||
@@ -70,7 +76,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, ContactId contactId, TransportId transportId,
|
||||
int maxLatency, StreamWriter streamWriter,
|
||||
int maxLatency, boolean eager, StreamWriter streamWriter,
|
||||
SyncRecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
@@ -78,6 +84,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
this.contactId = contactId;
|
||||
this.transportId = transportId;
|
||||
this.maxLatency = maxLatency;
|
||||
this.eager = eager;
|
||||
this.streamWriter = streamWriter;
|
||||
this.recordWriter = recordWriter;
|
||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
||||
@@ -92,8 +99,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
// Send our supported protocol versions
|
||||
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||
// Start a query for each type of record
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(this::generateAck);
|
||||
if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
|
||||
else dbExecutor.execute(this::generateBatch);
|
||||
// Write records until interrupted or no more records to write
|
||||
try {
|
||||
while (!interrupted) {
|
||||
@@ -138,81 +146,110 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private class GenerateAck implements Runnable {
|
||||
|
||||
@DatabaseExecutor
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
Ack a = db.transactionWithNullableResult(false, txn ->
|
||||
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();
|
||||
@DatabaseExecutor
|
||||
private void loadUnackedMessageIds() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
|
||||
db.getUnackedMessagesToSend(txn, contactId));
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(ids.size() + " unacked messages to send");
|
||||
}
|
||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private class WriteAck implements ThrowingRunnable<IOException> {
|
||||
|
||||
private final Ack ack;
|
||||
|
||||
private WriteAck(Ack ack) {
|
||||
this.ack = ack;
|
||||
@DatabaseExecutor
|
||||
private void generateEagerBatch(Map<MessageId, Integer> ids) {
|
||||
if (interrupted) return;
|
||||
// Take some message IDs from `ids` to form a batch
|
||||
Collection<MessageId> batchIds = new ArrayList<>();
|
||||
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;
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeAck(ack);
|
||||
LOG.info("Sent ack");
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
if (batchIds.isEmpty()) throw new AssertionError();
|
||||
try {
|
||||
Collection<Message> batch =
|
||||
db.transactionWithResult(false, txn ->
|
||||
db.generateBatch(txn, contactId, batchIds,
|
||||
maxLatency));
|
||||
writerTasks.add(() -> writeEagerBatch(batch, ids));
|
||||
} 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
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
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(new WriteBatch(b));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
interrupt();
|
||||
}
|
||||
@DatabaseExecutor
|
||||
private void generateAck() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
Ack a = db.transactionWithNullableResult(false, txn ->
|
||||
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Generated ack: " + (a != null));
|
||||
if (a == null) decrementOutstandingQueries();
|
||||
else writerTasks.add(() -> writeAck(a));
|
||||
} 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;
|
||||
|
||||
private WriteBatch(Collection<Message> batch) {
|
||||
this.batch = batch;
|
||||
@DatabaseExecutor
|
||||
private void generateBatch() {
|
||||
if (interrupted) return;
|
||||
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
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
for (Message m : batch) recordWriter.writeMessage(m);
|
||||
LOG.info("Sent batch");
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
@IoExecutor
|
||||
private void writeBatch(Collection<Message> batch) throws IOException {
|
||||
if (interrupted) return;
|
||||
for (Message m : batch) recordWriter.writeMessage(m);
|
||||
LOG.info("Sent batch");
|
||||
dbExecutor.execute(this::generateBatch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
|
||||
@Override
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
int maxLatency, StreamWriter streamWriter) {
|
||||
int maxLatency, boolean eager, StreamWriter streamWriter) {
|
||||
OutputStream out = streamWriter.getOutputStream();
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
||||
maxLatency, streamWriter, recordWriter);
|
||||
maxLatency, eager, streamWriter, recordWriter);
|
||||
}
|
||||
|
||||
@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.event.MessageAddedEvent;
|
||||
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.MessageValidator;
|
||||
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.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.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
@@ -185,16 +190,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
int majorVersion = g.getMajorVersion();
|
||||
Metadata meta =
|
||||
db.getMessageMetadataForValidator(txn, id);
|
||||
DeliveryResult result =
|
||||
DeliveryAction action =
|
||||
deliverMessage(txn, m, c, majorVersion, meta);
|
||||
if (result.valid) {
|
||||
addPendingDependents(txn, id, pending);
|
||||
if (result.share) {
|
||||
db.setMessageShared(txn, id);
|
||||
toShare.addAll(states.keySet());
|
||||
}
|
||||
} else {
|
||||
if (action == REJECT) {
|
||||
invalidateMessage(txn, id);
|
||||
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();
|
||||
db.mergeMessageMetadata(txn, id, meta);
|
||||
if (allDelivered) {
|
||||
DeliveryResult result =
|
||||
DeliveryAction action =
|
||||
deliverMessage(txn, m, c, majorVersion, meta);
|
||||
if (result.valid) {
|
||||
addPendingDependents(txn, id, pending);
|
||||
if (result.share) {
|
||||
db.setMessageShared(txn, id);
|
||||
toShare.addAll(dependencies);
|
||||
}
|
||||
} else {
|
||||
if (action == REJECT) {
|
||||
invalidateMessage(txn, id);
|
||||
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 {
|
||||
db.setMessageState(txn, id, PENDING);
|
||||
@@ -304,23 +317,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
||||
ClientId c, int majorVersion, Metadata meta) throws DbException {
|
||||
// Deliver the message to the client if it's registered a hook
|
||||
boolean shareMsg = false;
|
||||
private DeliveryAction deliverMessage(Transaction txn, Message m,
|
||||
ClientId c, int majorVersion, Metadata meta) {
|
||||
// Deliver the message to the client if it has registered a hook
|
||||
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
||||
IncomingMessageHook hook = hooks.get(cv);
|
||||
if (hook != null) {
|
||||
try {
|
||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||
} catch (InvalidMessageException e) {
|
||||
logException(LOG, INFO, e);
|
||||
invalidateMessage(txn, m.getId());
|
||||
return new DeliveryResult(false, false);
|
||||
}
|
||||
if (hook == null) return ACCEPT_DO_NOT_SHARE;
|
||||
try {
|
||||
return hook.incomingMessage(txn, m, meta);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, INFO, e);
|
||||
return DEFER;
|
||||
} catch (InvalidMessageException e) {
|
||||
logException(LOG, INFO, e);
|
||||
return REJECT;
|
||||
}
|
||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
||||
return new DeliveryResult(true, shareMsg);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -447,14 +458,4 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
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 Executor dbExecutor;
|
||||
private final PluginConfig pluginConfig;
|
||||
private final TransportKeyManagerFactory transportKeyManagerFactory;
|
||||
private final TransportCrypto transportCrypto;
|
||||
|
||||
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
||||
@@ -61,34 +60,39 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
KeyManagerImpl(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
PluginConfig pluginConfig,
|
||||
TransportKeyManagerFactory transportKeyManagerFactory,
|
||||
TransportCrypto transportCrypto) {
|
||||
TransportCrypto transportCrypto,
|
||||
TransportKeyManagerFactory transportKeyManagerFactory) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.pluginConfig = pluginConfig;
|
||||
this.transportKeyManagerFactory = transportKeyManagerFactory;
|
||||
this.transportCrypto = transportCrypto;
|
||||
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
|
||||
public void startService() throws ServiceException {
|
||||
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 {
|
||||
db.transaction(false, txn -> {
|
||||
for (Entry<TransportId, Integer> e : transports.entrySet())
|
||||
db.addTransport(txn, e.getKey(), e.getValue());
|
||||
for (Entry<TransportId, Integer> e : transports.entrySet()) {
|
||||
TransportKeyManager m = transportKeyManagerFactory
|
||||
.createTransportKeyManager(e.getKey(),
|
||||
e.getValue());
|
||||
managers.put(e.getKey(), m);
|
||||
m.start(txn);
|
||||
for (SimplexPluginFactory f :
|
||||
pluginConfig.getSimplexFactories()) {
|
||||
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
||||
managers.get(f.getId()).start(txn);
|
||||
}
|
||||
for (DuplexPluginFactory f :
|
||||
pluginConfig.getDuplexFactories()) {
|
||||
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
||||
managers.get(f.getId()).start(txn);
|
||||
}
|
||||
});
|
||||
} catch (DbException e) {
|
||||
@@ -101,9 +105,17 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, KeySetId> addRotationKeys(
|
||||
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
|
||||
boolean alice, boolean active) throws DbException {
|
||||
public KeySetId addRotationKeys(Transaction txn, ContactId c,
|
||||
TransportId t, SecretKey rootKey, long timestamp, boolean alice,
|
||||
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<>();
|
||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
@@ -137,7 +149,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
|
||||
throws DbException, GeneralSecurityException {
|
||||
SecretKey staticMasterKey = transportCrypto
|
||||
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
|
||||
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
|
||||
SecretKey rootKey =
|
||||
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
|
||||
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.SHARED;
|
||||
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_UPDATE_VERSION;
|
||||
|
||||
@@ -173,8 +174,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
// Parse the new remote update
|
||||
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
||||
@@ -187,7 +188,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
// Load and parse the latest local update
|
||||
if (latest.local == null) throw new DbException();
|
||||
@@ -241,7 +242,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private void storeClientVersions(Transaction txn,
|
||||
|
||||
@@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// 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));
|
||||
exactly(18).of(database).containsContact(txn, contactId);
|
||||
exactly(19).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(18).of(database).abortTransaction(txn);
|
||||
exactly(19).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
@@ -349,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getContact(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -357,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
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));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -365,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, messageId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -373,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getGroupVisibility(transaction, contactId, groupId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -381,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getSyncVersions(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
|
||||
@@ -57,6 +57,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
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);
|
||||
|
||||
// The contact has not seen the message, so it should be sendable
|
||||
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);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
|
||||
// Changing the status to seen = true should make the message unsendable
|
||||
db.raiseSeenFlag(txn, contactId, messageId);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
assertNothingToSendLazily(db, txn);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -253,32 +249,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, UNKNOWN, true, false, null);
|
||||
|
||||
// The message has not been validated, so it should not be sendable
|
||||
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());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Marking the message delivered should make it sendable
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
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);
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
// Marking the message invalid should make it unsendable
|
||||
db.setMessageState(txn, messageId, INVALID);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Marking the message pending should make it unsendable
|
||||
db.setMessageState(txn, messageId, PENDING);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -297,39 +284,28 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||
|
||||
// The group is invisible, so the message should not be sendable
|
||||
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());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Making the group visible should not make the message sendable
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Sharing the group should make the message sendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
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);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
|
||||
// Unsharing the group should make the message unsendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Making the group invisible should make the message unsendable
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -349,18 +325,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, DELIVERED, false, false, null);
|
||||
|
||||
// The message is not shared, so it should not be sendable
|
||||
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());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Sharing the message should make it sendable
|
||||
db.setMessageShared(txn, messageId, true);
|
||||
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);
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -380,10 +351,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
||||
MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// The message is just the right size to send
|
||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
||||
MAX_LATENCY);
|
||||
@@ -405,6 +379,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addGroup(txn, group);
|
||||
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
|
||||
Message message1 = getMessage(groupId);
|
||||
MessageId messageId1 = message1.getId();
|
||||
@@ -412,6 +392,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||
|
||||
// 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);
|
||||
assertEquals(asList(messageId, messageId1), ids);
|
||||
|
||||
@@ -419,6 +403,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||
|
||||
// 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,
|
||||
contactId, 1234));
|
||||
|
||||
@@ -427,6 +415,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
|
||||
// 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);
|
||||
assertEquals(asList(messageId, messageId1), ids);
|
||||
|
||||
@@ -447,22 +439,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||
|
||||
// Retrieve the message from the database and mark it as sent
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
// The message should be sendable via lazy or eager retransmission
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should no longer be sendable
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
// The message should no longer be sendable via lazy retransmission,
|
||||
// but it should still be sendable via eager retransmission
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
// Pretend that the message was acked
|
||||
// Mark the message as acked
|
||||
db.raiseSeenFlag(txn, contactId, messageId);
|
||||
|
||||
// The message still should not be sendable
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
// The message still should not be sendable via lazy or eager
|
||||
// retransmission
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -676,7 +671,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Initially there should be no transport keys in the database
|
||||
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
|
||||
|
||||
// Add the contact, the transport and the transport keys
|
||||
db.addIdentity(txn, identity);
|
||||
@@ -687,6 +684,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
||||
|
||||
// Retrieve the transport keys
|
||||
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
|
||||
Collection<TransportKeySet> allKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
@@ -699,6 +697,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertKeysEquals(keys1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
assertEquals(singletonMap(contactId, singletonList(transportId)),
|
||||
db.getTransportsWithKeys(txn));
|
||||
|
||||
// Update the transport keys
|
||||
TransportKeys updated = createTransportKeys(timePeriod + 1, active);
|
||||
@@ -710,6 +710,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
null, updated1));
|
||||
|
||||
// Retrieve the transport keys again
|
||||
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
|
||||
allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (TransportKeySet ks : allKeys) {
|
||||
@@ -721,10 +722,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertKeysEquals(updated1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
assertEquals(singletonMap(contactId, singletonList(transportId)),
|
||||
db.getTransportsWithKeys(txn));
|
||||
|
||||
// Removing the contact should remove the transport keys
|
||||
db.removeContact(txn, contactId);
|
||||
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -1925,11 +1930,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
// The message should be sendable
|
||||
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);
|
||||
assertOneMessageToSendLazily(db, txn);
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
// The message should be available
|
||||
Message m = db.getMessage(txn, messageId);
|
||||
@@ -1945,10 +1947,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
// The message should not be sendable
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertNothingToSendLazily(db, txn);
|
||||
assertNothingToSendEagerly(db, txn);
|
||||
|
||||
// Requesting the message should throw an exception
|
||||
try {
|
||||
@@ -2577,6 +2577,50 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
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 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.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
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.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.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
@Module
|
||||
public class DesktopPluginModule extends PluginModule {
|
||||
class RemovableDriveIntegrationTestModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth,
|
||||
ModemPluginFactory modem, LanTcpPluginFactory lan,
|
||||
WanTcpPluginFactory wan) {
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return asList(bluetooth, modem, lan, wan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return singletonList(drive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
||||
// Prefer LAN to Bluetooth
|
||||
return singletonMap(BluetoothConstants.ID,
|
||||
singletonList(LanTcpConstants.ID));
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
};
|
||||
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.MAJOR_VERSION;
|
||||
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.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
@@ -230,7 +231,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
TransportPropertyManagerImpl t = createInstance();
|
||||
assertFalse(t.incomingMessage(txn, message, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||
}
|
||||
|
||||
@@ -269,7 +271,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
TransportPropertyManagerImpl t = createInstance();
|
||||
assertFalse(t.incomingMessage(txn, message, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||
}
|
||||
|
||||
@@ -308,7 +311,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
TransportPropertyManagerImpl t = createInstance();
|
||||
assertFalse(t.incomingMessage(txn, message, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,14 @@ import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
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.test.TestUtils.getContactId;
|
||||
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 ContactId contactId = getContactId();
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final Message message = getMessage(new GroupId(getRandomId()));
|
||||
private final MessageId messageId = message.getId();
|
||||
private final Ack ack =
|
||||
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
|
||||
public void testNothingToSend() throws Exception {
|
||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||
streamWriter, recordWriter);
|
||||
false, streamWriter, recordWriter);
|
||||
|
||||
Transaction noAckTxn = new Transaction(null, false);
|
||||
Transaction noMsgTxn = new Transaction(null, false);
|
||||
|
||||
@@ -63,8 +73,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
// No messages to send
|
||||
oneOf(db).transactionWithNullableResult(with(false),
|
||||
withNullableDbCallable(noMsgTxn));
|
||||
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
|
||||
with(any(int.class)), with(MAX_LATENCY));
|
||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
||||
will(returnValue(null));
|
||||
// Send the end of stream marker
|
||||
oneOf(streamWriter).sendEndOfStream();
|
||||
@@ -76,11 +86,44 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSomethingToSend() throws Exception {
|
||||
Ack ack = new Ack(singletonList(messageId));
|
||||
public void testNothingToSendEagerly() throws Exception {
|
||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||
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 noAckTxn = new Transaction(null, false);
|
||||
Transaction msgTxn = new Transaction(null, false);
|
||||
@@ -100,8 +143,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
// One message to send
|
||||
oneOf(db).transactionWithNullableResult(with(false),
|
||||
withNullableDbCallable(msgTxn));
|
||||
oneOf(db).generateBatch(with(msgTxn), with(contactId),
|
||||
with(any(int.class)), with(MAX_LATENCY));
|
||||
oneOf(db).generateBatch(msgTxn, contactId,
|
||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
||||
will(returnValue(singletonList(message)));
|
||||
oneOf(recordWriter).writeMessage(message);
|
||||
// No more acks
|
||||
@@ -112,8 +155,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
// No more messages
|
||||
oneOf(db).transactionWithNullableResult(with(false),
|
||||
withNullableDbCallable(noMsgTxn));
|
||||
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
|
||||
with(any(int.class)), with(MAX_LATENCY));
|
||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
||||
will(returnValue(null));
|
||||
// Send the end of stream marker
|
||||
oneOf(streamWriter).sendEndOfStream();
|
||||
@@ -123,4 +166,63 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
|
||||
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.singletonList;
|
||||
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.INVALID;
|
||||
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);
|
||||
// Deliver the first message
|
||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn1, messageId);
|
||||
@@ -167,7 +169,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(new Metadata()));
|
||||
// Deliver the message
|
||||
oneOf(hook).incomingMessage(txn, message, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn, messageId, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn, messageId);
|
||||
@@ -187,7 +189,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(metadata));
|
||||
// Deliver the dependent
|
||||
oneOf(hook).incomingMessage(txn1, message2, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn1, messageId2);
|
||||
@@ -247,7 +249,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||
// Deliver the message
|
||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||
will(returnValue(true));
|
||||
will(returnValue(ACCEPT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn1, messageId);
|
||||
@@ -367,7 +369,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||
// Deliver the message
|
||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn1, messageId);
|
||||
@@ -432,7 +434,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||
// Deliver the message
|
||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn1, messageId);
|
||||
@@ -602,7 +604,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||
// Deliver the message
|
||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||
// The message has two pending dependents: 1 and 2
|
||||
oneOf(db).getMessageDependents(txn1, messageId);
|
||||
@@ -622,7 +624,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(metadata));
|
||||
// Deliver message 1
|
||||
oneOf(hook).incomingMessage(txn2, message1, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
|
||||
// Message 1 has one pending dependent: 3
|
||||
oneOf(db).getMessageDependents(txn2, messageId1);
|
||||
@@ -642,7 +644,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(metadata));
|
||||
// Deliver message 2
|
||||
oneOf(hook).incomingMessage(txn3, message2, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
|
||||
// Message 2 has one pending dependent: 3 (same dependent as 1)
|
||||
oneOf(db).getMessageDependents(txn3, messageId2);
|
||||
@@ -662,6 +664,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(metadata));
|
||||
// Deliver message 3
|
||||
oneOf(hook).incomingMessage(txn4, message3, metadata);
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
|
||||
// Message 3 has one pending dependent: 4
|
||||
oneOf(db).getMessageDependents(txn4, messageId3);
|
||||
@@ -685,7 +688,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(metadata));
|
||||
// Deliver message 4
|
||||
oneOf(hook).incomingMessage(txn6, message4, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
|
||||
// Message 4 has no pending dependents
|
||||
oneOf(db).getMessageDependents(txn6, messageId4);
|
||||
@@ -717,7 +720,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||
// Deliver the message
|
||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||
will(returnValue(false));
|
||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||
// Get any pending dependents
|
||||
oneOf(db).getMessageDependents(txn1, messageId);
|
||||
|
||||
@@ -25,7 +25,7 @@ public class TestDuplexTransportConnection
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TestDuplexTransportConnection(InputStream in, OutputStream out) {
|
||||
reader = new TestTransportConnectionReader(in);
|
||||
writer = new TestTransportConnectionWriter(out);
|
||||
writer = new TestTransportConnectionWriter(out, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,10 +15,13 @@ public class TestTransportConnectionWriter
|
||||
implements TransportConnectionWriter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final boolean lossyAndCheap;
|
||||
private final CountDownLatch disposed = new CountDownLatch(1);
|
||||
|
||||
public TestTransportConnectionWriter(OutputStream out) {
|
||||
public TestTransportConnectionWriter(OutputStream out,
|
||||
boolean lossyAndCheap) {
|
||||
this.out = out;
|
||||
this.lossyAndCheap = lossyAndCheap;
|
||||
}
|
||||
|
||||
public CountDownLatch getDisposedLatch() {
|
||||
@@ -35,6 +38,11 @@ public class TestTransportConnectionWriter
|
||||
return 60_000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLossyAndCheap() {
|
||||
return lossyAndCheap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
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 Random random = new Random();
|
||||
|
||||
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor,
|
||||
pluginConfig, transportKeyManagerFactory, transportCrypto);
|
||||
private KeyManagerImpl keyManager;
|
||||
|
||||
@Before
|
||||
public void testStartService() throws Exception {
|
||||
@@ -83,18 +83,25 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
singletonList(pluginFactory);
|
||||
int maxLatency = 1337;
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(pluginConfig).getSimplexFactories();
|
||||
context.checking(new Expectations() {{
|
||||
allowing(pluginConfig).getSimplexFactories();
|
||||
will(returnValue(factories));
|
||||
oneOf(pluginFactory).getId();
|
||||
allowing(pluginFactory).getId();
|
||||
will(returnValue(transportId));
|
||||
oneOf(pluginFactory).getMaxLatency();
|
||||
allowing(pluginFactory).getMaxLatency();
|
||||
will(returnValue(maxLatency));
|
||||
oneOf(db).addTransport(txn, transportId, maxLatency);
|
||||
allowing(pluginConfig).getDuplexFactories();
|
||||
will(returnValue(emptyList()));
|
||||
oneOf(transportKeyManagerFactory)
|
||||
.createTransportKeyManager(transportId, maxLatency);
|
||||
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(transportKeyManager).start(txn);
|
||||
}});
|
||||
@@ -235,4 +242,37 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
keyManager.eventOccurred(event);
|
||||
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.SHARED;
|
||||
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.MAJOR_VERSION;
|
||||
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_UPDATE_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -419,7 +419,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
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
|
||||
@@ -464,7 +465,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
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
|
||||
@@ -496,7 +498,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
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
|
||||
@@ -579,7 +582,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
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
|
||||
@@ -649,7 +653,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
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
|
||||
|
||||
@@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
||||
private final File torDirectory;
|
||||
|
||||
@Inject
|
||||
public UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@IoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
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.Clock;
|
||||
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.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
@@ -83,7 +84,8 @@ import dagger.Component;
|
||||
AppModule.class,
|
||||
AttachmentModule.class,
|
||||
ClockModule.class,
|
||||
MediaModule.class
|
||||
MediaModule.class,
|
||||
RemovableDriveModule.class
|
||||
})
|
||||
public interface AndroidComponent
|
||||
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.reporting.DevConfig;
|
||||
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.tor.AndroidTorPluginFactory;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
@@ -67,7 +69,6 @@ import dagger.Provides;
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
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.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||
@@ -92,6 +93,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
GroupListModule.class,
|
||||
GroupConversationModule.class,
|
||||
SharingModule.class,
|
||||
RemovableDriveModule.class
|
||||
})
|
||||
public class AppModule {
|
||||
|
||||
@@ -149,8 +151,10 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) {
|
||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
||||
AndroidRemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -161,7 +165,7 @@ public class AppModule {
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return emptyList();
|
||||
return singletonList(drive);
|
||||
}
|
||||
|
||||
@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.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.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
||||
@@ -124,8 +125,8 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
Group ourGroup = getOurGroup(txn);
|
||||
if (m.getGroupId().equals(ourGroup.getId())) {
|
||||
throw new InvalidMessageException(
|
||||
@@ -144,7 +145,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
||||
// We've already received a newer update - delete this one
|
||||
db.deleteMessage(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());
|
||||
@@ -155,7 +156,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import javax.annotation.Nullable;
|
||||
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_COMMENT;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||
@@ -109,8 +111,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList list,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList list, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
GroupId groupId = m.getGroupId();
|
||||
MessageType type = getMessageType(meta);
|
||||
@@ -138,7 +141,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
txn.attach(event);
|
||||
|
||||
// shares message and its dependencies
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
} else if (type == WRAPPED_COMMENT) {
|
||||
// Check that the original message ID in the dependency's metadata
|
||||
// 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
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,6 +45,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
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_LOCAL;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
|
||||
@@ -75,8 +76,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
|
||||
@@ -86,8 +88,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
new ForumPostReceivedEvent(m.getGroupId(), header, text);
|
||||
txn.attach(event);
|
||||
|
||||
// share message
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
@@ -449,31 +450,26 @@ class IntroduceeProtocolEngine
|
||||
s.getRemote().acceptTimestamp);
|
||||
if (timestamp == -1) throw new AssertionError();
|
||||
|
||||
Map<TransportId, KeySetId> keys = null;
|
||||
ContactId contactId;
|
||||
try {
|
||||
contactManager.addContact(txn, s.getRemote().author,
|
||||
contactId = contactManager.addContact(txn, s.getRemote().author,
|
||||
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
|
||||
transportPropertyManager.addRemoteProperties(txn, c.getId(),
|
||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||
s.getRemote().transportProperties);
|
||||
} catch (ContactExistsException e) {
|
||||
// Ignore this, because the other introducee might have deleted us.
|
||||
// So we still want updated transport properties
|
||||
// and new transport keys.
|
||||
// The other introducee might have deleted us and been
|
||||
// reintroduced. In that case we can ignore the transport
|
||||
// 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
|
||||
byte[] mac = crypto.activateMac(s);
|
||||
long localTimestamp = getTimestampForInvisibleMessage(s);
|
||||
|
||||
@@ -56,6 +56,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
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.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
@@ -171,8 +172,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary bdfMeta)
|
||||
throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// 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
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
||||
|
||||
@@ -63,6 +63,7 @@ import static java.util.Collections.emptyList;
|
||||
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.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.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
@@ -172,8 +173,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
BdfDictionary metaDict = metadataParser.parse(meta);
|
||||
// Message type is null for version 0.0 private messages
|
||||
@@ -193,8 +194,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
// Don't share message
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private void incomingPrivateMessage(Transaction txn, Message m,
|
||||
|
||||
@@ -55,6 +55,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
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.VERIFIED;
|
||||
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
||||
@@ -518,18 +519,19 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
MessageType type =
|
||||
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
||||
switch (type) {
|
||||
case JOIN:
|
||||
handleJoinMessage(txn, m, meta);
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
case POST:
|
||||
handleGroupMessage(txn, m, meta);
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
default:
|
||||
// the validator should only let valid types pass
|
||||
throw new RuntimeException("Unknown MessageType");
|
||||
|
||||
@@ -54,6 +54,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
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.privategroup.invitation.CreatorState.START;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||
@@ -147,8 +148,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary bdfMeta)
|
||||
throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// 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
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private SessionId getSessionId(GroupId privateGroupId) {
|
||||
|
||||
@@ -49,6 +49,7 @@ import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
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.sharing.MessageType.ABORT;
|
||||
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
|
||||
@@ -133,8 +134,8 @@ abstract class SharingManagerImpl<S extends Shareable>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary d) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary d) throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(d);
|
||||
// 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
|
||||
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.SHARED;
|
||||
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.getGroup;
|
||||
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.MSG_KEY_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -196,7 +196,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
null);
|
||||
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());
|
||||
Event event = ((EventAction) txn.getActions().get(0)).getEvent();
|
||||
AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event;
|
||||
@@ -230,7 +231,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
||||
expectGetContactId(txn, contactGroupId, contact.getId());
|
||||
|
||||
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
|
||||
// event to broadcast
|
||||
assertEquals(1, txn.getActions().size());
|
||||
@@ -260,7 +262,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
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
|
||||
assertEquals(0, txn.getActions().size());
|
||||
@@ -271,7 +274,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
throws DbException, InvalidMessageException {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
expectGetOurGroup(txn);
|
||||
avatarManager.incomingMessage(txn, ourMsg, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
avatarManager.incomingMessage(txn, ourMsg, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
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.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
@@ -184,7 +185,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
||||
will(returnValue(verifiedInfo));
|
||||
}});
|
||||
|
||||
blogManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_SHARE,
|
||||
blogManager.incomingMessage(txn, message, body, meta));
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertEquals(1, txn.getActions().size());
|
||||
@@ -225,7 +227,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
||||
will(returnValue(rssLocalAuthor));
|
||||
}});
|
||||
|
||||
blogManager.incomingMessage(txn, rssMessage, body, meta);
|
||||
assertEquals(ACCEPT_SHARE,
|
||||
blogManager.incomingMessage(txn, rssMessage, body, meta));
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertEquals(1, txn.getActions().size());
|
||||
|
||||
@@ -1667,6 +1667,126 @@ public class IntroductionIntegrationTest
|
||||
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 {
|
||||
return db0.transactionWithResult(false, txn -> introductionManager0
|
||||
.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.nullsafety.NotNullByDefault;
|
||||
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.MessagesSentEvent;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
||||
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
||||
@@ -71,7 +73,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@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
|
||||
Identity aliceIdentity =
|
||||
alice.getIdentityManager().createIdentity("Alice");
|
||||
@@ -86,16 +97,21 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
bob.getEventBus().addListener(listener);
|
||||
// Alice sends a private message to Bob
|
||||
sendMessage(alice, bobId);
|
||||
// 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);
|
||||
// Sync the private message and the attachment
|
||||
read(bob, write(alice, bobId), 2);
|
||||
// Sync Alice's client versions
|
||||
read(bob, write(alice, bobId, eager, 1), 1);
|
||||
// Sync Bob's client versions
|
||||
read(alice, write(bob, aliceId, eager, 1), 1);
|
||||
// Sync Alice's second client versioning update (with the active flag
|
||||
// raised), the private message and the attachment
|
||||
read(bob, write(alice, bobId, eager, 3), 3);
|
||||
// Bob should have received the private message
|
||||
assertTrue(listener.messageAdded);
|
||||
// Bob should have received the attachment
|
||||
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,
|
||||
@@ -149,15 +165,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
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
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestTransportConnectionWriter writer =
|
||||
new TestTransportConnectionWriter(out);
|
||||
new TestTransportConnectionWriter(out, eager);
|
||||
device.getConnectionManager().manageOutgoingConnection(contactId,
|
||||
SIMPLEX_TRANSPORT_ID, writer);
|
||||
// Wait for the writer to be disposed
|
||||
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 out.toByteArray();
|
||||
}
|
||||
@@ -178,6 +203,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
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
|
||||
private static class MessageDeliveryListener implements EventListener {
|
||||
|
||||
@@ -191,7 +234,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
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 junit.framework.TestCase.fail;
|
||||
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.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
@@ -275,43 +276,50 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testIncomingFirstInviteMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.INVITEE, INVITE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingFirstJoinMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.PEER, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingInviteMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, INVITE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingJoinMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingJoinMessageForCreator() throws Exception {
|
||||
expectIncomingMessage(Role.CREATOR, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingLeaveMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, LEAVE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingAbortMessage() throws Exception {
|
||||
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)
|
||||
|
||||
@@ -429,7 +429,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
// Write the messages to a transport stream
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestTransportConnectionWriter writer =
|
||||
new TestTransportConnectionWriter(out);
|
||||
new TestTransportConnectionWriter(out, false);
|
||||
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
||||
SIMPLEX_TRANSPORT_ID, writer);
|
||||
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
||||
@@ -487,7 +487,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
// start outgoing connection
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestTransportConnectionWriter writer =
|
||||
new TestTransportConnectionWriter(out);
|
||||
new TestTransportConnectionWriter(out, false);
|
||||
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
||||
SIMPLEX_TRANSPORT_ID, writer);
|
||||
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
||||
|
||||
@@ -74,6 +74,7 @@ internal class HeadlessModule(private val appDir: File) {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig {
|
||||
val duplex: List<DuplexPluginFactory> =
|
||||
if (isLinux() || isMac()) listOf(tor) else emptyList()
|
||||
|
||||
@@ -64,6 +64,7 @@ internal class HeadlessTestModule(private val appDir: File) {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun providePluginConfig(): PluginConfig {
|
||||
return object : PluginConfig {
|
||||
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = emptyList()
|
||||
|
||||
@@ -136,7 +136,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
||||
val messageId1 = MessageId(getRandomId())
|
||||
val messageId2 = MessageId(getRandomId())
|
||||
val messageIds = listOf(messageId1, messageId2)
|
||||
val event = MessagesSentEvent(contact.id, messageIds)
|
||||
val event = MessagesSentEvent(contact.id, messageIds, 1234)
|
||||
|
||||
every {
|
||||
webSocketController.sendEvent(
|
||||
@@ -274,7 +274,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
||||
val messageId1 = MessageId(getRandomId())
|
||||
val messageId2 = MessageId(getRandomId())
|
||||
val messageIds = listOf(messageId1, messageId2)
|
||||
val event = MessagesSentEvent(contact.id, messageIds)
|
||||
val event = MessagesSentEvent(contact.id, messageIds, 1234)
|
||||
|
||||
val json = """
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user