mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-23 08:09:54 +01:00
Compare commits
24 Commits
2085-trans
...
2048-webp-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7345d5c22c | ||
|
|
b4a2725f03 | ||
|
|
a5972e26fe | ||
|
|
aabb0bfb4a | ||
|
|
76b7e6fecf | ||
|
|
980940a7cf | ||
|
|
9a021daae8 | ||
|
|
cc9904a454 | ||
|
|
b0faab9395 | ||
|
|
f1198b47fd | ||
|
|
37f2ab555f | ||
|
|
61ca2a391b | ||
|
|
88537b9323 | ||
|
|
e6c004e8f6 | ||
|
|
dc7290dab7 | ||
|
|
9dff8bd64a | ||
|
|
35c2f60129 | ||
|
|
471a2372c8 | ||
|
|
fba028db03 | ||
|
|
c647c52638 | ||
|
|
cad5edcf86 | ||
|
|
38f70bb6be | ||
|
|
33bdc81b3e | ||
|
|
552b9ef21a |
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10303
|
versionCode 10304
|
||||||
versionName "1.3.3"
|
versionName "1.3.4"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
AndroidExecutor androidExecutor,
|
AndroidExecutor androidExecutor,
|
||||||
AndroidWakeLockManager wakeLockManager,
|
AndroidWakeLockManager wakeLockManager,
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
|
||||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
public class AndroidRemovableDrivePluginFactory implements
|
|
||||||
SimplexPluginFactory {
|
|
||||||
|
|
||||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
|
||||||
|
|
||||||
private final Application app;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
AndroidRemovableDrivePluginFactory(Application app) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportId getId() {
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxLatency() {
|
|
||||||
return MAX_LATENCY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public SimplexPlugin createPlugin(PluginCallback callback) {
|
|
||||||
return new AndroidRemovableDrivePlugin(app, MAX_LATENCY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
|||||||
private final Application app;
|
private final Application app;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final File torDirectory;
|
private final File torDirectory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
Application app,
|
Application app,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
|
|||||||
@@ -10,17 +10,20 @@ import org.briarproject.bramble.api.Pair;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static java.lang.Runtime.getRuntime;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
|
||||||
@@ -116,6 +119,24 @@ public class AndroidUtils {
|
|||||||
* Returns an array of supported content types for image attachments.
|
* Returns an array of supported content types for image attachments.
|
||||||
*/
|
*/
|
||||||
public static String[] getSupportedImageContentTypes() {
|
public static String[] getSupportedImageContentTypes() {
|
||||||
return new String[] {"image/jpeg", "image/png", "image/gif"};
|
return new String[] {
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getSystemProperty(String propName) {
|
||||||
|
try {
|
||||||
|
Process p = getRuntime().exec("getprop " + propName);
|
||||||
|
Scanner s = new Scanner(p.getInputStream());
|
||||||
|
String line = s.nextLine();
|
||||||
|
s.close();
|
||||||
|
return line;
|
||||||
|
} catch (SecurityException | IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.briarproject.bramble.api;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface Consumer<T> {
|
|
||||||
|
|
||||||
void accept(T t);
|
|
||||||
}
|
|
||||||
@@ -32,31 +32,28 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once for each incoming message that passes validation.
|
* Called once for each incoming message that passes validation.
|
||||||
* <p>
|
|
||||||
* If an unexpected exception occurs while handling data that is assumed
|
|
||||||
* to be valid (e.g. locally created metadata), it may be sensible to
|
|
||||||
* rethrow the unexpected exception as a DbException so that delivery is
|
|
||||||
* attempted again at next startup. This will allow delivery to succeed if
|
|
||||||
* the unexpected exception was caused by a bug that has subsequently been
|
|
||||||
* fixed.
|
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @throws DbException if a database error occurs while delivering the
|
* @return Whether or not this message should be shared
|
||||||
* message. Delivery will be attempted again at next startup. Throwing
|
* @throws DbException Should only be used for real database errors.
|
||||||
* this exception has the same effect as returning
|
* If this is thrown, delivery will be attempted again at next startup,
|
||||||
* {@link DeliveryAction#DEFER}.
|
* whereas if a FormatException is thrown, the message will be permanently
|
||||||
* @throws FormatException if the message is invalid in the context of its
|
* invalidated.
|
||||||
* dependencies. The message and any dependents will be marked as invalid
|
* @throws FormatException Use this for any non-database error
|
||||||
* and deleted along with their metadata. Throwing this exception has the
|
* that occurs while handling remotely created data.
|
||||||
* same effect as returning {@link DeliveryAction#REJECT}.
|
* 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!
|
||||||
*/
|
*/
|
||||||
protected abstract DeliveryAction incomingMessage(Transaction txn,
|
protected abstract boolean incomingMessage(Transaction txn, Message m,
|
||||||
Message m, BdfList body, BdfDictionary meta)
|
BdfList body, BdfDictionary meta) throws DbException,
|
||||||
throws DbException, FormatException;
|
FormatException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
BdfList body = clientHelper.toList(m);
|
BdfList body = clientHelper.toList(m);
|
||||||
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
||||||
|
|||||||
@@ -118,18 +118,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
||||||
TransportKeys k) throws DbException;
|
TransportKeys k) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are any acks or messages to send to the given
|
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
|
||||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
|
||||||
int maxLatency, boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
* local pseudonym.
|
* local pseudonym.
|
||||||
@@ -162,16 +150,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the database contains keys for communicating with the
|
|
||||||
* given contact over the given transport. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the message with the given ID. Unlike
|
* Deletes the message with the given ID. Unlike
|
||||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
||||||
@@ -202,18 +180,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
int maxLength, int maxLatency) throws DbException;
|
int maxLength, int maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a batch of messages for the given contact containing the
|
|
||||||
* messages with the given IDs, for transmission over a transport with
|
|
||||||
* the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* If any of the given messages are not in the database or are not visible
|
|
||||||
* to the contact, they are omitted from the batch without throwing an
|
|
||||||
* exception.
|
|
||||||
*/
|
|
||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
|
||||||
Collection<MessageId> ids, int maxLatency) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an offer for the given contact for transmission over a
|
* Returns an offer for the given contact for transmission over a
|
||||||
* transport with the given maximum latency, or null if there are no
|
* transport with the given maximum latency, or null if there are no
|
||||||
@@ -460,27 +426,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IDs of all messages that are eligible to be sent to the
|
|
||||||
* given contact, together with their raw lengths. This may include
|
|
||||||
* messages that have already been sent and are not yet due for
|
|
||||||
* retransmission.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
|
||||||
ContactId c) throws DbException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total length, including headers, of all messages that are
|
|
||||||
* eligible to be sent to the given contact. This may include messages
|
|
||||||
* that have already been sent and are not yet due for retransmission.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
long getUnackedMessageBytesToSend(Transaction txn, ContactId c)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||||
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
|
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
|
||||||
@@ -538,16 +483,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
|
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contact IDs and transport IDs for which the DB contains
|
|
||||||
* at least one set of transport keys. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
|
||||||
Transaction txn) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given transport keys.
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.keyagreement.event;
|
|||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event that is broadcast when a BQP protocol completes.
|
* An event that is broadcast when a BQP protocol begins.
|
||||||
*/
|
*/
|
||||||
public class KeyAgreementStartedEvent extends Event {
|
public class KeyAgreementStartedEvent extends Event {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.api.plugin.file;
|
package org.briarproject.bramble.api.plugin;
|
||||||
|
|
||||||
public interface FileConstants {
|
public interface FileConstants {
|
||||||
|
|
||||||
@@ -22,11 +22,6 @@ public interface TransportConnectionWriter {
|
|||||||
*/
|
*/
|
||||||
int getMaxIdleTime();
|
int getMaxIdleTime();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the transport is lossy and cheap.
|
|
||||||
*/
|
|
||||||
boolean isLossyAndCheap();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an output stream for writing to the transport connection.
|
* Returns an output stream for writing to the transport connection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,11 +79,6 @@ public abstract class AbstractDuplexTransportConnection
|
|||||||
return plugin.getMaxIdleTime();
|
return plugin.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() throws IOException {
|
public OutputStream getOutputStream() throws IOException {
|
||||||
return AbstractDuplexTransportConnection.this.getOutputStream();
|
return AbstractDuplexTransportConnection.this.getOutputStream();
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
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";
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
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,12 +15,6 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface SimplexPlugin extends Plugin {
|
public interface SimplexPlugin extends Plugin {
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the transport is likely to lose streams and the cost of
|
|
||||||
* transmitting redundant copies of data is cheap.
|
|
||||||
*/
|
|
||||||
boolean isLossyAndCheap();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to create and return a reader for the given transport
|
* Attempts to create and return a reader for the given transport
|
||||||
* properties. Returns null if a reader cannot be created.
|
* properties. Returns null if a reader cannot be created.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
|
|||||||
PriorityHandler handler);
|
PriorityHandler handler);
|
||||||
|
|
||||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, boolean eager, StreamWriter streamWriter);
|
int maxLatency, StreamWriter streamWriter);
|
||||||
|
|
||||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ public class MessagesSentEvent extends Event {
|
|||||||
|
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final Collection<MessageId> messageIds;
|
private final Collection<MessageId> messageIds;
|
||||||
private final long totalLength;
|
|
||||||
|
|
||||||
public MessagesSentEvent(ContactId contactId,
|
public MessagesSentEvent(ContactId contactId,
|
||||||
Collection<MessageId> messageIds, long totalLength) {
|
Collection<MessageId> messageIds) {
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.messageIds = messageIds;
|
this.messageIds = messageIds;
|
||||||
this.totalLength = totalLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactId getContactId() {
|
public ContactId getContactId() {
|
||||||
@@ -34,8 +32,4 @@ public class MessagesSentEvent extends Event {
|
|||||||
public Collection<MessageId> getMessageIds() {
|
public Collection<MessageId> getMessageIds() {
|
||||||
return messageIds;
|
return messageIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTotalLength() {
|
|
||||||
return totalLength;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,54 +10,23 @@ public interface IncomingMessageHook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once for each incoming message that passes validation.
|
* Called once for each incoming message that passes validation.
|
||||||
* <p>
|
|
||||||
* If an unexpected exception occurs while handling data that is assumed
|
|
||||||
* to be valid (e.g. locally created metadata), it may be sensible to
|
|
||||||
* rethrow the unexpected exception as a DbException so that delivery is
|
|
||||||
* attempted again at next startup. This will allow delivery to succeed if
|
|
||||||
* the unexpected exception was caused by a bug that has subsequently been
|
|
||||||
* fixed.
|
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @throws DbException if a database error occurs while delivering the
|
* @return Whether or not this message should be shared
|
||||||
* message. Delivery will be attempted again at next startup. Throwing
|
* @throws DbException Should only be used for real database errors.
|
||||||
* this exception has the same effect as returning
|
* If this is thrown, delivery will be attempted again at next startup,
|
||||||
* {@link DeliveryAction#DEFER}.
|
* whereas if an InvalidMessageException is thrown,
|
||||||
* @throws InvalidMessageException if the message is invalid in the context
|
* the message will be permanently invalidated.
|
||||||
* of its dependencies. The message and any dependents will be marked as
|
* @throws InvalidMessageException for any non-database error
|
||||||
* invalid and deleted along with their metadata. Throwing this exception
|
* that occurs while handling remotely created data.
|
||||||
* has the same effect as returning {@link DeliveryAction#REJECT}.
|
* 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!
|
||||||
*/
|
*/
|
||||||
DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta)
|
boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
throws DbException, InvalidMessageException;
|
throws DbException, InvalidMessageException;
|
||||||
|
|
||||||
enum DeliveryAction {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message and any dependent messages will be moved to the
|
|
||||||
* {@link MessageState#INVALID INVALID state} and deleted, along with
|
|
||||||
* their metadata.
|
|
||||||
*/
|
|
||||||
REJECT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message will be moved to the
|
|
||||||
* {@link MessageState#PENDING PENDING state}. Delivery will be
|
|
||||||
* attempted again at next startup.
|
|
||||||
*/
|
|
||||||
DEFER,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message will be moved to the
|
|
||||||
* {@link MessageState#DELIVERED DELIVERED state} and shared.
|
|
||||||
*/
|
|
||||||
ACCEPT_SHARE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message will be moved to the
|
|
||||||
* {@link MessageState#DELIVERED DELIVERED state} and will not be
|
|
||||||
* shared.
|
|
||||||
*/
|
|
||||||
ACCEPT_DO_NOT_SHARE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,8 @@
|
|||||||
package org.briarproject.bramble.api.sync.validation;
|
package org.briarproject.bramble.api.sync.validation;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
|
||||||
|
|
||||||
public enum MessageState {
|
public enum MessageState {
|
||||||
|
|
||||||
/**
|
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
|
||||||
* A remote message that has not yet been validated.
|
|
||||||
*/
|
|
||||||
UNKNOWN(0),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A remote message that has failed validation, has been
|
|
||||||
* {@link DeliveryAction#REJECT rejected} by the local sync client, or
|
|
||||||
* depends on another message that has failed validation or been rejected.
|
|
||||||
*/
|
|
||||||
INVALID(1),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A remote message that has passed validation and is awaiting delivery to
|
|
||||||
* the local sync client. The message will not be delivered until all its
|
|
||||||
* dependencies have been validated and delivered.
|
|
||||||
*/
|
|
||||||
PENDING(2),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A local message, or a remote message that has passed validation and
|
|
||||||
* been delivered to the local sync client.
|
|
||||||
*/
|
|
||||||
DELIVERED(3);
|
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
|
|
||||||
|
|||||||
@@ -22,24 +22,8 @@ public interface KeyManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives and stores a set of rotation mode transport keys for
|
* Derives and stores a set of rotation mode transport keys for
|
||||||
* communicating with the given contact over the given transport and
|
* communicating with the given contact over each transport and returns the
|
||||||
* returns the key set ID, or null if the transport is not supported.
|
* key set IDs.
|
||||||
* <p/>
|
|
||||||
* {@link StreamContext StreamContexts} for the contact can be created
|
|
||||||
* after this method has returned.
|
|
||||||
*
|
|
||||||
* @param alice True if the local party is Alice
|
|
||||||
* @param active Whether the derived keys can be used for outgoing streams
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t,
|
|
||||||
SecretKey rootKey, long timestamp, boolean alice,
|
|
||||||
boolean active) throws DbException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives and stores a set of rotation mode transport keys for
|
|
||||||
* communicating with the given contact over each supported transport and
|
|
||||||
* returns the key set IDs.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* {@link StreamContext StreamContexts} for the contact can be created
|
* {@link StreamContext StreamContexts} for the contact can be created
|
||||||
* after this method has returned.
|
* after this method has returned.
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
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,7 +11,6 @@ import org.briarproject.bramble.properties.PropertiesModule;
|
|||||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
|
||||||
import org.briarproject.bramble.versioning.VersioningModule;
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
public interface BrambleCoreEagerSingletons {
|
public interface BrambleCoreEagerSingletons {
|
||||||
@@ -34,8 +33,6 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
|
|
||||||
void inject(RendezvousModule.EagerSingletons init);
|
void inject(RendezvousModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(TransportKeyAgreementModule.EagerSingletons init);
|
|
||||||
|
|
||||||
void inject(TransportModule.EagerSingletons init);
|
void inject(TransportModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(ValidationModule.EagerSingletons init);
|
void inject(ValidationModule.EagerSingletons init);
|
||||||
@@ -54,7 +51,6 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
c.inject(new RendezvousModule.EagerSingletons());
|
c.inject(new RendezvousModule.EagerSingletons());
|
||||||
c.inject(new PluginModule.EagerSingletons());
|
c.inject(new PluginModule.EagerSingletons());
|
||||||
c.inject(new PropertiesModule.EagerSingletons());
|
c.inject(new PropertiesModule.EagerSingletons());
|
||||||
c.inject(new TransportKeyAgreementModule.EagerSingletons());
|
|
||||||
c.inject(new TransportModule.EagerSingletons());
|
c.inject(new TransportModule.EagerSingletons());
|
||||||
c.inject(new ValidationModule.EagerSingletons());
|
c.inject(new ValidationModule.EagerSingletons());
|
||||||
c.inject(new VersioningModule.EagerSingletons());
|
c.inject(new VersioningModule.EagerSingletons());
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.briarproject.bramble.settings.SettingsModule;
|
|||||||
import org.briarproject.bramble.sync.SyncModule;
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
|
||||||
import org.briarproject.bramble.versioning.VersioningModule;
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
@@ -50,7 +49,6 @@ import dagger.Module;
|
|||||||
RendezvousModule.class,
|
RendezvousModule.class,
|
||||||
SettingsModule.class,
|
SettingsModule.class,
|
||||||
SyncModule.class,
|
SyncModule.class,
|
||||||
TransportKeyAgreementModule.class,
|
|
||||||
TransportModule.class,
|
TransportModule.class,
|
||||||
ValidationModule.class,
|
ValidationModule.class,
|
||||||
VersioningModule.class
|
VersioningModule.class
|
||||||
|
|||||||
@@ -71,10 +71,8 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||||
w.getOutputStream(), ctx);
|
w.getOutputStream(), ctx);
|
||||||
ContactId c = requireNonNull(ctx.getContactId());
|
ContactId c = requireNonNull(ctx.getContactId());
|
||||||
// Use eager retransmission if the transport is lossy and cheap
|
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
|
||||||
streamWriter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,18 +162,6 @@ interface Database<T> {
|
|||||||
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
|
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are any acks or messages to send to the given
|
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
|
||||||
boolean containsAnythingToSend(T txn, ContactId c, int maxLatency,
|
|
||||||
boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
* local pseudonym.
|
* local pseudonym.
|
||||||
@@ -227,16 +215,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
boolean containsTransport(T txn, TransportId t) throws DbException;
|
boolean containsTransport(T txn, TransportId t) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the database contains keys for communicating with the
|
|
||||||
* given contact over the given transport. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
boolean containsTransportKeys(T txn, ContactId c, TransportId t)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given message, the message is
|
* Returns true if the database contains the given message, the message is
|
||||||
* shared, and the visibility of the message's group to the given contact
|
* shared, and the visibility of the message's group to the given contact
|
||||||
@@ -498,37 +476,11 @@ interface Database<T> {
|
|||||||
* Returns the IDs of some messages that are eligible to be sent to the
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
* given contact, up to the given total length.
|
* given contact, up to the given total length.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
|
||||||
* does not return messages that have already been sent unless they are
|
|
||||||
* due for retransmission.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
||||||
int maxLatency) throws DbException;
|
int maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IDs of all messages that are eligible to be sent to the
|
|
||||||
* given contact, together with their raw lengths.
|
|
||||||
* <p/>
|
|
||||||
* Unlike {@link #getMessagesToSend(Object, ContactId, int, int)} this
|
|
||||||
* method may return messages that have already been sent and are not yet
|
|
||||||
* due for retransmission.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total length, including headers, of all messages that are
|
|
||||||
* eligible to be sent to the given contact. This may include messages
|
|
||||||
* that have already been sent and are not yet due for retransmission.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
long getUnackedMessageBytesToSend(T txn, ContactId c) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated.
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -628,16 +580,6 @@ interface Database<T> {
|
|||||||
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contact IDs and transport IDs for which the DB contains
|
|
||||||
* at least one set of transport keys. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(T txn)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given transport keys.
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -341,15 +341,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.addTransportKeys(txn, p, k);
|
return db.addTransportKeys(txn, p, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
|
||||||
int maxLatency, boolean eager) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsContact(Transaction transaction, AuthorId remote,
|
public boolean containsContact(Transaction transaction, AuthorId remote,
|
||||||
AuthorId local) throws DbException {
|
AuthorId local) throws DbException {
|
||||||
@@ -380,13 +371,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.containsPendingContact(txn, p);
|
return db.containsPendingContact(txn, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsTransportKeys(Transaction transaction, ContactId c,
|
|
||||||
TransportId t) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
return db.containsTransportKeys(txn, c, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMessage(Transaction transaction, MessageId m)
|
public void deleteMessage(Transaction transaction, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -431,43 +415,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||||
long totalLength = 0;
|
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
Message message = db.getMessage(txn, m);
|
messages.add(db.getMessage(txn, m));
|
||||||
totalLength += message.getRawLength();
|
|
||||||
messages.add(message);
|
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
transaction.attach(new MessagesSentEvent(c, ids));
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Message> generateBatch(Transaction transaction,
|
|
||||||
ContactId c, Collection<MessageId> ids, int maxLatency)
|
|
||||||
throws DbException {
|
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
long totalLength = 0;
|
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
|
||||||
List<MessageId> sentIds = new ArrayList<>(ids.size());
|
|
||||||
for (MessageId m : ids) {
|
|
||||||
if (db.containsVisibleMessage(txn, c, m)) {
|
|
||||||
Message message = db.getMessage(txn, m);
|
|
||||||
totalLength += message.getRawLength();
|
|
||||||
messages.add(message);
|
|
||||||
sentIds.add(m);
|
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (messages.isEmpty()) return messages;
|
|
||||||
db.lowerRequestedFlag(txn, c, sentIds);
|
|
||||||
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,17 +467,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||||
long totalLength = 0;
|
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
Message message = db.getMessage(txn, m);
|
messages.add(db.getMessage(txn, m));
|
||||||
totalLength += message.getRawLength();
|
|
||||||
messages.add(message);
|
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
transaction.attach(new MessagesSentEvent(c, ids));
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,25 +692,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<MessageId, Integer> getUnackedMessagesToSend(
|
|
||||||
Transaction transaction,
|
|
||||||
ContactId c) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
return db.getUnackedMessagesToSend(txn, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getUnackedMessageBytesToSend(Transaction transaction,
|
|
||||||
ContactId c) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
return db.getUnackedMessageBytesToSend(txn, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, MessageState> getMessageDependencies(
|
public Map<MessageId, MessageState> getMessageDependencies(
|
||||||
Transaction transaction, MessageId m) throws DbException {
|
Transaction transaction, MessageId m) throws DbException {
|
||||||
@@ -832,13 +765,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getTransportKeys(txn, t);
|
return db.getTransportKeys(txn, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
|
||||||
Transaction transaction) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
return db.getTransportsWithKeys(txn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -345,11 +344,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||||
+ " ON statuses (contactId, timestamp)";
|
+ " ON statuses (contactId, timestamp)";
|
||||||
|
|
||||||
private static final String
|
|
||||||
INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP =
|
|
||||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp"
|
|
||||||
+ " ON statuses (contactId, txCount, timestamp)";
|
|
||||||
|
|
||||||
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
|
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
|
||||||
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
||||||
+ " ON messages (cleanupDeadline)";
|
+ " ON messages (cleanupDeadline)";
|
||||||
@@ -576,7 +570,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
|
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -1127,55 +1120,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
|
||||||
int maxLatency, boolean eager) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT NULL FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND ack = TRUE";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean acksToSend = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
if (acksToSend) return true;
|
|
||||||
if (eager) {
|
|
||||||
sql = "SELECT NULL from statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
} else {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
long eta = now + maxLatency;
|
|
||||||
sql = "SELECT NULL FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
|
||||||
+ " AND (expiry <= ? OR eta > ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
ps.setLong(3, now);
|
|
||||||
ps.setLong(4, eta);
|
|
||||||
}
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean messagesToSend = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return messagesToSend;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsContact(Connection txn, AuthorId remote,
|
public boolean containsContact(Connection txn, AuthorId remote,
|
||||||
AuthorId local) throws DbException {
|
AuthorId local) throws DbException {
|
||||||
@@ -1333,29 +1277,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsTransportKeys(Connection txn, ContactId c,
|
|
||||||
TransportId t) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT NULL FROM outgoingKeys"
|
|
||||||
+ " WHERE contactId = ? AND transportId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setString(2, t.getString());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean found = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return found;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
||||||
MessageId m) throws DbException {
|
MessageId m) throws DbException {
|
||||||
@@ -2284,63 +2205,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
|
|
||||||
ContactId c) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT length, messageId FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
|
||||||
+ " ORDER BY txCount, timestamp";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
Map<MessageId, Integer> results = new LinkedHashMap<>();
|
|
||||||
while (rs.next()) {
|
|
||||||
int length = rs.getInt(1);
|
|
||||||
MessageId id = new MessageId(rs.getBytes(2));
|
|
||||||
results.put(id, length);
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return results;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getUnackedMessageBytesToSend(Connection txn, ContactId c)
|
|
||||||
throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT SUM(length) FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
rs.next();
|
|
||||||
long total = rs.getInt(1);
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return total;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2710,38 +2574,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
|
||||||
Connection txn) throws DbException {
|
|
||||||
Statement s = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT DISTINCT contactId, transportId"
|
|
||||||
+ " FROM outgoingKeys";
|
|
||||||
s = txn.createStatement();
|
|
||||||
rs = s.executeQuery(sql);
|
|
||||||
Map<ContactId, Collection<TransportId>> ids = new HashMap<>();
|
|
||||||
while (rs.next()) {
|
|
||||||
ContactId c = new ContactId(rs.getInt(1));
|
|
||||||
TransportId t = new TransportId(rs.getString(2));
|
|
||||||
Collection<TransportId> transportIds = ids.get(c);
|
|
||||||
if (transportIds == null) {
|
|
||||||
transportIds = new ArrayList<>();
|
|
||||||
ids.put(c, transportIds);
|
|
||||||
}
|
|
||||||
transportIds.add(t);
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
s.close();
|
|
||||||
return ids;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(s, LOG, WARNING);
|
|
||||||
tryToClose(s, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Connection txn, TransportId t,
|
public void incrementStreamCounter(Connection txn, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
|
|||||||
@@ -473,6 +473,16 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
|||||||
return discoverSemaphore.availablePermits() == 0;
|
return discoverSemaphore.availablePermits() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disablePolling() {
|
||||||
|
connectionLimiter.startLimiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enablePolling() {
|
||||||
|
connectionLimiter.endLimiting();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection discoverAndConnectForSetup(String uuid) {
|
public DuplexTransportConnection discoverAndConnectForSetup(String uuid) {
|
||||||
DuplexTransportConnection conn = discoverAndConnect(uuid);
|
DuplexTransportConnection conn = discoverAndConnect(uuid);
|
||||||
@@ -501,9 +511,9 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
|||||||
if (s.getNamespace().equals(ID.getString()))
|
if (s.getNamespace().equals(ID.getString()))
|
||||||
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
|
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
|
||||||
} else if (e instanceof KeyAgreementListeningEvent) {
|
} else if (e instanceof KeyAgreementListeningEvent) {
|
||||||
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
|
connectionLimiter.startLimiting();
|
||||||
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
|
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
|
||||||
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
|
connectionLimiter.endLimiting();
|
||||||
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
|
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
|
||||||
RemoteTransportPropertiesUpdatedEvent r =
|
RemoteTransportPropertiesUpdatedEvent r =
|
||||||
(RemoteTransportPropertiesUpdatedEvent) e;
|
(RemoteTransportPropertiesUpdatedEvent) e;
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|||||||
interface BluetoothConnectionLimiter {
|
interface BluetoothConnectionLimiter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the limiter that key agreement has started.
|
* Tells the limiter to not allow regular polling connections (because we
|
||||||
|
* are about to do key agreement, or connect via BT for setup).
|
||||||
*/
|
*/
|
||||||
void keyAgreementStarted();
|
void startLimiting();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the limiter that key agreement has ended.
|
* Tells the limiter to no longer limit regular polling connections.
|
||||||
*/
|
*/
|
||||||
void keyAgreementEnded();
|
void endLimiting();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if a contact connection can be opened. This method does not
|
* Returns true if a contact connection can be opened. This method does not
|
||||||
|
|||||||
@@ -30,34 +30,37 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
|||||||
private final List<DuplexTransportConnection> connections =
|
private final List<DuplexTransportConnection> connections =
|
||||||
new LinkedList<>();
|
new LinkedList<>();
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private boolean keyAgreementInProgress = false;
|
private int limitingInProgress = 0;
|
||||||
|
|
||||||
BluetoothConnectionLimiterImpl(EventBus eventBus) {
|
BluetoothConnectionLimiterImpl(EventBus eventBus) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyAgreementStarted() {
|
public void startLimiting() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
keyAgreementInProgress = true;
|
limitingInProgress++;
|
||||||
}
|
}
|
||||||
LOG.info("Key agreement started");
|
LOG.info("Limiting started");
|
||||||
eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
|
eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyAgreementEnded() {
|
public void endLimiting() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
keyAgreementInProgress = false;
|
limitingInProgress--;
|
||||||
|
if (limitingInProgress < 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LOG.info("Key agreement ended");
|
LOG.info("Limiting ended");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canOpenContactConnection() {
|
public boolean canOpenContactConnection() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (keyAgreementInProgress) {
|
if (limitingInProgress > 0) {
|
||||||
LOG.info("Can't open contact connection during key agreement");
|
LOG.info("Can't open contact connection while limiting");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Can open contact connection");
|
LOG.info("Can open contact connection");
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ public interface BluetoothPlugin extends DuplexPlugin {
|
|||||||
|
|
||||||
boolean isDiscovering();
|
boolean isDiscovering();
|
||||||
|
|
||||||
|
void disablePolling();
|
||||||
|
|
||||||
|
void enablePolling();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection discoverAndConnectForSetup(String uuid);
|
DuplexTransportConnection discoverAndConnectForSetup(String uuid);
|
||||||
|
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
|
||||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(AbstractRemovableDrivePlugin.class.getName());
|
|
||||||
|
|
||||||
private final int maxLatency;
|
|
||||||
|
|
||||||
abstract InputStream openInputStream(TransportProperties p)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
abstract OutputStream openOutputStream(TransportProperties p)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
AbstractRemovableDrivePlugin(int maxLatency) {
|
|
||||||
this.maxLatency = maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportId getId() {
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxLatency() {
|
|
||||||
return maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
// Unused for simplex transports
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public State getState() {
|
|
||||||
return ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getReasonsDisabled() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldPoll() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPollingInterval() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void poll(
|
|
||||||
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportConnectionReader createReader(TransportProperties p) {
|
|
||||||
try {
|
|
||||||
return new TransportInputStreamReader(openInputStream(p));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportConnectionWriter createWriter(TransportProperties p) {
|
|
||||||
try {
|
|
||||||
return new TransportOutputStreamWriter(this, openOutputStream(p));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,8 +15,8 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ class FileTransportWriter implements TransportConnectionWriter {
|
|||||||
return plugin.getMaxIdleTime();
|
return plugin.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return plugin.isLossyAndCheap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() {
|
public OutputStream getOutputStream() {
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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,7 +44,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
|
|||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -116,8 +115,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
// Find the latest update for this transport, if any
|
// Find the latest update for this transport, if any
|
||||||
BdfDictionary d = metadataParser.parse(meta);
|
BdfDictionary d = metadataParser.parse(meta);
|
||||||
@@ -132,14 +131,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
// We've already received a newer update - delete this one
|
// We've already received a newer update - delete this one
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.Versions;
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
@@ -23,11 +22,7 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
|||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -66,7 +61,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
private final boolean eager;
|
|
||||||
private final StreamWriter streamWriter;
|
private final StreamWriter streamWriter;
|
||||||
private final SyncRecordWriter recordWriter;
|
private final SyncRecordWriter recordWriter;
|
||||||
private final AtomicInteger outstandingQueries;
|
private final AtomicInteger outstandingQueries;
|
||||||
@@ -76,7 +70,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
|
|
||||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
EventBus eventBus, ContactId contactId, TransportId transportId,
|
EventBus eventBus, ContactId contactId, TransportId transportId,
|
||||||
int maxLatency, boolean eager, StreamWriter streamWriter,
|
int maxLatency, StreamWriter streamWriter,
|
||||||
SyncRecordWriter recordWriter) {
|
SyncRecordWriter recordWriter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
@@ -84,7 +78,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
this.eager = eager;
|
|
||||||
this.streamWriter = streamWriter;
|
this.streamWriter = streamWriter;
|
||||||
this.recordWriter = recordWriter;
|
this.recordWriter = recordWriter;
|
||||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
outstandingQueries = new AtomicInteger(2); // One per type of record
|
||||||
@@ -99,9 +92,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
// Send our supported protocol versions
|
// Send our supported protocol versions
|
||||||
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
dbExecutor.execute(this::generateAck);
|
dbExecutor.execute(new GenerateAck());
|
||||||
if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
|
dbExecutor.execute(new GenerateBatch());
|
||||||
else dbExecutor.execute(this::generateBatch);
|
|
||||||
// Write records until interrupted or no more records to write
|
// Write records until interrupted or no more records to write
|
||||||
try {
|
try {
|
||||||
while (!interrupted) {
|
while (!interrupted) {
|
||||||
@@ -146,110 +138,81 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
private class GenerateAck implements Runnable {
|
||||||
private void loadUnackedMessageIds() {
|
|
||||||
if (interrupted) return;
|
@DatabaseExecutor
|
||||||
try {
|
@Override
|
||||||
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
|
public void run() {
|
||||||
db.getUnackedMessagesToSend(txn, contactId));
|
if (interrupted) return;
|
||||||
if (LOG.isLoggable(INFO)) {
|
try {
|
||||||
LOG.info(ids.size() + " unacked messages to send");
|
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();
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
|
||||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
private class WriteAck implements ThrowingRunnable<IOException> {
|
||||||
private void generateEagerBatch(Map<MessageId, Integer> ids) {
|
|
||||||
if (interrupted) return;
|
private final Ack ack;
|
||||||
// Take some message IDs from `ids` to form a batch
|
|
||||||
Collection<MessageId> batchIds = new ArrayList<>();
|
private WriteAck(Ack ack) {
|
||||||
long totalLength = 0;
|
this.ack = ack;
|
||||||
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
// Check whether the next message will fit in the batch
|
|
||||||
Entry<MessageId, Integer> e = it.next();
|
|
||||||
int length = e.getValue();
|
|
||||||
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
|
|
||||||
// Add the message to the batch
|
|
||||||
it.remove();
|
|
||||||
batchIds.add(e.getKey());
|
|
||||||
totalLength += length;
|
|
||||||
}
|
}
|
||||||
if (batchIds.isEmpty()) throw new AssertionError();
|
|
||||||
try {
|
@IoExecutor
|
||||||
Collection<Message> batch =
|
@Override
|
||||||
db.transactionWithResult(false, txn ->
|
public void run() throws IOException {
|
||||||
db.generateBatch(txn, contactId, batchIds,
|
if (interrupted) return;
|
||||||
maxLatency));
|
recordWriter.writeAck(ack);
|
||||||
writerTasks.add(() -> writeEagerBatch(batch, ids));
|
LOG.info("Sent ack");
|
||||||
} catch (DbException e) {
|
dbExecutor.execute(new GenerateAck());
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IoExecutor
|
private class GenerateBatch implements Runnable {
|
||||||
private void writeEagerBatch(Collection<Message> batch,
|
|
||||||
Map<MessageId, Integer> ids) throws IOException {
|
|
||||||
if (interrupted) return;
|
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
|
||||||
LOG.info("Sent eager batch");
|
|
||||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
|
||||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void generateAck() {
|
@Override
|
||||||
if (interrupted) return;
|
public void run() {
|
||||||
try {
|
if (interrupted) return;
|
||||||
Ack a = db.transactionWithNullableResult(false, txn ->
|
try {
|
||||||
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
|
Collection<Message> b =
|
||||||
if (LOG.isLoggable(INFO))
|
db.transactionWithNullableResult(false, txn ->
|
||||||
LOG.info("Generated ack: " + (a != null));
|
db.generateBatch(txn, contactId,
|
||||||
if (a == null) decrementOutstandingQueries();
|
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
|
||||||
else writerTasks.add(() -> writeAck(a));
|
if (LOG.isLoggable(INFO))
|
||||||
} catch (DbException e) {
|
LOG.info("Generated batch: " + (b != null));
|
||||||
logException(LOG, WARNING, e);
|
if (b == null) decrementOutstandingQueries();
|
||||||
interrupt();
|
else writerTasks.add(new WriteBatch(b));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IoExecutor
|
private class WriteBatch implements ThrowingRunnable<IOException> {
|
||||||
private void writeAck(Ack ack) throws IOException {
|
|
||||||
if (interrupted) return;
|
|
||||||
recordWriter.writeAck(ack);
|
|
||||||
LOG.info("Sent ack");
|
|
||||||
dbExecutor.execute(this::generateAck);
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
private final Collection<Message> batch;
|
||||||
private void generateBatch() {
|
|
||||||
if (interrupted) return;
|
private WriteBatch(Collection<Message> batch) {
|
||||||
try {
|
this.batch = batch;
|
||||||
Collection<Message> b =
|
|
||||||
db.transactionWithNullableResult(false, txn ->
|
|
||||||
db.generateBatch(txn, contactId,
|
|
||||||
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Generated batch: " + (b != null));
|
|
||||||
if (b == null) decrementOutstandingQueries();
|
|
||||||
else writerTasks.add(() -> writeBatch(b));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void writeBatch(Collection<Message> batch) throws IOException {
|
@Override
|
||||||
if (interrupted) return;
|
public void run() throws IOException {
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
if (interrupted) return;
|
||||||
LOG.info("Sent batch");
|
for (Message m : batch) recordWriter.writeMessage(m);
|
||||||
dbExecutor.execute(this::generateBatch);
|
LOG.info("Sent batch");
|
||||||
|
dbExecutor.execute(new GenerateBatch());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, boolean eager, StreamWriter streamWriter) {
|
int maxLatency, StreamWriter streamWriter) {
|
||||||
OutputStream out = streamWriter.getOutputStream();
|
OutputStream out = streamWriter.getOutputStream();
|
||||||
SyncRecordWriter recordWriter =
|
SyncRecordWriter recordWriter =
|
||||||
recordWriterFactory.createRecordWriter(out);
|
recordWriterFactory.createRecordWriter(out);
|
||||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
||||||
maxLatency, eager, streamWriter, recordWriter);
|
maxLatency, streamWriter, recordWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.sync.MessageContext;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
@@ -41,10 +40,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||||
@@ -190,19 +185,16 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
int majorVersion = g.getMajorVersion();
|
int majorVersion = g.getMajorVersion();
|
||||||
Metadata meta =
|
Metadata meta =
|
||||||
db.getMessageMetadataForValidator(txn, id);
|
db.getMessageMetadataForValidator(txn, id);
|
||||||
DeliveryAction action =
|
DeliveryResult result =
|
||||||
deliverMessage(txn, m, c, majorVersion, meta);
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (action == REJECT) {
|
if (result.valid) {
|
||||||
invalidateMessage(txn, id);
|
addPendingDependents(txn, id, pending);
|
||||||
|
if (result.share) {
|
||||||
|
db.setMessageShared(txn, id);
|
||||||
|
toShare.addAll(states.keySet());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
addDependentsToInvalidate(txn, id, invalidate);
|
addDependentsToInvalidate(txn, id, invalidate);
|
||||||
} else if (action == ACCEPT_SHARE) {
|
|
||||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
|
||||||
addPendingDependents(txn, id, pending);
|
|
||||||
db.setMessageShared(txn, id);
|
|
||||||
toShare.addAll(states.keySet());
|
|
||||||
} else if (action == ACCEPT_DO_NOT_SHARE) {
|
|
||||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
|
||||||
addPendingDependents(txn, id, pending);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,21 +275,16 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
Metadata meta = context.getMetadata();
|
Metadata meta = context.getMetadata();
|
||||||
db.mergeMessageMetadata(txn, id, meta);
|
db.mergeMessageMetadata(txn, id, meta);
|
||||||
if (allDelivered) {
|
if (allDelivered) {
|
||||||
DeliveryAction action =
|
DeliveryResult result =
|
||||||
deliverMessage(txn, m, c, majorVersion, meta);
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (action == REJECT) {
|
if (result.valid) {
|
||||||
invalidateMessage(txn, id);
|
addPendingDependents(txn, id, pending);
|
||||||
|
if (result.share) {
|
||||||
|
db.setMessageShared(txn, id);
|
||||||
|
toShare.addAll(dependencies);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
addDependentsToInvalidate(txn, id, invalidate);
|
addDependentsToInvalidate(txn, id, invalidate);
|
||||||
} else if (action == DEFER) {
|
|
||||||
db.setMessageState(txn, id, PENDING);
|
|
||||||
} else if (action == ACCEPT_SHARE) {
|
|
||||||
db.setMessageState(txn, id, DELIVERED);
|
|
||||||
addPendingDependents(txn, id, pending);
|
|
||||||
db.setMessageShared(txn, id);
|
|
||||||
toShare.addAll(dependencies);
|
|
||||||
} else if (action == ACCEPT_DO_NOT_SHARE) {
|
|
||||||
db.setMessageState(txn, id, DELIVERED);
|
|
||||||
addPendingDependents(txn, id, pending);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
db.setMessageState(txn, id, PENDING);
|
db.setMessageState(txn, id, PENDING);
|
||||||
@@ -317,21 +304,23 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private DeliveryAction deliverMessage(Transaction txn, Message m,
|
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
||||||
ClientId c, int majorVersion, Metadata meta) {
|
ClientId c, int majorVersion, Metadata meta) throws DbException {
|
||||||
// Deliver the message to the client if it has registered a hook
|
// Deliver the message to the client if it's registered a hook
|
||||||
|
boolean shareMsg = false;
|
||||||
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
||||||
IncomingMessageHook hook = hooks.get(cv);
|
IncomingMessageHook hook = hooks.get(cv);
|
||||||
if (hook == null) return ACCEPT_DO_NOT_SHARE;
|
if (hook != null) {
|
||||||
try {
|
try {
|
||||||
return hook.incomingMessage(txn, m, meta);
|
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||||
} catch (DbException e) {
|
} catch (InvalidMessageException e) {
|
||||||
logException(LOG, INFO, e);
|
logException(LOG, INFO, e);
|
||||||
return DEFER;
|
invalidateMessage(txn, m.getId());
|
||||||
} catch (InvalidMessageException e) {
|
return new DeliveryResult(false, false);
|
||||||
logException(LOG, INFO, e);
|
}
|
||||||
return REJECT;
|
|
||||||
}
|
}
|
||||||
|
db.setMessageState(txn, m.getId(), DELIVERED);
|
||||||
|
return new DeliveryResult(true, shareMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -458,4 +447,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DeliveryResult {
|
||||||
|
|
||||||
|
private final boolean valid, share;
|
||||||
|
|
||||||
|
private DeliveryResult(boolean valid, boolean share) {
|
||||||
|
this.valid = valid;
|
||||||
|
this.share = share;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final PluginConfig pluginConfig;
|
private final PluginConfig pluginConfig;
|
||||||
|
private final TransportKeyManagerFactory transportKeyManagerFactory;
|
||||||
private final TransportCrypto transportCrypto;
|
private final TransportCrypto transportCrypto;
|
||||||
|
|
||||||
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
||||||
@@ -60,39 +61,34 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
KeyManagerImpl(DatabaseComponent db,
|
KeyManagerImpl(DatabaseComponent db,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
PluginConfig pluginConfig,
|
PluginConfig pluginConfig,
|
||||||
TransportCrypto transportCrypto,
|
TransportKeyManagerFactory transportKeyManagerFactory,
|
||||||
TransportKeyManagerFactory transportKeyManagerFactory) {
|
TransportCrypto transportCrypto) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.pluginConfig = pluginConfig;
|
this.pluginConfig = pluginConfig;
|
||||||
|
this.transportKeyManagerFactory = transportKeyManagerFactory;
|
||||||
this.transportCrypto = transportCrypto;
|
this.transportCrypto = transportCrypto;
|
||||||
managers = new ConcurrentHashMap<>();
|
managers = new ConcurrentHashMap<>();
|
||||||
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
|
|
||||||
TransportKeyManager m = transportKeyManagerFactory.
|
|
||||||
createTransportKeyManager(f.getId(), f.getMaxLatency());
|
|
||||||
managers.put(f.getId(), m);
|
|
||||||
}
|
|
||||||
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) {
|
|
||||||
TransportKeyManager m = transportKeyManagerFactory.
|
|
||||||
createTransportKeyManager(f.getId(), f.getMaxLatency());
|
|
||||||
managers.put(f.getId(), m);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startService() throws ServiceException {
|
public void startService() throws ServiceException {
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
|
Map<TransportId, Integer> transports = new HashMap<>();
|
||||||
|
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
|
||||||
|
transports.put(f.getId(), f.getMaxLatency());
|
||||||
|
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
|
||||||
|
transports.put(f.getId(), f.getMaxLatency());
|
||||||
try {
|
try {
|
||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
for (SimplexPluginFactory f :
|
for (Entry<TransportId, Integer> e : transports.entrySet())
|
||||||
pluginConfig.getSimplexFactories()) {
|
db.addTransport(txn, e.getKey(), e.getValue());
|
||||||
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
for (Entry<TransportId, Integer> e : transports.entrySet()) {
|
||||||
managers.get(f.getId()).start(txn);
|
TransportKeyManager m = transportKeyManagerFactory
|
||||||
}
|
.createTransportKeyManager(e.getKey(),
|
||||||
for (DuplexPluginFactory f :
|
e.getValue());
|
||||||
pluginConfig.getDuplexFactories()) {
|
managers.put(e.getKey(), m);
|
||||||
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
m.start(txn);
|
||||||
managers.get(f.getId()).start(txn);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
@@ -105,17 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeySetId addRotationKeys(Transaction txn, ContactId c,
|
public Map<TransportId, KeySetId> addRotationKeys(
|
||||||
TransportId t, SecretKey rootKey, long timestamp, boolean alice,
|
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
|
||||||
boolean active) throws DbException {
|
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<>();
|
Map<TransportId, KeySetId> ids = new HashMap<>();
|
||||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||||
TransportId t = e.getKey();
|
TransportId t = e.getKey();
|
||||||
@@ -149,7 +137,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
|
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
|
||||||
throws DbException, GeneralSecurityException {
|
throws DbException, GeneralSecurityException {
|
||||||
SecretKey staticMasterKey = transportCrypto
|
SecretKey staticMasterKey = transportCrypto
|
||||||
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
|
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
|
||||||
SecretKey rootKey =
|
SecretKey rootKey =
|
||||||
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
|
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
|
||||||
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
|
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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";
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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,7 +50,6 @@ import static java.util.Collections.emptyList;
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
|
|
||||||
@@ -174,8 +173,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
// Parse the new remote update
|
// Parse the new remote update
|
||||||
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
||||||
@@ -188,7 +187,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
// Load and parse the latest local update
|
// Load and parse the latest local update
|
||||||
if (latest.local == null) throw new DbException();
|
if (latest.local == null) throw new DbException();
|
||||||
@@ -242,7 +241,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeClientVersions(Transaction txn,
|
private void storeClientVersions(Transaction txn,
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
|
||||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
|
||||||
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
|
|
||||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||||
|
|||||||
@@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// Check whether the contact is in the DB (which it's not)
|
||||||
exactly(19).of(database).startTransaction();
|
exactly(18).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(19).of(database).containsContact(txn, contactId);
|
exactly(18).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(19).of(database).abortTransaction(txn);
|
exactly(18).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
@@ -349,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.getContact(transaction, contactId));
|
db.getContact(transaction, contactId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -357,15 +357,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.getUnackedMessageBytesToSend(transaction, contactId));
|
|
||||||
fail();
|
|
||||||
} catch (NoSuchContactException expected) {
|
|
||||||
// Expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
db.transaction(true, transaction ->
|
|
||||||
db.getMessageStatus(transaction, contactId, groupId));
|
db.getMessageStatus(transaction, contactId, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -373,7 +365,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.getMessageStatus(transaction, contactId, messageId));
|
db.getMessageStatus(transaction, contactId, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -381,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.getGroupVisibility(transaction, contactId, groupId));
|
db.getGroupVisibility(transaction, contactId, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -389,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.getSyncVersions(transaction, contactId));
|
db.getSyncVersions(transaction, contactId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singleton;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
@@ -223,13 +222,18 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The contact has not seen the message, so it should be sendable
|
// The contact has not seen the message, so it should be sendable
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
Collection<MessageId> ids =
|
||||||
assertOneMessageToSendLazily(db, txn);
|
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// Changing the status to seen = true should make the message unsendable
|
// Changing the status to seen = true should make the message unsendable
|
||||||
db.raiseSeenFlag(txn, contactId, messageId);
|
db.raiseSeenFlag(txn, contactId, messageId);
|
||||||
assertNothingToSendEagerly(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendLazily(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -249,23 +253,32 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, UNKNOWN, true, false, null);
|
db.addMessage(txn, message, UNKNOWN, true, false, null);
|
||||||
|
|
||||||
// The message has not been validated, so it should not be sendable
|
// The message has not been validated, so it should not be sendable
|
||||||
assertNothingToSendLazily(db, txn);
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
assertNothingToSendEagerly(db, txn);
|
ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Marking the message delivered should make it sendable
|
// Marking the message delivered should make it sendable
|
||||||
db.setMessageState(txn, messageId, DELIVERED);
|
db.setMessageState(txn, messageId, DELIVERED);
|
||||||
assertOneMessageToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// Marking the message invalid should make it unsendable
|
// Marking the message invalid should make it unsendable
|
||||||
db.setMessageState(txn, messageId, INVALID);
|
db.setMessageState(txn, messageId, INVALID);
|
||||||
assertNothingToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendEagerly(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Marking the message pending should make it unsendable
|
// Marking the message pending should make it unsendable
|
||||||
db.setMessageState(txn, messageId, PENDING);
|
db.setMessageState(txn, messageId, PENDING);
|
||||||
assertNothingToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendEagerly(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -284,28 +297,39 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The group is invisible, so the message should not be sendable
|
// The group is invisible, so the message should not be sendable
|
||||||
assertNothingToSendLazily(db, txn);
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
assertNothingToSendEagerly(db, txn);
|
ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Making the group visible should not make the message sendable
|
// Making the group visible should not make the message sendable
|
||||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||||
assertNothingToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendEagerly(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Sharing the group should make the message sendable
|
// Sharing the group should make the message sendable
|
||||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertOneMessageToSendLazily(db, txn);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// Unsharing the group should make the message unsendable
|
// Unsharing the group should make the message unsendable
|
||||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||||
assertNothingToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendEagerly(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Making the group invisible should make the message unsendable
|
// Making the group invisible should make the message unsendable
|
||||||
db.removeGroupVisibility(txn, contactId, groupId);
|
db.removeGroupVisibility(txn, contactId, groupId);
|
||||||
assertNothingToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendEagerly(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -325,13 +349,18 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, false, false, null);
|
db.addMessage(txn, message, DELIVERED, false, false, null);
|
||||||
|
|
||||||
// The message is not shared, so it should not be sendable
|
// The message is not shared, so it should not be sendable
|
||||||
assertNothingToSendLazily(db, txn);
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
assertNothingToSendEagerly(db, txn);
|
ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Sharing the message should make it sendable
|
// Sharing the message should make it sendable
|
||||||
db.setMessageShared(txn, messageId, true);
|
db.setMessageShared(txn, messageId, true);
|
||||||
assertOneMessageToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -351,13 +380,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The message is sendable, but too large to send
|
// The message is sendable, but too large to send
|
||||||
assertOneMessageToSendLazily(db, txn);
|
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
||||||
MAX_LATENCY);
|
MAX_LATENCY);
|
||||||
assertTrue(ids.isEmpty());
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// The message is just the right size to send
|
// The message is just the right size to send
|
||||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
||||||
MAX_LATENCY);
|
MAX_LATENCY);
|
||||||
@@ -379,12 +405,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||||
|
|
||||||
// Initially there should be nothing to send
|
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
|
|
||||||
// Add some messages to ack
|
// Add some messages to ack
|
||||||
Message message1 = getMessage(groupId);
|
Message message1 = getMessage(groupId);
|
||||||
MessageId messageId1 = message1.getId();
|
MessageId messageId1 = message1.getId();
|
||||||
@@ -392,10 +412,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
@@ -403,10 +419,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||||
|
|
||||||
// Both message IDs should have been removed
|
// Both message IDs should have been removed
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
||||||
contactId, 1234));
|
contactId, 1234));
|
||||||
|
|
||||||
@@ -415,10 +427,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.raiseAckFlag(txn, contactId, messageId1);
|
db.raiseAckFlag(txn, contactId, messageId1);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
@@ -439,25 +447,22 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The message should be sendable via lazy or eager retransmission
|
// Retrieve the message from the database and mark it as sent
|
||||||
assertOneMessageToSendLazily(db, txn);
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
// Mark the message as sent
|
|
||||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||||
|
|
||||||
// The message should no longer be sendable via lazy retransmission,
|
// The message should no longer be sendable
|
||||||
// but it should still be sendable via eager retransmission
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendLazily(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
|
||||||
|
|
||||||
// Mark the message as acked
|
// Pretend that the message was acked
|
||||||
db.raiseSeenFlag(txn, contactId, messageId);
|
db.raiseSeenFlag(txn, contactId, messageId);
|
||||||
|
|
||||||
// The message still should not be sendable via lazy or eager
|
// The message still should not be sendable
|
||||||
// retransmission
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendLazily(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
assertNothingToSendEagerly(db, txn);
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -671,9 +676,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
// Initially there should be no transport keys in the database
|
// Initially there should be no transport keys in the database
|
||||||
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
|
|
||||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
|
|
||||||
|
|
||||||
// Add the contact, the transport and the transport keys
|
// Add the contact, the transport and the transport keys
|
||||||
db.addIdentity(txn, identity);
|
db.addIdentity(txn, identity);
|
||||||
@@ -684,7 +687,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
||||||
|
|
||||||
// Retrieve the transport keys
|
// Retrieve the transport keys
|
||||||
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
|
|
||||||
Collection<TransportKeySet> allKeys =
|
Collection<TransportKeySet> allKeys =
|
||||||
db.getTransportKeys(txn, transportId);
|
db.getTransportKeys(txn, transportId);
|
||||||
assertEquals(2, allKeys.size());
|
assertEquals(2, allKeys.size());
|
||||||
@@ -697,8 +699,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertKeysEquals(keys1, ks.getKeys());
|
assertKeysEquals(keys1, ks.getKeys());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(singletonMap(contactId, singletonList(transportId)),
|
|
||||||
db.getTransportsWithKeys(txn));
|
|
||||||
|
|
||||||
// Update the transport keys
|
// Update the transport keys
|
||||||
TransportKeys updated = createTransportKeys(timePeriod + 1, active);
|
TransportKeys updated = createTransportKeys(timePeriod + 1, active);
|
||||||
@@ -710,7 +710,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
null, updated1));
|
null, updated1));
|
||||||
|
|
||||||
// Retrieve the transport keys again
|
// Retrieve the transport keys again
|
||||||
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
|
|
||||||
allKeys = db.getTransportKeys(txn, transportId);
|
allKeys = db.getTransportKeys(txn, transportId);
|
||||||
assertEquals(2, allKeys.size());
|
assertEquals(2, allKeys.size());
|
||||||
for (TransportKeySet ks : allKeys) {
|
for (TransportKeySet ks : allKeys) {
|
||||||
@@ -722,14 +721,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertKeysEquals(updated1, ks.getKeys());
|
assertKeysEquals(updated1, ks.getKeys());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(singletonMap(contactId, singletonList(transportId)),
|
|
||||||
db.getTransportsWithKeys(txn));
|
|
||||||
|
|
||||||
// Removing the contact should remove the transport keys
|
// Removing the contact should remove the transport keys
|
||||||
db.removeContact(txn, contactId);
|
db.removeContact(txn, contactId);
|
||||||
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
|
|
||||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -1930,8 +1925,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
|
|
||||||
// The message should be sendable
|
// The message should be sendable
|
||||||
assertOneMessageToSendLazily(db, txn);
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// The message should be available
|
// The message should be available
|
||||||
Message m = db.getMessage(txn, messageId);
|
Message m = db.getMessage(txn, messageId);
|
||||||
@@ -1947,8 +1945,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
|
|
||||||
// The message should not be sendable
|
// The message should not be sendable
|
||||||
assertNothingToSendLazily(db, txn);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertNothingToSendEagerly(db, txn);
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Requesting the message should throw an exception
|
// Requesting the message should throw an exception
|
||||||
try {
|
try {
|
||||||
@@ -2577,50 +2577,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
deleteTestDirectory(testDir);
|
deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNothingToSendLazily(Database<Connection> db,
|
|
||||||
Connection txn) throws Exception {
|
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
Collection<MessageId> ids =
|
|
||||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertOneMessageToSendLazily(Database<Connection> db,
|
|
||||||
Connection txn) throws Exception {
|
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
Collection<MessageId> ids =
|
|
||||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertNothingToSendEagerly(Database<Connection> db,
|
|
||||||
Connection txn) throws Exception {
|
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
Map<MessageId, Integer> unacked =
|
|
||||||
db.getUnackedMessagesToSend(txn, contactId);
|
|
||||||
assertTrue(unacked.isEmpty());
|
|
||||||
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertOneMessageToSendEagerly(Database<Connection> db,
|
|
||||||
Connection txn) throws Exception {
|
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
Map<MessageId, Integer> unacked =
|
|
||||||
db.getUnackedMessagesToSend(txn, contactId);
|
|
||||||
assertEquals(singleton(messageId), unacked.keySet());
|
|
||||||
assertEquals(message.getRawLength(), unacked.get(messageId).intValue());
|
|
||||||
assertEquals(message.getRawLength(),
|
|
||||||
db.getUnackedMessageBytesToSend(txn, contactId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StoppedClock implements Clock {
|
private static class StoppedClock implements Clock {
|
||||||
|
|
||||||
private final long time;
|
private final long time;
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,7 +43,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
|
|||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -231,8 +230,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TransportPropertyManagerImpl t = createInstance();
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(t.incomingMessage(txn, message, meta));
|
||||||
t.incomingMessage(txn, message, meta));
|
|
||||||
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +269,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TransportPropertyManagerImpl t = createInstance();
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(t.incomingMessage(txn, message, meta));
|
||||||
t.incomingMessage(txn, message, meta));
|
|
||||||
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,8 +308,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TransportPropertyManagerImpl t = createInstance();
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(t.incomingMessage(txn, message, meta));
|
||||||
t.incomingMessage(txn, message, meta));
|
|
||||||
assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,9 @@ import org.briarproject.bramble.test.DbExpectations;
|
|||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -44,19 +39,14 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
private final Executor dbExecutor = new ImmediateExecutor();
|
private final Executor dbExecutor = new ImmediateExecutor();
|
||||||
private final ContactId contactId = getContactId();
|
private final ContactId contactId = getContactId();
|
||||||
private final TransportId transportId = getTransportId();
|
private final TransportId transportId = getTransportId();
|
||||||
private final Ack ack =
|
private final Message message = getMessage(new GroupId(getRandomId()));
|
||||||
new Ack(singletonList(new MessageId(getRandomId())));
|
private final MessageId messageId = message.getId();
|
||||||
private final Message message = getMessage(new GroupId(getRandomId()),
|
|
||||||
MAX_MESSAGE_BODY_LENGTH);
|
|
||||||
private final Message message1 = getMessage(new GroupId(getRandomId()),
|
|
||||||
MAX_MESSAGE_BODY_LENGTH);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNothingToSend() throws Exception {
|
public void testNothingToSend() throws Exception {
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
false, streamWriter, recordWriter);
|
streamWriter, recordWriter);
|
||||||
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
Transaction noMsgTxn = new Transaction(null, false);
|
Transaction noMsgTxn = new Transaction(null, false);
|
||||||
|
|
||||||
@@ -73,8 +63,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
// No messages to send
|
// No messages to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noMsgTxn));
|
withNullableDbCallable(noMsgTxn));
|
||||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
with(any(int.class)), with(MAX_LATENCY));
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
// Send the end of stream marker
|
// Send the end of stream marker
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
@@ -85,45 +75,12 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
session.run();
|
session.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNothingToSendEagerly() throws Exception {
|
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
|
||||||
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
|
@Test
|
||||||
public void testSomethingToSend() throws Exception {
|
public void testSomethingToSend() throws Exception {
|
||||||
|
Ack ack = new Ack(singletonList(messageId));
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
false, streamWriter, recordWriter);
|
streamWriter, recordWriter);
|
||||||
|
|
||||||
Transaction ackTxn = new Transaction(null, false);
|
Transaction ackTxn = new Transaction(null, false);
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
Transaction msgTxn = new Transaction(null, false);
|
Transaction msgTxn = new Transaction(null, false);
|
||||||
@@ -143,8 +100,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
// One message to send
|
// One message to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(msgTxn));
|
withNullableDbCallable(msgTxn));
|
||||||
oneOf(db).generateBatch(msgTxn, contactId,
|
oneOf(db).generateBatch(with(msgTxn), with(contactId),
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
with(any(int.class)), with(MAX_LATENCY));
|
||||||
will(returnValue(singletonList(message)));
|
will(returnValue(singletonList(message)));
|
||||||
oneOf(recordWriter).writeMessage(message);
|
oneOf(recordWriter).writeMessage(message);
|
||||||
// No more acks
|
// No more acks
|
||||||
@@ -155,8 +112,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
// No more messages
|
// No more messages
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noMsgTxn));
|
withNullableDbCallable(noMsgTxn));
|
||||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
with(any(int.class)), with(MAX_LATENCY));
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
// Send the end of stream marker
|
// Send the end of stream marker
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
@@ -166,63 +123,4 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
session.run();
|
session.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSomethingToSendEagerly() throws Exception {
|
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
|
||||||
true, streamWriter, recordWriter);
|
|
||||||
|
|
||||||
Map<MessageId, Integer> unacked = new LinkedHashMap<>();
|
|
||||||
unacked.put(message.getId(), message.getRawLength());
|
|
||||||
unacked.put(message1.getId(), message1.getRawLength());
|
|
||||||
|
|
||||||
Transaction ackTxn = new Transaction(null, false);
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
|
||||||
Transaction idsTxn = new Transaction(null, true);
|
|
||||||
Transaction msgTxn = new Transaction(null, false);
|
|
||||||
Transaction msgTxn1 = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
// Add listener
|
|
||||||
oneOf(eventBus).addListener(session);
|
|
||||||
// Send the protocol versions
|
|
||||||
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
|
||||||
// One ack to send
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(ackTxn));
|
|
||||||
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(ack));
|
|
||||||
oneOf(recordWriter).writeAck(ack);
|
|
||||||
// No more acks
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(noAckTxn));
|
|
||||||
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(null));
|
|
||||||
// Two messages to send
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(idsTxn));
|
|
||||||
oneOf(db).getUnackedMessagesToSend(idsTxn, contactId);
|
|
||||||
will(returnValue(unacked));
|
|
||||||
// Send the first message
|
|
||||||
oneOf(db).transactionWithResult(with(false),
|
|
||||||
withDbCallable(msgTxn));
|
|
||||||
oneOf(db).generateBatch(msgTxn, contactId,
|
|
||||||
singletonList(message.getId()), MAX_LATENCY);
|
|
||||||
will(returnValue(singletonList(message)));
|
|
||||||
oneOf(recordWriter).writeMessage(message);
|
|
||||||
// Send the second message
|
|
||||||
oneOf(db).transactionWithResult(with(false),
|
|
||||||
withDbCallable(msgTxn1));
|
|
||||||
oneOf(db).generateBatch(msgTxn1, contactId,
|
|
||||||
singletonList(message1.getId()), MAX_LATENCY);
|
|
||||||
will(returnValue(singletonList(message1)));
|
|
||||||
oneOf(recordWriter).writeMessage(message1);
|
|
||||||
// Send the end of stream marker
|
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
|
||||||
// Remove listener
|
|
||||||
oneOf(eventBus).removeListener(session);
|
|
||||||
}});
|
|
||||||
|
|
||||||
session.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||||
@@ -113,7 +111,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the first message
|
// Deliver the first message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -169,7 +167,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(new Metadata()));
|
will(returnValue(new Metadata()));
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn, message, metadata);
|
oneOf(hook).incomingMessage(txn, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn, messageId);
|
oneOf(db).getMessageDependents(txn, messageId);
|
||||||
@@ -189,7 +187,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver the dependent
|
// Deliver the dependent
|
||||||
oneOf(hook).incomingMessage(txn1, message2, metadata);
|
oneOf(hook).incomingMessage(txn1, message2, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId2);
|
oneOf(db).getMessageDependents(txn1, messageId2);
|
||||||
@@ -249,7 +247,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_SHARE));
|
will(returnValue(true));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -369,7 +367,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -434,7 +432,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -604,7 +602,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// The message has two pending dependents: 1 and 2
|
// The message has two pending dependents: 1 and 2
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -624,7 +622,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 1
|
// Deliver message 1
|
||||||
oneOf(hook).incomingMessage(txn2, message1, metadata);
|
oneOf(hook).incomingMessage(txn2, message1, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
|
oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
|
||||||
// Message 1 has one pending dependent: 3
|
// Message 1 has one pending dependent: 3
|
||||||
oneOf(db).getMessageDependents(txn2, messageId1);
|
oneOf(db).getMessageDependents(txn2, messageId1);
|
||||||
@@ -644,7 +642,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 2
|
// Deliver message 2
|
||||||
oneOf(hook).incomingMessage(txn3, message2, metadata);
|
oneOf(hook).incomingMessage(txn3, message2, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
|
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
|
||||||
// Message 2 has one pending dependent: 3 (same dependent as 1)
|
// Message 2 has one pending dependent: 3 (same dependent as 1)
|
||||||
oneOf(db).getMessageDependents(txn3, messageId2);
|
oneOf(db).getMessageDependents(txn3, messageId2);
|
||||||
@@ -664,7 +662,6 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 3
|
// Deliver message 3
|
||||||
oneOf(hook).incomingMessage(txn4, message3, metadata);
|
oneOf(hook).incomingMessage(txn4, message3, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
|
||||||
oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
|
oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
|
||||||
// Message 3 has one pending dependent: 4
|
// Message 3 has one pending dependent: 4
|
||||||
oneOf(db).getMessageDependents(txn4, messageId3);
|
oneOf(db).getMessageDependents(txn4, messageId3);
|
||||||
@@ -688,7 +685,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 4
|
// Deliver message 4
|
||||||
oneOf(hook).incomingMessage(txn6, message4, metadata);
|
oneOf(hook).incomingMessage(txn6, message4, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
|
oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
|
||||||
// Message 4 has no pending dependents
|
// Message 4 has no pending dependents
|
||||||
oneOf(db).getMessageDependents(txn6, messageId4);
|
oneOf(db).getMessageDependents(txn6, messageId4);
|
||||||
@@ -720,7 +717,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class TestDuplexTransportConnection
|
|||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public TestDuplexTransportConnection(InputStream in, OutputStream out) {
|
public TestDuplexTransportConnection(InputStream in, OutputStream out) {
|
||||||
reader = new TestTransportConnectionReader(in);
|
reader = new TestTransportConnectionReader(in);
|
||||||
writer = new TestTransportConnectionWriter(out, false);
|
writer = new TestTransportConnectionWriter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,13 +15,10 @@ public class TestTransportConnectionWriter
|
|||||||
implements TransportConnectionWriter {
|
implements TransportConnectionWriter {
|
||||||
|
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
private final boolean lossyAndCheap;
|
|
||||||
private final CountDownLatch disposed = new CountDownLatch(1);
|
private final CountDownLatch disposed = new CountDownLatch(1);
|
||||||
|
|
||||||
public TestTransportConnectionWriter(OutputStream out,
|
public TestTransportConnectionWriter(OutputStream out) {
|
||||||
boolean lossyAndCheap) {
|
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.lossyAndCheap = lossyAndCheap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountDownLatch getDisposedLatch() {
|
public CountDownLatch getDisposedLatch() {
|
||||||
@@ -38,11 +35,6 @@ public class TestTransportConnectionWriter
|
|||||||
return 60_000;
|
return 60_000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return lossyAndCheap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() {
|
public OutputStream getOutputStream() {
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
@@ -72,7 +71,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final SecretKey rootKey = getSecretKey();
|
private final SecretKey rootKey = getSecretKey();
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
|
|
||||||
private KeyManagerImpl keyManager;
|
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor,
|
||||||
|
pluginConfig, transportKeyManagerFactory, transportCrypto);
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void testStartService() throws Exception {
|
public void testStartService() throws Exception {
|
||||||
@@ -83,25 +83,18 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
singletonList(pluginFactory);
|
singletonList(pluginFactory);
|
||||||
int maxLatency = 1337;
|
int maxLatency = 1337;
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
allowing(pluginConfig).getSimplexFactories();
|
oneOf(pluginConfig).getSimplexFactories();
|
||||||
will(returnValue(factories));
|
will(returnValue(factories));
|
||||||
allowing(pluginFactory).getId();
|
oneOf(pluginFactory).getId();
|
||||||
will(returnValue(transportId));
|
will(returnValue(transportId));
|
||||||
allowing(pluginFactory).getMaxLatency();
|
oneOf(pluginFactory).getMaxLatency();
|
||||||
will(returnValue(maxLatency));
|
will(returnValue(maxLatency));
|
||||||
allowing(pluginConfig).getDuplexFactories();
|
oneOf(db).addTransport(txn, transportId, maxLatency);
|
||||||
will(returnValue(emptyList()));
|
|
||||||
oneOf(transportKeyManagerFactory)
|
oneOf(transportKeyManagerFactory)
|
||||||
.createTransportKeyManager(transportId, maxLatency);
|
.createTransportKeyManager(transportId, maxLatency);
|
||||||
will(returnValue(transportKeyManager));
|
will(returnValue(transportKeyManager));
|
||||||
}});
|
oneOf(pluginConfig).getDuplexFactories();
|
||||||
|
|
||||||
keyManager = new KeyManagerImpl(db, executor,
|
|
||||||
pluginConfig, transportCrypto, transportKeyManagerFactory);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
oneOf(db).addTransport(txn, transportId, maxLatency);
|
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(transportKeyManager).start(txn);
|
oneOf(transportKeyManager).start(txn);
|
||||||
}});
|
}});
|
||||||
@@ -242,37 +235,4 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
keyManager.eventOccurred(event);
|
keyManager.eventOccurred(event);
|
||||||
executor.runUntilIdle();
|
executor.runUntilIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddMultipleRotationKeySets() throws Exception {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
boolean alice = random.nextBoolean();
|
|
||||||
boolean active = random.nextBoolean();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(transportKeyManager).addRotationKeys(txn, contactId,
|
|
||||||
rootKey, timestamp, alice, active);
|
|
||||||
will(returnValue(keySetId));
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertEquals(singletonMap(transportId, keySetId),
|
|
||||||
keyManager.addRotationKeys(txn, contactId, rootKey, timestamp,
|
|
||||||
alice, active));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddSingleRotationKeySet() throws Exception {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
boolean alice = random.nextBoolean();
|
|
||||||
boolean active = random.nextBoolean();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(transportKeyManager).addRotationKeys(txn, contactId,
|
|
||||||
rootKey, timestamp, alice, active);
|
|
||||||
will(returnValue(keySetId));
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertEquals(keySetId, keyManager.addRotationKeys(txn, contactId,
|
|
||||||
transportId, rootKey, timestamp, alice, active));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,554 +0,0 @@
|
|||||||
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,7 +31,6 @@ import static java.util.Collections.singletonMap;
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
@@ -42,6 +41,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
|||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -419,8 +419,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -465,8 +464,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -498,8 +496,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -582,8 +579,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -653,8 +649,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,81 +1,61 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
package org.briarproject.bramble.plugin;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FeatureFlags;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||||
|
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
|
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||||
|
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||||
|
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||||
|
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class RemovableDriveIntegrationTestModule {
|
public class DesktopPluginModule extends PluginModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth,
|
||||||
PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
|
ModemPluginFactory modem, LanTcpPluginFactory lan,
|
||||||
|
WanTcpPluginFactory wan) {
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
PluginConfig pluginConfig = new PluginConfig() {
|
PluginConfig pluginConfig = new PluginConfig() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||||
return emptyList();
|
return asList(bluetooth, modem, lan, wan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||||
return singletonList(drive);
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldPoll() {
|
public boolean shouldPoll() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
||||||
return emptyMap();
|
// Prefer LAN to Bluetooth
|
||||||
|
return singletonMap(BluetoothConstants.ID,
|
||||||
|
singletonList(LanTcpConstants.ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
return pluginConfig;
|
return pluginConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
FeatureFlags provideFeatureFlags() {
|
|
||||||
return new FeatureFlags() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnableImageAttachments() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnableProfilePictures() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnableDisappearingMessages() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnableConnectViaBluetooth() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final File torDirectory;
|
private final File torDirectory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
public UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@IoExecutor Executor wakefulIoExecutor,
|
@IoExecutor Executor wakefulIoExecutor,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10303
|
versionCode 10304
|
||||||
versionName "1.3.3"
|
versionName "1.3.4"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -121,6 +121,7 @@ dependencies {
|
|||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
||||||
}
|
}
|
||||||
|
implementation "com.github.zjupure:webpdecoder:2.0.$glideVersion"
|
||||||
|
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||||
|
|||||||
@@ -104,8 +104,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.account.SetupActivity"
|
android:name="org.briarproject.briar.android.account.SetupActivity"
|
||||||
android:label="@string/setup_title"
|
android:label="@string/setup_title" />
|
||||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
|
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
|
|||||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
|
||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.bramble.system.ClockModule;
|
import org.briarproject.bramble.system.ClockModule;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
@@ -84,8 +83,7 @@ import dagger.Component;
|
|||||||
AppModule.class,
|
AppModule.class,
|
||||||
AttachmentModule.class,
|
AttachmentModule.class,
|
||||||
ClockModule.class,
|
ClockModule.class,
|
||||||
MediaModule.class,
|
MediaModule.class
|
||||||
RemovableDriveModule.class
|
|
||||||
})
|
})
|
||||||
public interface AndroidComponent
|
public interface AndroidComponent
|
||||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
|||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.reporting.DevConfig;
|
import org.briarproject.bramble.api.reporting.DevConfig;
|
||||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
|
|
||||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
|
||||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
|
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
@@ -69,6 +67,7 @@ import dagger.Provides;
|
|||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||||
@@ -93,7 +92,6 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
GroupListModule.class,
|
GroupListModule.class,
|
||||||
GroupConversationModule.class,
|
GroupConversationModule.class,
|
||||||
SharingModule.class,
|
SharingModule.class,
|
||||||
RemovableDriveModule.class
|
|
||||||
})
|
})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
|
|
||||||
@@ -151,10 +149,8 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
|
||||||
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
||||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) {
|
||||||
AndroidRemovableDrivePluginFactory drive) {
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
PluginConfig pluginConfig = new PluginConfig() {
|
PluginConfig pluginConfig = new PluginConfig() {
|
||||||
|
|
||||||
@@ -165,7 +161,7 @@ public class AppModule {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||||
return singletonList(drive);
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class DozeFragment extends SetupFragment
|
|||||||
private DozeView dozeView;
|
private DozeView dozeView;
|
||||||
private HuaweiProtectedAppsView huaweiProtectedAppsView;
|
private HuaweiProtectedAppsView huaweiProtectedAppsView;
|
||||||
private HuaweiAppLaunchView huaweiAppLaunchView;
|
private HuaweiAppLaunchView huaweiAppLaunchView;
|
||||||
|
private XiaomiView xiaomiView;
|
||||||
private Button next;
|
private Button next;
|
||||||
private boolean secondAttempt = false;
|
private boolean secondAttempt = false;
|
||||||
|
|
||||||
@@ -53,6 +54,8 @@ public class DozeFragment extends SetupFragment
|
|||||||
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
|
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
|
||||||
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
|
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
|
||||||
huaweiAppLaunchView.setOnCheckedChangedListener(this);
|
huaweiAppLaunchView.setOnCheckedChangedListener(this);
|
||||||
|
xiaomiView = v.findViewById(R.id.xiaomiView);
|
||||||
|
xiaomiView.setOnCheckedChangedListener(this);
|
||||||
next = v.findViewById(R.id.next);
|
next = v.findViewById(R.id.next);
|
||||||
ProgressBar progressBar = v.findViewById(R.id.progress);
|
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||||
|
|
||||||
@@ -98,7 +101,8 @@ public class DozeFragment extends SetupFragment
|
|||||||
public void onCheckedChanged() {
|
public void onCheckedChanged() {
|
||||||
next.setEnabled(dozeView.isChecked() &&
|
next.setEnabled(dozeView.isChecked() &&
|
||||||
huaweiProtectedAppsView.isChecked() &&
|
huaweiProtectedAppsView.isChecked() &&
|
||||||
huaweiAppLaunchView.isChecked());
|
huaweiAppLaunchView.isChecked() &&
|
||||||
|
xiaomiView.isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
@SuppressLint("BatteryLife")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class DozeHelperImpl implements DozeHelper {
|
|||||||
Context appContext = context.getApplicationContext();
|
Context appContext = context.getApplicationContext();
|
||||||
return needsDozeWhitelisting(appContext) ||
|
return needsDozeWhitelisting(appContext) ||
|
||||||
HuaweiProtectedAppsView.needsToBeShown(appContext) ||
|
HuaweiProtectedAppsView.needsToBeShown(appContext) ||
|
||||||
HuaweiAppLaunchView.needsToBeShown(appContext);
|
HuaweiAppLaunchView.needsToBeShown(appContext) ||
|
||||||
|
XiaomiView.isXiaomiOrRedmiDevice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class SetPasswordFragment extends SetupFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
IBinder token = passwordEntry.getWindowToken();
|
IBinder token = passwordEntry.getWindowToken();
|
||||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||||
viewModel.setPassword(passwordEntry.getText().toString());
|
viewModel.setPassword(passwordEntry.getText().toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import static org.briarproject.briar.android.account.SetupViewModel.State.CREATE
|
|||||||
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
|
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
|
||||||
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
|
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
|
||||||
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
|
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.setInputStateAlwaysVisible;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.setInputStateHidden;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -55,10 +57,13 @@ public class SetupActivity extends BaseActivity
|
|||||||
|
|
||||||
private void onStateChanged(SetupViewModel.State state) {
|
private void onStateChanged(SetupViewModel.State state) {
|
||||||
if (state == AUTHOR_NAME) {
|
if (state == AUTHOR_NAME) {
|
||||||
|
setInputStateAlwaysVisible(this);
|
||||||
showInitialFragment(AuthorNameFragment.newInstance());
|
showInitialFragment(AuthorNameFragment.newInstance());
|
||||||
} else if (state == SET_PASSWORD) {
|
} else if (state == SET_PASSWORD) {
|
||||||
|
setInputStateAlwaysVisible(this);
|
||||||
showPasswordFragment();
|
showPasswordFragment();
|
||||||
} else if (state == DOZE) {
|
} else if (state == DOZE) {
|
||||||
|
setInputStateHidden(this);
|
||||||
showDozeFragment();
|
showDozeFragment();
|
||||||
} else if (state == CREATED || state == FAILED) {
|
} else if (state == CREATED || state == FAILED) {
|
||||||
// TODO: Show an error if failed
|
// TODO: Show an error if failed
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package org.briarproject.briar.android.account;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
|
import static android.os.Build.BRAND;
|
||||||
|
import static org.briarproject.bramble.util.AndroidUtils.getSystemProperty;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
|
class XiaomiView extends PowerView {
|
||||||
|
|
||||||
|
public XiaomiView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XiaomiView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XiaomiView(Context context, @Nullable AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
setText(R.string.setup_xiaomi_text);
|
||||||
|
setButtonText(R.string.setup_xiaomi_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsToBeShown() {
|
||||||
|
return isXiaomiOrRedmiDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isXiaomiOrRedmiDevice() {
|
||||||
|
return "Xiaomi".equalsIgnoreCase(BRAND) ||
|
||||||
|
"Redmi".equalsIgnoreCase(BRAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@StringRes
|
||||||
|
protected int getHelpText() {
|
||||||
|
return R.string.setup_xiaomi_help;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onButtonClick() {
|
||||||
|
int bodyRes = isMiuiTenOrLater()
|
||||||
|
? R.string.setup_xiaomi_dialog_body_new
|
||||||
|
: R.string.setup_xiaomi_dialog_body_old;
|
||||||
|
showOnboardingDialog(getContext(), getContext().getString(bodyRes));
|
||||||
|
setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMiuiTenOrLater() {
|
||||||
|
String version = getSystemProperty("ro.miui.ui.version.name");
|
||||||
|
if (isNullOrEmpty(version)) return false;
|
||||||
|
version = version.replaceAll("[^\\d]", "");
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(version) >= 10;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,12 +102,10 @@ class AttachmentCreationTask {
|
|||||||
}
|
}
|
||||||
InputStream is = contentResolver.openInputStream(uri);
|
InputStream is = contentResolver.openInputStream(uri);
|
||||||
if (is == null) throw new IOException();
|
if (is == null) throw new IOException();
|
||||||
is = imageCompressor
|
is = imageCompressor.compressImage(is, contentType);
|
||||||
.compressImage(is, contentType);
|
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
AttachmentHeader h = messagingManager
|
AttachmentHeader h = messagingManager.addLocalAttachment(groupId,
|
||||||
.addLocalAttachment(groupId, timestamp,
|
timestamp, ImageCompressor.MIME_TYPE, is);
|
||||||
ImageCompressor.MIME_TYPE, is);
|
|
||||||
tryToClose(is, LOG, WARNING);
|
tryToClose(is, LOG, WARNING);
|
||||||
logDuration(LOG, "Storing attachment", start);
|
logDuration(LOG, "Storing attachment", start);
|
||||||
return h;
|
return h;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public interface ImageCompressor {
|
|||||||
/**
|
/**
|
||||||
* The MIME type of compressed images
|
* The MIME type of compressed images
|
||||||
*/
|
*/
|
||||||
String MIME_TYPE = "image/jpeg";
|
String MIME_TYPE = "image/webp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an image from {@code is}, compress it and return an InputStream
|
* Load an image from {@code is}, compress it and return an InputStream
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.graphics.Bitmap.CompressFormat.JPEG;
|
import static android.graphics.Bitmap.CompressFormat.WEBP;
|
||||||
import static android.graphics.BitmapFactory.decodeStream;
|
import static android.graphics.BitmapFactory.decodeStream;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -50,7 +50,7 @@ class ImageCompressorImpl implements ImageCompressor {
|
|||||||
public InputStream compressImage(Bitmap bitmap) throws IOException {
|
public InputStream compressImage(Bitmap bitmap) throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
for (int quality = 100; quality >= 0; quality -= 10) {
|
for (int quality = 100; quality >= 0; quality -= 10) {
|
||||||
if (!bitmap.compress(JPEG, quality, out))
|
if (!bitmap.compress(WEBP, quality, out))
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
if (out.size() <= MAX_IMAGE_SIZE) {
|
if (out.size() <= MAX_IMAGE_SIZE) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.briarproject.briar.android.attachment.media;
|
|||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.bumptech.glide.integration.webp.WebpBitmapFactory;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -25,6 +27,10 @@ class ImageHelperImpl implements ImageHelper {
|
|||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeStream(is, null, options);
|
BitmapFactory.decodeStream(is, null, options);
|
||||||
|
if (options.outWidth < 1 || options.outHeight < 1) {
|
||||||
|
// BitmapFactory doesn't fully support WebP on API < 17
|
||||||
|
WebpBitmapFactory.decodeStream(is, null, options);
|
||||||
|
}
|
||||||
String mimeType = options.outMimeType;
|
String mimeType = options.outMimeType;
|
||||||
if (mimeType == null) mimeType = "";
|
if (mimeType == null) mimeType = "";
|
||||||
return new DecodeResult(options.outWidth, options.outHeight,
|
return new DecodeResult(options.outWidth, options.outHeight,
|
||||||
@@ -34,6 +40,7 @@ class ImageHelperImpl implements ImageHelper {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public String getExtensionFromMimeType(String mimeType) {
|
public String getExtensionFromMimeType(String mimeType) {
|
||||||
|
if ("image/webp".equals(mimeType)) return "webp";
|
||||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static androidx.exifinterface.media.ExifInterface.TAG_ORIENTATION;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ImageSizeCalculatorImpl implements ImageSizeCalculator {
|
class ImageSizeCalculatorImpl implements ImageSizeCalculator {
|
||||||
@@ -61,6 +62,10 @@ class ImageSizeCalculatorImpl implements ImageSizeCalculator {
|
|||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!size.hasError() && isNullOrEmpty(size.getMimeType())) {
|
||||||
|
LOG.info("Could not determine content type, using supplied type");
|
||||||
|
size = new Size(size.getWidth(), size.getHeight(), contentType);
|
||||||
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,9 +106,11 @@ public class FeedFragment extends BaseFragment
|
|||||||
adapter.submitList(update.getItems(), () -> {
|
adapter.submitList(update.getItems(), () -> {
|
||||||
Boolean wasLocal = update.getPostAddedWasLocal();
|
Boolean wasLocal = update.getPostAddedWasLocal();
|
||||||
if (wasLocal != null && wasLocal) {
|
if (wasLocal != null && wasLocal) {
|
||||||
showSnackBar(R.string.blogs_blog_post_created);
|
showSnackBar(R.string.blogs_blog_post_created, true);
|
||||||
|
// automatically scroll to our new post
|
||||||
|
list.smoothScrollToPosition(0);
|
||||||
} else if (wasLocal != null) {
|
} else if (wasLocal != null) {
|
||||||
showSnackBar(R.string.blogs_blog_post_received);
|
showSnackBar(R.string.blogs_blog_post_received, false);
|
||||||
}
|
}
|
||||||
viewModel.resetLocalUpdate();
|
viewModel.resetLocalUpdate();
|
||||||
list.showData();
|
list.showData();
|
||||||
@@ -170,12 +172,12 @@ public class FeedFragment extends BaseFragment
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSnackBar(int stringRes) {
|
private void showSnackBar(int stringRes, boolean isLocal) {
|
||||||
int firstVisible =
|
int firstVisible =
|
||||||
layoutManager.findFirstCompletelyVisibleItemPosition();
|
layoutManager.findFirstCompletelyVisibleItemPosition();
|
||||||
int lastVisible = layoutManager.findLastCompletelyVisibleItemPosition();
|
int lastVisible = layoutManager.findLastCompletelyVisibleItemPosition();
|
||||||
int count = adapter.getItemCount();
|
int count = adapter.getItemCount();
|
||||||
boolean scroll = count > (lastVisible - firstVisible + 1);
|
boolean scroll = !isLocal && count > (lastVisible - firstVisible + 1);
|
||||||
|
|
||||||
BriarSnackbarBuilder sb = new BriarSnackbarBuilder();
|
BriarSnackbarBuilder sb = new BriarSnackbarBuilder();
|
||||||
if (scroll) {
|
if (scroll) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user