Compare commits

..

24 Commits

Author SHA1 Message Date
akwizgran
7345d5c22c Add WebP library for API 16. 2021-05-24 15:09:19 +01:00
akwizgran
b4a2725f03 Add workarounds for missing MIME type information on API 16. 2021-05-24 14:56:50 +01:00
akwizgran
a5972e26fe Use WebP for compressing images. 2021-05-21 12:43:50 +01:00
akwizgran
aabb0bfb4a Allow WebP images to be used as attachments and avatars. 2021-05-21 12:43:19 +01:00
akwizgran
76b7e6fecf Bump version numbers for 1.3.4 release. 2021-05-19 10:47:08 +01:00
akwizgran
980940a7cf Update translations. 2021-05-19 10:46:21 +01:00
akwizgran
9a021daae8 Merge branch '2043-blog-scrolling' into 'master'
Automatically scroll to our own blog posts

Closes #2043

See merge request briar/briar!1462
2021-05-19 09:35:10 +00:00
Torsten Grote
cc9904a454 Automatically scroll to our own blog posts 2021-05-18 15:39:22 -03:00
Torsten Grote
b0faab9395 Merge branch 'remove-failed-bridges' into 'master'
Remove three failed bridges

See merge request briar/briar!1460
2021-05-17 17:37:58 +00:00
akwizgran
f1198b47fd Remove three failed bridges. 2021-05-17 16:36:00 +01:00
akwizgran
37f2ab555f Update translations. 2021-05-17 16:26:22 +01:00
akwizgran
61ca2a391b Merge branch 'create-testdata-zero-values' into 'master'
Add button to zero values when creating test data

See merge request briar/briar!1447
2021-05-10 12:05:26 +00:00
akwizgran
88537b9323 Merge branch '2032-connect-via-bt-crash' into 'master'
Remove duplicate code due to merge mistake

Closes #2032

See merge request briar/briar!1456
2021-05-10 11:55:38 +00:00
Daniel Lublin
e6c004e8f6 Add button to zero values when creating test data 2021-05-10 13:46:49 +02:00
akwizgran
dc7290dab7 Remove duplicate code due to merge mistake. 2021-05-10 12:29:28 +01:00
akwizgran
9dff8bd64a Merge branch '2005-connect-via-bt-error' into 'master'
Connect via Bluetooth: Wait before showing an error Toast

See merge request briar/briar!1446
2021-05-06 13:14:32 +00:00
akwizgran
35c2f60129 Merge branch 'master' into '2005-connect-via-bt-error'
# Conflicts:
#   briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java
2021-05-06 13:03:46 +00:00
akwizgran
471a2372c8 Merge branch '2027-pause-polling-when-doing-connect-via-bt' into 'master'
Disable polling while doing connect-via-BT

Closes #2027

See merge request briar/briar!1450
2021-05-06 12:59:38 +00:00
Daniel Lublin
fba028db03 Disable polling while doing connect-via-BT 2021-05-06 11:13:35 +02:00
Torsten Grote
c647c52638 Merge branch '2009-xiaomi-setup-screen' into 'master'
Show instructions for locking Briar to the Xiaomi recent apps list

Closes #2009

See merge request briar/briar!1451
2021-05-05 16:39:55 +00:00
akwizgran
cad5edcf86 Move input state methods to UiUtils. 2021-05-05 16:57:25 +01:00
akwizgran
38f70bb6be Add full stop. 2021-05-05 13:02:56 +01:00
akwizgran
33bdc81b3e Show instructions for locking Briar to the Xiaomi recent apps list. 2021-05-05 12:37:23 +01:00
Torsten Grote
552b9ef21a Wait for an incoming connection before showing an error Toast 2021-04-30 16:01:54 -03:00
157 changed files with 920 additions and 4463 deletions

View File

@@ -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"

View File

@@ -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,

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}
} }
} }

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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.
*/ */

View File

@@ -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 {
} }

View File

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

View File

@@ -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.
*/ */

View File

@@ -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();

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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.

View File

@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
PriorityHandler handler); PriorityHandler handler);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, 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,

View File

@@ -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;
}
} }

View File

@@ -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
}
} }

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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());

View File

@@ -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

View File

@@ -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);
} }
} }

View File

@@ -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.
*/ */

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

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

View File

@@ -36,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;

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
});
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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());
}
} }
} }

View File

@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, 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

View File

@@ -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;
}
}
} }

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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,

View File

@@ -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

View File

@@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(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) {

View File

@@ -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;

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
} }

View File

@@ -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();
}
} }

View File

@@ -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);

View File

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

View File

@@ -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;

View File

@@ -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));
}
} }

View File

@@ -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));
}});
}
}

View File

@@ -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

View File

@@ -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;
}
};
}
} }

View File

@@ -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,

View File

@@ -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"

View File

@@ -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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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")

View File

@@ -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();
} }
} }

View File

@@ -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());
} }

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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