mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-24 16:49:55 +01:00
Compare commits
1 Commits
2085-trans
...
removable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3692b2a97 |
@@ -32,31 +32,28 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once for each incoming message that passes validation.
|
* Called once for each incoming message that passes validation.
|
||||||
* <p>
|
|
||||||
* If an unexpected exception occurs while handling data that is assumed
|
|
||||||
* to be valid (e.g. locally created metadata), it may be sensible to
|
|
||||||
* rethrow the unexpected exception as a DbException so that delivery is
|
|
||||||
* attempted again at next startup. This will allow delivery to succeed if
|
|
||||||
* the unexpected exception was caused by a bug that has subsequently been
|
|
||||||
* fixed.
|
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @throws DbException if a database error occurs while delivering the
|
* @return Whether or not this message should be shared
|
||||||
* message. Delivery will be attempted again at next startup. Throwing
|
* @throws DbException Should only be used for real database errors.
|
||||||
* this exception has the same effect as returning
|
* If this is thrown, delivery will be attempted again at next startup,
|
||||||
* {@link DeliveryAction#DEFER}.
|
* whereas if a FormatException is thrown, the message will be permanently
|
||||||
* @throws FormatException if the message is invalid in the context of its
|
* invalidated.
|
||||||
* dependencies. The message and any dependents will be marked as invalid
|
* @throws FormatException Use this for any non-database error
|
||||||
* and deleted along with their metadata. Throwing this exception has the
|
* that occurs while handling remotely created data.
|
||||||
* same effect as returning {@link DeliveryAction#REJECT}.
|
* This includes errors that occur while handling locally created data
|
||||||
|
* in a context controlled by remotely created data
|
||||||
|
* (for example, parsing the metadata of a dependency
|
||||||
|
* of an incoming message).
|
||||||
|
* Never rethrow DbException as FormatException!
|
||||||
*/
|
*/
|
||||||
protected abstract DeliveryAction incomingMessage(Transaction txn,
|
protected abstract boolean incomingMessage(Transaction txn, Message m,
|
||||||
Message m, BdfList body, BdfDictionary meta)
|
BdfList body, BdfDictionary meta) throws DbException,
|
||||||
throws DbException, FormatException;
|
FormatException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
BdfList body = clientHelper.toList(m);
|
BdfList body = clientHelper.toList(m);
|
||||||
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
||||||
|
|||||||
@@ -118,18 +118,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
||||||
TransportKeys k) throws DbException;
|
TransportKeys k) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are any acks or messages to send to the given
|
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
|
||||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
|
||||||
int maxLatency, boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
* local pseudonym.
|
* local pseudonym.
|
||||||
@@ -162,16 +150,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the database contains keys for communicating with the
|
|
||||||
* given contact over the given transport. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the message with the given ID. Unlike
|
* Deletes the message with the given ID. Unlike
|
||||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
||||||
@@ -202,18 +180,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
int maxLength, int maxLatency) throws DbException;
|
int maxLength, int maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a batch of messages for the given contact containing the
|
|
||||||
* messages with the given IDs, for transmission over a transport with
|
|
||||||
* the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* If any of the given messages are not in the database or are not visible
|
|
||||||
* to the contact, they are omitted from the batch without throwing an
|
|
||||||
* exception.
|
|
||||||
*/
|
|
||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
|
||||||
Collection<MessageId> ids, int maxLatency) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an offer for the given contact for transmission over a
|
* Returns an offer for the given contact for transmission over a
|
||||||
* transport with the given maximum latency, or null if there are no
|
* transport with the given maximum latency, or null if there are no
|
||||||
@@ -326,6 +292,16 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
Message getMessage(Transaction txn, MessageId m) throws DbException;
|
Message getMessage(Transaction txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total length, including headers, of any messages that are
|
||||||
|
* eligible to be sent to the given contact via a transport with the given
|
||||||
|
* max latency.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getMessageBytesToSend(Transaction txn, ContactId c, int maxLatency)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of all delivered messages in the given group.
|
* Returns the IDs of all delivered messages in the given group.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -460,27 +436,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 +493,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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ public interface TransportConnectionWriter {
|
|||||||
*/
|
*/
|
||||||
int getMaxIdleTime();
|
int getMaxIdleTime();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the transport is lossy and cheap.
|
|
||||||
*/
|
|
||||||
boolean isLossyAndCheap();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an output stream for writing to the transport connection.
|
* Returns an output stream for writing to the transport connection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,11 +79,6 @@ public abstract class AbstractDuplexTransportConnection
|
|||||||
return plugin.getMaxIdleTime();
|
return plugin.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() throws IOException {
|
public OutputStream getOutputStream() throws IOException {
|
||||||
return AbstractDuplexTransportConnection.this.getOutputStream();
|
return AbstractDuplexTransportConnection.this.getOutputStream();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.api.plugin.file;
|
package org.briarproject.bramble.api.plugin.file;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
|
||||||
@@ -11,36 +10,32 @@ import javax.annotation.Nullable;
|
|||||||
public interface RemovableDriveManager {
|
public interface RemovableDriveManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the currently running reader task, or null if no reader task
|
* Returns the currently running reader task for the given contact,
|
||||||
* is running.
|
* or null if no task is running.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
RemovableDriveTask getCurrentReaderTask();
|
RemovableDriveTask getCurrentReaderTask(ContactId c);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the currently running writer task, or null if no writer task
|
* Returns the currently running writer task for the given contact,
|
||||||
* is running.
|
* or null if no task is running.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
RemovableDriveTask getCurrentWriterTask();
|
RemovableDriveTask getCurrentWriterTask(ContactId c);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts and returns a reader task, reading from a stream described by
|
* Starts and returns a reader task for the given contact, reading from
|
||||||
* the given transport properties. If a reader task is already running,
|
* a stream described by the given transport properties. If a reader task
|
||||||
* it will be returned and the argument will be ignored.
|
* for the contact is already running, it will be returned and the
|
||||||
|
* transport properties will be ignored.
|
||||||
*/
|
*/
|
||||||
RemovableDriveTask startReaderTask(TransportProperties p);
|
RemovableDriveTask startReaderTask(ContactId c, TransportProperties p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts and returns a writer task for the given contact, writing to
|
* Starts and returns a writer task for the given contact, writing to
|
||||||
* a stream described by the given transport properties. If a writer task
|
* a stream described by the given transport properties. If a writer task
|
||||||
* is already running, it will be returned and the arguments will be
|
* for the contact is already running, it will be returned and the
|
||||||
* ignored.
|
* transport properties will be ignored.
|
||||||
*/
|
*/
|
||||||
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
|
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there is anything to send to the given contact.
|
|
||||||
*/
|
|
||||||
boolean isWriterTaskNeeded(ContactId c) throws DbException;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public interface RemovableDriveTask extends Runnable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total length in bytes of the messages read or written
|
* Returns the total length in bytes of the messages read or written
|
||||||
* so far, or zero if the total is unknown.
|
* so far.
|
||||||
*/
|
*/
|
||||||
public long getDone() {
|
public long getDone() {
|
||||||
return done;
|
return done;
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface SimplexPlugin extends Plugin {
|
public interface SimplexPlugin extends Plugin {
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the transport is likely to lose streams and the cost of
|
|
||||||
* transmitting redundant copies of data is cheap.
|
|
||||||
*/
|
|
||||||
boolean isLossyAndCheap();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to create and return a reader for the given transport
|
* Attempts to create and return a reader for the given transport
|
||||||
* properties. Returns null if a reader cannot be created.
|
* properties. Returns null if a reader cannot be created.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
|
|||||||
PriorityHandler handler);
|
PriorityHandler handler);
|
||||||
|
|
||||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, boolean eager, StreamWriter streamWriter);
|
int maxLatency, StreamWriter streamWriter);
|
||||||
|
|
||||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
|
|||||||
@@ -10,54 +10,23 @@ public interface IncomingMessageHook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once for each incoming message that passes validation.
|
* Called once for each incoming message that passes validation.
|
||||||
* <p>
|
|
||||||
* If an unexpected exception occurs while handling data that is assumed
|
|
||||||
* to be valid (e.g. locally created metadata), it may be sensible to
|
|
||||||
* rethrow the unexpected exception as a DbException so that delivery is
|
|
||||||
* attempted again at next startup. This will allow delivery to succeed if
|
|
||||||
* the unexpected exception was caused by a bug that has subsequently been
|
|
||||||
* fixed.
|
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @throws DbException if a database error occurs while delivering the
|
* @return Whether or not this message should be shared
|
||||||
* message. Delivery will be attempted again at next startup. Throwing
|
* @throws DbException Should only be used for real database errors.
|
||||||
* this exception has the same effect as returning
|
* If this is thrown, delivery will be attempted again at next startup,
|
||||||
* {@link DeliveryAction#DEFER}.
|
* whereas if an InvalidMessageException is thrown,
|
||||||
* @throws InvalidMessageException if the message is invalid in the context
|
* the message will be permanently invalidated.
|
||||||
* of its dependencies. The message and any dependents will be marked as
|
* @throws InvalidMessageException for any non-database error
|
||||||
* invalid and deleted along with their metadata. Throwing this exception
|
* that occurs while handling remotely created data.
|
||||||
* has the same effect as returning {@link DeliveryAction#REJECT}.
|
* This includes errors that occur while handling locally created data
|
||||||
|
* in a context controlled by remotely created data
|
||||||
|
* (for example, parsing the metadata of a dependency
|
||||||
|
* of an incoming message).
|
||||||
|
* Throwing this will delete the incoming message and its metadata
|
||||||
|
* marking it as invalid in the database.
|
||||||
|
* Never rethrow DbException as InvalidMessageException!
|
||||||
*/
|
*/
|
||||||
DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta)
|
boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
throws DbException, InvalidMessageException;
|
throws DbException, InvalidMessageException;
|
||||||
|
|
||||||
enum DeliveryAction {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message and any dependent messages will be moved to the
|
|
||||||
* {@link MessageState#INVALID INVALID state} and deleted, along with
|
|
||||||
* their metadata.
|
|
||||||
*/
|
|
||||||
REJECT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message will be moved to the
|
|
||||||
* {@link MessageState#PENDING PENDING state}. Delivery will be
|
|
||||||
* attempted again at next startup.
|
|
||||||
*/
|
|
||||||
DEFER,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message will be moved to the
|
|
||||||
* {@link MessageState#DELIVERED DELIVERED state} and shared.
|
|
||||||
*/
|
|
||||||
ACCEPT_SHARE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message will be moved to the
|
|
||||||
* {@link MessageState#DELIVERED DELIVERED state} and will not be
|
|
||||||
* shared.
|
|
||||||
*/
|
|
||||||
ACCEPT_DO_NOT_SHARE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,8 @@
|
|||||||
package org.briarproject.bramble.api.sync.validation;
|
package org.briarproject.bramble.api.sync.validation;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
|
||||||
|
|
||||||
public enum MessageState {
|
public enum MessageState {
|
||||||
|
|
||||||
/**
|
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
|
||||||
* A remote message that has not yet been validated.
|
|
||||||
*/
|
|
||||||
UNKNOWN(0),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A remote message that has failed validation, has been
|
|
||||||
* {@link DeliveryAction#REJECT rejected} by the local sync client, or
|
|
||||||
* depends on another message that has failed validation or been rejected.
|
|
||||||
*/
|
|
||||||
INVALID(1),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A remote message that has passed validation and is awaiting delivery to
|
|
||||||
* the local sync client. The message will not be delivered until all its
|
|
||||||
* dependencies have been validated and delivered.
|
|
||||||
*/
|
|
||||||
PENDING(2),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A local message, or a remote message that has passed validation and
|
|
||||||
* been delivered to the local sync client.
|
|
||||||
*/
|
|
||||||
DELIVERED(3);
|
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
|
|
||||||
|
|||||||
@@ -22,24 +22,8 @@ public interface KeyManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives and stores a set of rotation mode transport keys for
|
* Derives and stores a set of rotation mode transport keys for
|
||||||
* communicating with the given contact over the given transport and
|
* communicating with the given contact over each transport and returns the
|
||||||
* returns the key set ID, or null if the transport is not supported.
|
* key set IDs.
|
||||||
* <p/>
|
|
||||||
* {@link StreamContext StreamContexts} for the contact can be created
|
|
||||||
* after this method has returned.
|
|
||||||
*
|
|
||||||
* @param alice True if the local party is Alice
|
|
||||||
* @param active Whether the derived keys can be used for outgoing streams
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t,
|
|
||||||
SecretKey rootKey, long timestamp, boolean alice,
|
|
||||||
boolean active) throws DbException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives and stores a set of rotation mode transport keys for
|
|
||||||
* communicating with the given contact over each supported transport and
|
|
||||||
* returns the key set IDs.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* {@link StreamContext StreamContexts} for the contact can be created
|
* {@link StreamContext StreamContexts} for the contact can be created
|
||||||
* after this method has returned.
|
* after this method has returned.
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface TransportKeyAgreementManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The unique ID of the transport key agreement client.
|
|
||||||
*/
|
|
||||||
ClientId CLIENT_ID =
|
|
||||||
new ClientId("org.briarproject.bramble.transport.agreement");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current major version of the transport key agreement client.
|
|
||||||
*/
|
|
||||||
int MAJOR_VERSION = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current minor version of the transport key agreement client.
|
|
||||||
*/
|
|
||||||
int MINOR_VERSION = 0;
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ import org.briarproject.bramble.properties.PropertiesModule;
|
|||||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
|
||||||
import org.briarproject.bramble.versioning.VersioningModule;
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
public interface BrambleCoreEagerSingletons {
|
public interface BrambleCoreEagerSingletons {
|
||||||
@@ -34,8 +33,6 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
|
|
||||||
void inject(RendezvousModule.EagerSingletons init);
|
void inject(RendezvousModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(TransportKeyAgreementModule.EagerSingletons init);
|
|
||||||
|
|
||||||
void inject(TransportModule.EagerSingletons init);
|
void inject(TransportModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(ValidationModule.EagerSingletons init);
|
void inject(ValidationModule.EagerSingletons init);
|
||||||
@@ -54,7 +51,6 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
c.inject(new RendezvousModule.EagerSingletons());
|
c.inject(new RendezvousModule.EagerSingletons());
|
||||||
c.inject(new PluginModule.EagerSingletons());
|
c.inject(new PluginModule.EagerSingletons());
|
||||||
c.inject(new PropertiesModule.EagerSingletons());
|
c.inject(new PropertiesModule.EagerSingletons());
|
||||||
c.inject(new TransportKeyAgreementModule.EagerSingletons());
|
|
||||||
c.inject(new TransportModule.EagerSingletons());
|
c.inject(new TransportModule.EagerSingletons());
|
||||||
c.inject(new ValidationModule.EagerSingletons());
|
c.inject(new ValidationModule.EagerSingletons());
|
||||||
c.inject(new VersioningModule.EagerSingletons());
|
c.inject(new VersioningModule.EagerSingletons());
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.briarproject.bramble.settings.SettingsModule;
|
|||||||
import org.briarproject.bramble.sync.SyncModule;
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
|
||||||
import org.briarproject.bramble.versioning.VersioningModule;
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
@@ -50,7 +49,6 @@ import dagger.Module;
|
|||||||
RendezvousModule.class,
|
RendezvousModule.class,
|
||||||
SettingsModule.class,
|
SettingsModule.class,
|
||||||
SyncModule.class,
|
SyncModule.class,
|
||||||
TransportKeyAgreementModule.class,
|
|
||||||
TransportModule.class,
|
TransportModule.class,
|
||||||
ValidationModule.class,
|
ValidationModule.class,
|
||||||
VersioningModule.class
|
VersioningModule.class
|
||||||
|
|||||||
@@ -71,10 +71,8 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||||
w.getOutputStream(), ctx);
|
w.getOutputStream(), ctx);
|
||||||
ContactId c = requireNonNull(ctx.getContactId());
|
ContactId c = requireNonNull(ctx.getContactId());
|
||||||
// Use eager retransmission if the transport is lossy and cheap
|
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
|
||||||
streamWriter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,18 +162,6 @@ interface Database<T> {
|
|||||||
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
|
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are any acks or messages to send to the given
|
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
|
||||||
boolean containsAnythingToSend(T txn, ContactId c, int maxLatency,
|
|
||||||
boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
* local pseudonym.
|
* local pseudonym.
|
||||||
@@ -227,16 +215,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
boolean containsTransport(T txn, TransportId t) throws DbException;
|
boolean containsTransport(T txn, TransportId t) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the database contains keys for communicating with the
|
|
||||||
* given contact over the given transport. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
boolean containsTransportKeys(T txn, ContactId c, TransportId t)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given message, the message is
|
* Returns true if the database contains the given message, the message is
|
||||||
* shared, and the visibility of the message's group to the given contact
|
* shared, and the visibility of the message's group to the given contact
|
||||||
@@ -369,6 +347,16 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Message getMessage(T txn, MessageId m) throws DbException;
|
Message getMessage(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total length, including headers, of any messages that are
|
||||||
|
* eligible to be sent to the given contact via a transport with the given
|
||||||
|
* max latency.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getMessageBytesToSend(T txn, ContactId c, int maxLatency)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs and states of all dependencies of the given message.
|
* Returns the IDs and states of all dependencies of the given message.
|
||||||
* For missing dependencies and dependencies in other groups, the state
|
* For missing dependencies and dependencies in other groups, the state
|
||||||
@@ -498,37 +486,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 +590,6 @@ interface Database<T> {
|
|||||||
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contact IDs and transport IDs for which the DB contains
|
|
||||||
* at least one set of transport keys. Handshake mode and rotation mode
|
|
||||||
* keys are included, whether activated or not.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(T txn)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given transport keys.
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -341,15 +341,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.addTransportKeys(txn, p, k);
|
return db.addTransportKeys(txn, p, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
|
||||||
int maxLatency, boolean eager) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsContact(Transaction transaction, AuthorId remote,
|
public boolean containsContact(Transaction transaction, AuthorId remote,
|
||||||
AuthorId local) throws DbException {
|
AuthorId local) throws DbException {
|
||||||
@@ -380,13 +371,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.containsPendingContact(txn, p);
|
return db.containsPendingContact(txn, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsTransportKeys(Transaction transaction, ContactId c,
|
|
||||||
TransportId t) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
return db.containsTransportKeys(txn, c, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMessage(Transaction transaction, MessageId m)
|
public void deleteMessage(Transaction transaction, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -445,32 +429,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return messages;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Offer generateOffer(Transaction transaction, ContactId c,
|
public Offer generateOffer(Transaction transaction, ContactId c,
|
||||||
@@ -617,6 +575,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessage(txn, m);
|
return db.getMessage(txn, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMessageBytesToSend(Transaction transaction, ContactId c,
|
||||||
|
int maxLatency) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.getMessageBytesToSend(txn, c, maxLatency);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessageIds(Transaction transaction,
|
public Collection<MessageId> getMessageIds(Transaction transaction,
|
||||||
GroupId g) throws DbException {
|
GroupId g) throws DbException {
|
||||||
@@ -740,25 +707,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 +780,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getTransportKeys(txn, t);
|
return db.getTransportKeys(txn, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
|
||||||
Transaction transaction) throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
return db.getTransportsWithKeys(txn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -345,11 +344,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||||
+ " ON statuses (contactId, timestamp)";
|
+ " ON statuses (contactId, timestamp)";
|
||||||
|
|
||||||
private static final String
|
|
||||||
INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP =
|
|
||||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp"
|
|
||||||
+ " ON statuses (contactId, txCount, timestamp)";
|
|
||||||
|
|
||||||
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
|
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
|
||||||
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
||||||
+ " ON messages (cleanupDeadline)";
|
+ " ON messages (cleanupDeadline)";
|
||||||
@@ -576,7 +570,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
|
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -1127,55 +1120,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
|
||||||
int maxLatency, boolean eager) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT NULL FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND ack = TRUE";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean acksToSend = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
if (acksToSend) return true;
|
|
||||||
if (eager) {
|
|
||||||
sql = "SELECT NULL from statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
} else {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
long eta = now + maxLatency;
|
|
||||||
sql = "SELECT NULL FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
|
||||||
+ " AND (expiry <= ? OR eta > ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
ps.setLong(3, now);
|
|
||||||
ps.setLong(4, eta);
|
|
||||||
}
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean messagesToSend = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return messagesToSend;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsContact(Connection txn, AuthorId remote,
|
public boolean containsContact(Connection txn, AuthorId remote,
|
||||||
AuthorId local) throws DbException {
|
AuthorId local) throws DbException {
|
||||||
@@ -1333,29 +1277,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsTransportKeys(Connection txn, ContactId c,
|
|
||||||
TransportId t) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT NULL FROM outgoingKeys"
|
|
||||||
+ " WHERE contactId = ? AND transportId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setString(2, t.getString());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean found = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return found;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
||||||
MessageId m) throws DbException {
|
MessageId m) throws DbException {
|
||||||
@@ -1837,6 +1758,37 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMessageBytesToSend(Connection txn, ContactId c,
|
||||||
|
int maxLatency) throws DbException {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long eta = now + maxLatency;
|
||||||
|
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"
|
||||||
|
+ " 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();
|
||||||
|
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> getMessageIds(Connection txn, GroupId g)
|
public Collection<MessageId> getMessageIds(Connection txn, GroupId g)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2284,63 +2236,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 +2605,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 {
|
||||||
|
|||||||
@@ -92,11 +92,6 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransportConnectionReader createReader(TransportProperties p) {
|
public TransportConnectionReader createReader(TransportProperties p) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ class FileTransportWriter implements TransportConnectionWriter {
|
|||||||
return plugin.getMaxIdleTime();
|
return plugin.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return plugin.isLossyAndCheap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() {
|
public OutputStream getOutputStream() {
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -1,102 +1,83 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
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.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY;
|
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class RemovableDriveManagerImpl
|
class RemovableDriveManagerImpl
|
||||||
implements RemovableDriveManager, RemovableDriveTaskRegistry {
|
implements RemovableDriveManager, RemovableDriveTaskRegistry {
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final RemovableDriveTaskFactory taskFactory;
|
private final RemovableDriveTaskFactory taskFactory;
|
||||||
private final Object lock = new Object();
|
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
|
||||||
|
readers = new ConcurrentHashMap<>();
|
||||||
@GuardedBy("lock")
|
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
|
||||||
private RemovableDriveTask reader = null;
|
writers = new ConcurrentHashMap<>();
|
||||||
@GuardedBy("lock")
|
|
||||||
private RemovableDriveTask writer = null;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
|
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
|
||||||
DatabaseComponent db, RemovableDriveTaskFactory taskFactory) {
|
RemovableDriveTaskFactory taskFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.db = db;
|
|
||||||
this.taskFactory = taskFactory;
|
this.taskFactory = taskFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public RemovableDriveTask getCurrentReaderTask() {
|
public RemovableDriveTask getCurrentReaderTask(ContactId c) {
|
||||||
synchronized (lock) {
|
return readers.get(c);
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public RemovableDriveTask getCurrentWriterTask() {
|
public RemovableDriveTask getCurrentWriterTask(ContactId c) {
|
||||||
synchronized (lock) {
|
return writers.get(c);
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemovableDriveTask startReaderTask(TransportProperties p) {
|
public RemovableDriveTask startReaderTask(ContactId c,
|
||||||
RemovableDriveTask created;
|
TransportProperties p) {
|
||||||
synchronized (lock) {
|
RemovableDriveTask task = taskFactory.createReader(this, c, p);
|
||||||
if (reader != null) return reader;
|
RemovableDriveTask old = readers.putIfAbsent(c, task);
|
||||||
reader = created = taskFactory.createReader(this, p);
|
if (old == null) {
|
||||||
|
ioExecutor.execute(task);
|
||||||
|
return task;
|
||||||
|
} else {
|
||||||
|
return old;
|
||||||
}
|
}
|
||||||
ioExecutor.execute(created);
|
|
||||||
return created;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemovableDriveTask startWriterTask(ContactId c,
|
public RemovableDriveTask startWriterTask(ContactId c,
|
||||||
TransportProperties p) {
|
TransportProperties p) {
|
||||||
RemovableDriveTask created;
|
RemovableDriveTask task = taskFactory.createWriter(this, c, p);
|
||||||
synchronized (lock) {
|
RemovableDriveTask old = writers.putIfAbsent(c, task);
|
||||||
if (writer != null) return writer;
|
if (old == null) {
|
||||||
writer = created = taskFactory.createWriter(this, c, p);
|
ioExecutor.execute(task);
|
||||||
}
|
return task;
|
||||||
ioExecutor.execute(created);
|
} else {
|
||||||
return created;
|
return old;
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public void removeWriter(RemovableDriveTask task) {
|
public void removeReader(ContactId c, RemovableDriveTask task) {
|
||||||
synchronized (lock) {
|
readers.remove(c, task);
|
||||||
if (writer == task) writer = null;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void removeWriter(ContactId c, RemovableDriveTask task) {
|
||||||
|
writers.remove(c, task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
||||||
|
|
||||||
static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
RemovableDrivePluginFactory() {
|
RemovableDrivePluginFactory() {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -16,7 +20,8 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
|
class RemovableDriveReaderTask extends RemovableDriveTaskImpl
|
||||||
|
implements EventListener {
|
||||||
|
|
||||||
private final static Logger LOG =
|
private final static Logger LOG =
|
||||||
getLogger(RemovableDriveReaderTask.class.getName());
|
getLogger(RemovableDriveReaderTask.class.getName());
|
||||||
@@ -27,9 +32,10 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
|
|||||||
ConnectionManager connectionManager,
|
ConnectionManager connectionManager,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
RemovableDriveTaskRegistry registry,
|
RemovableDriveTaskRegistry registry,
|
||||||
|
ContactId contactId,
|
||||||
TransportProperties transportProperties) {
|
TransportProperties transportProperties) {
|
||||||
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||||
registry, transportProperties);
|
registry, contactId, transportProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -38,13 +44,25 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
|
|||||||
getPlugin().createReader(transportProperties);
|
getPlugin().createReader(transportProperties);
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
LOG.warning("Failed to create reader");
|
LOG.warning("Failed to create reader");
|
||||||
registry.removeReader(this);
|
registry.removeReader(contactId, this);
|
||||||
setSuccess(false);
|
setSuccess(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
eventBus.addListener(this);
|
||||||
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
|
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof MessageAddedEvent) {
|
||||||
|
MessageAddedEvent m = (MessageAddedEvent) e;
|
||||||
|
if (contactId.equals(m.getContactId())) {
|
||||||
|
LOG.info("Message received");
|
||||||
|
addDone(m.getMessage().getRawLength());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class DecoratedReader implements TransportConnectionReader {
|
private class DecoratedReader implements TransportConnectionReader {
|
||||||
|
|
||||||
private final TransportConnectionReader delegate;
|
private final TransportConnectionReader delegate;
|
||||||
@@ -62,7 +80,8 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
|
|||||||
public void dispose(boolean exception, boolean recognised)
|
public void dispose(boolean exception, boolean recognised)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
delegate.dispose(exception, recognised);
|
delegate.dispose(exception, recognised);
|
||||||
registry.removeReader(RemovableDriveReaderTask.this);
|
registry.removeReader(contactId, RemovableDriveReaderTask.this);
|
||||||
|
eventBus.removeListener(RemovableDriveReaderTask.this);
|
||||||
setSuccess(!exception && recognised);
|
setSuccess(!exception && recognised);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
|||||||
interface RemovableDriveTaskFactory {
|
interface RemovableDriveTaskFactory {
|
||||||
|
|
||||||
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
||||||
TransportProperties p);
|
ContactId c, TransportProperties p);
|
||||||
|
|
||||||
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
|
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
|
||||||
ContactId c, TransportProperties p);
|
ContactId c, TransportProperties p);
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
||||||
TransportProperties p) {
|
ContactId c, TransportProperties p) {
|
||||||
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
|
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
|
||||||
connectionManager, eventBus, registry, p);
|
connectionManager, eventBus, registry, c, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.file;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.Consumer;
|
import org.briarproject.bramble.api.Consumer;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
@@ -29,6 +30,7 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
|
|||||||
final ConnectionManager connectionManager;
|
final ConnectionManager connectionManager;
|
||||||
final EventBus eventBus;
|
final EventBus eventBus;
|
||||||
final RemovableDriveTaskRegistry registry;
|
final RemovableDriveTaskRegistry registry;
|
||||||
|
final ContactId contactId;
|
||||||
final TransportProperties transportProperties;
|
final TransportProperties transportProperties;
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
@@ -43,12 +45,14 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
|
|||||||
ConnectionManager connectionManager,
|
ConnectionManager connectionManager,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
RemovableDriveTaskRegistry registry,
|
RemovableDriveTaskRegistry registry,
|
||||||
|
ContactId contactId,
|
||||||
TransportProperties transportProperties) {
|
TransportProperties transportProperties) {
|
||||||
this.eventExecutor = eventExecutor;
|
this.eventExecutor = eventExecutor;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
|
this.contactId = contactId;
|
||||||
this.transportProperties = transportProperties;
|
this.transportProperties = transportProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,15 +98,15 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
|
|||||||
done = min(state.getDone() + done, state.getTotal());
|
done = min(state.getDone() + done, state.getTotal());
|
||||||
state = new State(done, state.getTotal(), state.isFinished(),
|
state = new State(done, state.getTotal(), state.isFinished(),
|
||||||
state.isSuccess());
|
state.isSuccess());
|
||||||
notifyObservers();
|
|
||||||
}
|
}
|
||||||
|
notifyObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSuccess(boolean success) {
|
void setSuccess(boolean success) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
state = new State(state.getDone(), state.getTotal(), true, success);
|
state = new State(state.getDone(), state.getTotal(), true, success);
|
||||||
notifyObservers();
|
|
||||||
}
|
}
|
||||||
|
notifyObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface RemovableDriveTaskRegistry {
|
interface RemovableDriveTaskRegistry {
|
||||||
|
|
||||||
void removeReader(RemovableDriveTask task);
|
void removeReader(ContactId c, RemovableDriveTask task);
|
||||||
|
|
||||||
void removeWriter(RemovableDriveTask task);
|
void removeWriter(ContactId c, RemovableDriveTask task);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
|||||||
getLogger(RemovableDriveWriterTask.class.getName());
|
getLogger(RemovableDriveWriterTask.class.getName());
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final ContactId contactId;
|
|
||||||
|
|
||||||
RemovableDriveWriterTask(
|
RemovableDriveWriterTask(
|
||||||
DatabaseComponent db,
|
DatabaseComponent db,
|
||||||
@@ -45,9 +44,8 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
|||||||
ContactId contactId,
|
ContactId contactId,
|
||||||
TransportProperties transportProperties) {
|
TransportProperties transportProperties) {
|
||||||
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||||
registry, transportProperties);
|
registry, contactId, transportProperties);
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.contactId = contactId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,16 +54,17 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
|||||||
TransportConnectionWriter w = plugin.createWriter(transportProperties);
|
TransportConnectionWriter w = plugin.createWriter(transportProperties);
|
||||||
if (w == null) {
|
if (w == null) {
|
||||||
LOG.warning("Failed to create writer");
|
LOG.warning("Failed to create writer");
|
||||||
registry.removeWriter(this);
|
registry.removeWriter(contactId, this);
|
||||||
setSuccess(false);
|
setSuccess(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
int maxLatency = plugin.getMaxLatency();
|
||||||
try {
|
try {
|
||||||
setTotal(db.transactionWithResult(true, txn ->
|
setTotal(db.transactionWithResult(true, txn ->
|
||||||
db.getUnackedMessageBytesToSend(txn, contactId)));
|
db.getMessageBytesToSend(txn, contactId, maxLatency)));
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
registry.removeWriter(this);
|
registry.removeWriter(contactId, this);
|
||||||
setSuccess(false);
|
setSuccess(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -105,11 +104,6 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
|||||||
return delegate.getMaxIdleTime();
|
return delegate.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return delegate.isLossyAndCheap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() throws IOException {
|
public OutputStream getOutputStream() throws IOException {
|
||||||
return delegate.getOutputStream();
|
return delegate.getOutputStream();
|
||||||
@@ -118,7 +112,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
|||||||
@Override
|
@Override
|
||||||
public void dispose(boolean exception) throws IOException {
|
public void dispose(boolean exception) throws IOException {
|
||||||
delegate.dispose(exception);
|
delegate.dispose(exception);
|
||||||
registry.removeWriter(RemovableDriveWriterTask.this);
|
registry.removeWriter(contactId, RemovableDriveWriterTask.this);
|
||||||
eventBus.removeListener(RemovableDriveWriterTask.this);
|
eventBus.removeListener(RemovableDriveWriterTask.this);
|
||||||
setSuccess(!exception);
|
setSuccess(!exception);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Plugin;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -17,10 +17,10 @@ class TransportOutputStreamWriter implements TransportConnectionWriter {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(TransportOutputStreamWriter.class.getName());
|
getLogger(TransportOutputStreamWriter.class.getName());
|
||||||
|
|
||||||
private final SimplexPlugin plugin;
|
private final Plugin plugin;
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
|
|
||||||
TransportOutputStreamWriter(SimplexPlugin plugin, OutputStream out) {
|
TransportOutputStreamWriter(Plugin plugin, OutputStream out) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,6 @@ class TransportOutputStreamWriter 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;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
|
|||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -116,8 +115,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
// Find the latest update for this transport, if any
|
// Find the latest update for this transport, if any
|
||||||
BdfDictionary d = metadataParser.parse(meta);
|
BdfDictionary d = metadataParser.parse(meta);
|
||||||
@@ -132,14 +131,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
// We've already received a newer update - delete this one
|
// We've already received a newer update - delete this one
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.Versions;
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
@@ -23,11 +22,7 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
|||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -66,7 +61,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
private final boolean eager;
|
|
||||||
private final StreamWriter streamWriter;
|
private final StreamWriter streamWriter;
|
||||||
private final SyncRecordWriter recordWriter;
|
private final SyncRecordWriter recordWriter;
|
||||||
private final AtomicInteger outstandingQueries;
|
private final AtomicInteger outstandingQueries;
|
||||||
@@ -76,7 +70,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
|
|
||||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
EventBus eventBus, ContactId contactId, TransportId transportId,
|
EventBus eventBus, ContactId contactId, TransportId transportId,
|
||||||
int maxLatency, boolean eager, StreamWriter streamWriter,
|
int maxLatency, StreamWriter streamWriter,
|
||||||
SyncRecordWriter recordWriter) {
|
SyncRecordWriter recordWriter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
@@ -84,7 +78,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
this.eager = eager;
|
|
||||||
this.streamWriter = streamWriter;
|
this.streamWriter = streamWriter;
|
||||||
this.recordWriter = recordWriter;
|
this.recordWriter = recordWriter;
|
||||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
outstandingQueries = new AtomicInteger(2); // One per type of record
|
||||||
@@ -99,9 +92,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
// Send our supported protocol versions
|
// Send our supported protocol versions
|
||||||
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
dbExecutor.execute(this::generateAck);
|
dbExecutor.execute(new GenerateAck());
|
||||||
if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
|
dbExecutor.execute(new GenerateBatch());
|
||||||
else dbExecutor.execute(this::generateBatch);
|
|
||||||
// Write records until interrupted or no more records to write
|
// Write records until interrupted or no more records to write
|
||||||
try {
|
try {
|
||||||
while (!interrupted) {
|
while (!interrupted) {
|
||||||
@@ -146,65 +138,11 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
private class GenerateAck implements Runnable {
|
||||||
private void loadUnackedMessageIds() {
|
|
||||||
if (interrupted) return;
|
|
||||||
try {
|
|
||||||
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
|
|
||||||
db.getUnackedMessagesToSend(txn, contactId));
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(ids.size() + " unacked messages to send");
|
|
||||||
}
|
|
||||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
|
||||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void generateEagerBatch(Map<MessageId, Integer> ids) {
|
@Override
|
||||||
if (interrupted) return;
|
public void run() {
|
||||||
// Take some message IDs from `ids` to form a batch
|
|
||||||
Collection<MessageId> batchIds = new ArrayList<>();
|
|
||||||
long totalLength = 0;
|
|
||||||
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
// Check whether the next message will fit in the batch
|
|
||||||
Entry<MessageId, Integer> e = it.next();
|
|
||||||
int length = e.getValue();
|
|
||||||
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
|
|
||||||
// Add the message to the batch
|
|
||||||
it.remove();
|
|
||||||
batchIds.add(e.getKey());
|
|
||||||
totalLength += length;
|
|
||||||
}
|
|
||||||
if (batchIds.isEmpty()) throw new AssertionError();
|
|
||||||
try {
|
|
||||||
Collection<Message> batch =
|
|
||||||
db.transactionWithResult(false, txn ->
|
|
||||||
db.generateBatch(txn, contactId, batchIds,
|
|
||||||
maxLatency));
|
|
||||||
writerTasks.add(() -> writeEagerBatch(batch, ids));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void writeEagerBatch(Collection<Message> batch,
|
|
||||||
Map<MessageId, Integer> ids) throws IOException {
|
|
||||||
if (interrupted) return;
|
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
|
||||||
LOG.info("Sent eager batch");
|
|
||||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
|
||||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
private void generateAck() {
|
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
try {
|
try {
|
||||||
Ack a = db.transactionWithNullableResult(false, txn ->
|
Ack a = db.transactionWithNullableResult(false, txn ->
|
||||||
@@ -212,23 +150,37 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Generated ack: " + (a != null));
|
LOG.info("Generated ack: " + (a != null));
|
||||||
if (a == null) decrementOutstandingQueries();
|
if (a == null) decrementOutstandingQueries();
|
||||||
else writerTasks.add(() -> writeAck(a));
|
else writerTasks.add(new WriteAck(a));
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
interrupt();
|
interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WriteAck implements ThrowingRunnable<IOException> {
|
||||||
|
|
||||||
|
private final Ack ack;
|
||||||
|
|
||||||
|
private WriteAck(Ack ack) {
|
||||||
|
this.ack = ack;
|
||||||
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void writeAck(Ack ack) throws IOException {
|
@Override
|
||||||
|
public void run() throws IOException {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeAck(ack);
|
recordWriter.writeAck(ack);
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
dbExecutor.execute(this::generateAck);
|
dbExecutor.execute(new GenerateAck());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class GenerateBatch implements Runnable {
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void generateBatch() {
|
@Override
|
||||||
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
try {
|
try {
|
||||||
Collection<Message> b =
|
Collection<Message> b =
|
||||||
@@ -238,18 +190,29 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Generated batch: " + (b != null));
|
LOG.info("Generated batch: " + (b != null));
|
||||||
if (b == null) decrementOutstandingQueries();
|
if (b == null) decrementOutstandingQueries();
|
||||||
else writerTasks.add(() -> writeBatch(b));
|
else writerTasks.add(new WriteBatch(b));
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
interrupt();
|
interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WriteBatch implements ThrowingRunnable<IOException> {
|
||||||
|
|
||||||
|
private final Collection<Message> batch;
|
||||||
|
|
||||||
|
private WriteBatch(Collection<Message> batch) {
|
||||||
|
this.batch = batch;
|
||||||
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void writeBatch(Collection<Message> batch) throws IOException {
|
@Override
|
||||||
|
public void run() throws IOException {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
for (Message m : batch) recordWriter.writeMessage(m);
|
||||||
LOG.info("Sent batch");
|
LOG.info("Sent batch");
|
||||||
dbExecutor.execute(this::generateBatch);
|
dbExecutor.execute(new GenerateBatch());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, boolean eager, StreamWriter streamWriter) {
|
int maxLatency, StreamWriter streamWriter) {
|
||||||
OutputStream out = streamWriter.getOutputStream();
|
OutputStream out = streamWriter.getOutputStream();
|
||||||
SyncRecordWriter recordWriter =
|
SyncRecordWriter recordWriter =
|
||||||
recordWriterFactory.createRecordWriter(out);
|
recordWriterFactory.createRecordWriter(out);
|
||||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
||||||
maxLatency, eager, streamWriter, recordWriter);
|
maxLatency, streamWriter, recordWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.sync.MessageContext;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
@@ -41,10 +40,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||||
@@ -190,19 +185,16 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
int majorVersion = g.getMajorVersion();
|
int majorVersion = g.getMajorVersion();
|
||||||
Metadata meta =
|
Metadata meta =
|
||||||
db.getMessageMetadataForValidator(txn, id);
|
db.getMessageMetadataForValidator(txn, id);
|
||||||
DeliveryAction action =
|
DeliveryResult result =
|
||||||
deliverMessage(txn, m, c, majorVersion, meta);
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (action == REJECT) {
|
if (result.valid) {
|
||||||
invalidateMessage(txn, id);
|
|
||||||
addDependentsToInvalidate(txn, id, invalidate);
|
|
||||||
} else if (action == ACCEPT_SHARE) {
|
|
||||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
|
||||||
addPendingDependents(txn, id, pending);
|
addPendingDependents(txn, id, pending);
|
||||||
|
if (result.share) {
|
||||||
db.setMessageShared(txn, id);
|
db.setMessageShared(txn, id);
|
||||||
toShare.addAll(states.keySet());
|
toShare.addAll(states.keySet());
|
||||||
} else if (action == ACCEPT_DO_NOT_SHARE) {
|
}
|
||||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
} else {
|
||||||
addPendingDependents(txn, id, pending);
|
addDependentsToInvalidate(txn, id, invalidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
|
||||||
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);
|
addPendingDependents(txn, id, pending);
|
||||||
|
if (result.share) {
|
||||||
db.setMessageShared(txn, id);
|
db.setMessageShared(txn, id);
|
||||||
toShare.addAll(dependencies);
|
toShare.addAll(dependencies);
|
||||||
} else if (action == ACCEPT_DO_NOT_SHARE) {
|
}
|
||||||
db.setMessageState(txn, id, DELIVERED);
|
} else {
|
||||||
addPendingDependents(txn, id, pending);
|
addDependentsToInvalidate(txn, id, invalidate);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
db.setMessageState(txn, id, PENDING);
|
db.setMessageState(txn, id, PENDING);
|
||||||
@@ -317,22 +304,24 @@ 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) {
|
|
||||||
logException(LOG, INFO, e);
|
|
||||||
return DEFER;
|
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
logException(LOG, INFO, e);
|
logException(LOG, INFO, e);
|
||||||
return REJECT;
|
invalidateMessage(txn, m.getId());
|
||||||
|
return new DeliveryResult(false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
db.setMessageState(txn, m.getId(), DELIVERED);
|
||||||
|
return new DeliveryResult(true, shareMsg);
|
||||||
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void addPendingDependents(Transaction txn, MessageId m,
|
private void addPendingDependents(Transaction txn, MessageId m,
|
||||||
@@ -458,4 +447,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DeliveryResult {
|
||||||
|
|
||||||
|
private final boolean valid, share;
|
||||||
|
|
||||||
|
private DeliveryResult(boolean valid, boolean share) {
|
||||||
|
this.valid = valid;
|
||||||
|
this.share = share;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final PluginConfig pluginConfig;
|
private final PluginConfig pluginConfig;
|
||||||
|
private final TransportKeyManagerFactory transportKeyManagerFactory;
|
||||||
private final TransportCrypto transportCrypto;
|
private final TransportCrypto transportCrypto;
|
||||||
|
|
||||||
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
||||||
@@ -60,39 +61,34 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
KeyManagerImpl(DatabaseComponent db,
|
KeyManagerImpl(DatabaseComponent db,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
PluginConfig pluginConfig,
|
PluginConfig pluginConfig,
|
||||||
TransportCrypto transportCrypto,
|
TransportKeyManagerFactory transportKeyManagerFactory,
|
||||||
TransportKeyManagerFactory transportKeyManagerFactory) {
|
TransportCrypto transportCrypto) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.pluginConfig = pluginConfig;
|
this.pluginConfig = pluginConfig;
|
||||||
|
this.transportKeyManagerFactory = transportKeyManagerFactory;
|
||||||
this.transportCrypto = transportCrypto;
|
this.transportCrypto = transportCrypto;
|
||||||
managers = new ConcurrentHashMap<>();
|
managers = new ConcurrentHashMap<>();
|
||||||
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
|
|
||||||
TransportKeyManager m = transportKeyManagerFactory.
|
|
||||||
createTransportKeyManager(f.getId(), f.getMaxLatency());
|
|
||||||
managers.put(f.getId(), m);
|
|
||||||
}
|
|
||||||
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) {
|
|
||||||
TransportKeyManager m = transportKeyManagerFactory.
|
|
||||||
createTransportKeyManager(f.getId(), f.getMaxLatency());
|
|
||||||
managers.put(f.getId(), m);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startService() throws ServiceException {
|
public void startService() throws ServiceException {
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
|
Map<TransportId, Integer> transports = new HashMap<>();
|
||||||
|
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
|
||||||
|
transports.put(f.getId(), f.getMaxLatency());
|
||||||
|
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
|
||||||
|
transports.put(f.getId(), f.getMaxLatency());
|
||||||
try {
|
try {
|
||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
for (SimplexPluginFactory f :
|
for (Entry<TransportId, Integer> e : transports.entrySet())
|
||||||
pluginConfig.getSimplexFactories()) {
|
db.addTransport(txn, e.getKey(), e.getValue());
|
||||||
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
for (Entry<TransportId, Integer> e : transports.entrySet()) {
|
||||||
managers.get(f.getId()).start(txn);
|
TransportKeyManager m = transportKeyManagerFactory
|
||||||
}
|
.createTransportKeyManager(e.getKey(),
|
||||||
for (DuplexPluginFactory f :
|
e.getValue());
|
||||||
pluginConfig.getDuplexFactories()) {
|
managers.put(e.getKey(), m);
|
||||||
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
m.start(txn);
|
||||||
managers.get(f.getId()).start(txn);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
@@ -105,17 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeySetId addRotationKeys(Transaction txn, ContactId c,
|
public Map<TransportId, KeySetId> addRotationKeys(
|
||||||
TransportId t, SecretKey rootKey, long timestamp, boolean alice,
|
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
|
||||||
boolean active) throws DbException {
|
boolean alice, boolean active) throws DbException {
|
||||||
return withManager(t, m ->
|
|
||||||
m.addRotationKeys(txn, c, rootKey, timestamp, alice, active));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
|
|
||||||
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
|
|
||||||
boolean active) throws DbException {
|
|
||||||
Map<TransportId, KeySetId> ids = new HashMap<>();
|
Map<TransportId, KeySetId> ids = new HashMap<>();
|
||||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||||
TransportId t = e.getKey();
|
TransportId t = e.getKey();
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MessageEncoder {
|
|
||||||
|
|
||||||
Message encodeKeyMessage(GroupId contactGroupId,
|
|
||||||
TransportId transportId, PublicKey publicKey);
|
|
||||||
|
|
||||||
Message encodeActivateMessage(GroupId contactGroupId,
|
|
||||||
TransportId transportId, MessageId previousMessageId);
|
|
||||||
|
|
||||||
BdfDictionary encodeMessageMetadata(TransportId transportId,
|
|
||||||
MessageType type, boolean local);
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class MessageEncoderImpl implements MessageEncoder {
|
|
||||||
|
|
||||||
private final ClientHelper clientHelper;
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MessageEncoderImpl(ClientHelper clientHelper, Clock clock) {
|
|
||||||
this.clientHelper = clientHelper;
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message encodeKeyMessage(GroupId contactGroupId,
|
|
||||||
TransportId transportId, PublicKey publicKey) {
|
|
||||||
BdfList body = BdfList.of(
|
|
||||||
KEY.getValue(),
|
|
||||||
transportId.getString(),
|
|
||||||
publicKey.getEncoded());
|
|
||||||
return encodeMessage(contactGroupId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message encodeActivateMessage(GroupId contactGroupId,
|
|
||||||
TransportId transportId, MessageId previousMessageId) {
|
|
||||||
BdfList body = BdfList.of(
|
|
||||||
ACTIVATE.getValue(),
|
|
||||||
transportId.getString(),
|
|
||||||
previousMessageId);
|
|
||||||
return encodeMessage(contactGroupId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BdfDictionary encodeMessageMetadata(TransportId transportId,
|
|
||||||
MessageType type, boolean local) {
|
|
||||||
return BdfDictionary.of(
|
|
||||||
new BdfEntry(MSG_KEY_IS_SESSION, false),
|
|
||||||
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()),
|
|
||||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
|
|
||||||
new BdfEntry(MSG_KEY_LOCAL, local));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message encodeMessage(GroupId contactGroupId, BdfList body) {
|
|
||||||
try {
|
|
||||||
return clientHelper.createMessage(contactGroupId,
|
|
||||||
clock.currentTimeMillis(), clientHelper.toByteArray(body));
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
enum MessageType {
|
|
||||||
|
|
||||||
KEY(0),
|
|
||||||
ACTIVATE(1);
|
|
||||||
|
|
||||||
private final int value;
|
|
||||||
|
|
||||||
MessageType(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MessageType fromValue(int value) throws FormatException {
|
|
||||||
for (MessageType t : values()) if (t.value == value) return t;
|
|
||||||
throw new FormatException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class Session {
|
|
||||||
|
|
||||||
private final State state;
|
|
||||||
@Nullable
|
|
||||||
private final MessageId lastLocalMessageId;
|
|
||||||
@Nullable
|
|
||||||
private final KeyPair localKeyPair;
|
|
||||||
@Nullable
|
|
||||||
private final Long localTimestamp;
|
|
||||||
@Nullable
|
|
||||||
private final KeySetId keySetId;
|
|
||||||
|
|
||||||
Session(State state, @Nullable MessageId lastLocalMessageId,
|
|
||||||
@Nullable KeyPair localKeyPair, @Nullable Long localTimestamp,
|
|
||||||
@Nullable KeySetId keySetId) {
|
|
||||||
this.state = state;
|
|
||||||
this.lastLocalMessageId = lastLocalMessageId;
|
|
||||||
this.localKeyPair = localKeyPair;
|
|
||||||
this.localTimestamp = localTimestamp;
|
|
||||||
this.keySetId = keySetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
State getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
MessageId getLastLocalMessageId() {
|
|
||||||
return lastLocalMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
KeyPair getLocalKeyPair() {
|
|
||||||
return localKeyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
Long getLocalTimestamp() {
|
|
||||||
return localTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
KeySetId getKeySetId() {
|
|
||||||
return keySetId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface SessionEncoder {
|
|
||||||
|
|
||||||
BdfDictionary encodeSession(Session s, TransportId transportId);
|
|
||||||
|
|
||||||
BdfDictionary getSessionQuery(TransportId transportId);
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class SessionEncoderImpl implements SessionEncoder {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SessionEncoderImpl() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BdfDictionary encodeSession(Session s, TransportId transportId) {
|
|
||||||
BdfDictionary meta = new BdfDictionary();
|
|
||||||
meta.put(MSG_KEY_IS_SESSION, true);
|
|
||||||
meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString());
|
|
||||||
meta.put(SESSION_KEY_STATE, s.getState().getValue());
|
|
||||||
putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
|
|
||||||
s.getLastLocalMessageId());
|
|
||||||
KeyPair localKeyPair = s.getLocalKeyPair();
|
|
||||||
if (localKeyPair == null) {
|
|
||||||
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE);
|
|
||||||
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE);
|
|
||||||
} else {
|
|
||||||
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY,
|
|
||||||
localKeyPair.getPublic().getEncoded());
|
|
||||||
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY,
|
|
||||||
localKeyPair.getPrivate().getEncoded());
|
|
||||||
}
|
|
||||||
putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
|
|
||||||
KeySetId keySetId = s.getKeySetId();
|
|
||||||
if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE);
|
|
||||||
else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt());
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BdfDictionary getSessionQuery(TransportId transportId) {
|
|
||||||
return BdfDictionary.of(
|
|
||||||
new BdfEntry(MSG_KEY_IS_SESSION, true),
|
|
||||||
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void putNullable(BdfDictionary meta, String key,
|
|
||||||
@Nullable Object o) {
|
|
||||||
meta.put(key, o == null ? NULL_VALUE : o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface SessionParser {
|
|
||||||
|
|
||||||
Session parseSession(BdfDictionary meta) throws FormatException;
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class SessionParserImpl implements SessionParser {
|
|
||||||
|
|
||||||
private final TransportKeyAgreementCrypto crypto;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SessionParserImpl(TransportKeyAgreementCrypto crypto) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Session parseSession(BdfDictionary meta) throws FormatException {
|
|
||||||
State state =
|
|
||||||
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
|
|
||||||
|
|
||||||
MessageId lastLocalMessageId = null;
|
|
||||||
byte[] lastLocalMessageIdBytes =
|
|
||||||
meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
|
|
||||||
if (lastLocalMessageIdBytes != null) {
|
|
||||||
lastLocalMessageId = new MessageId(lastLocalMessageIdBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyPair localKeyPair = null;
|
|
||||||
byte[] localPublicKeyBytes =
|
|
||||||
meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY);
|
|
||||||
byte[] localPrivateKeyBytes =
|
|
||||||
meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY);
|
|
||||||
if (localPublicKeyBytes != null && localPrivateKeyBytes != null) {
|
|
||||||
PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes);
|
|
||||||
PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes);
|
|
||||||
localKeyPair = new KeyPair(pub, priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
|
|
||||||
|
|
||||||
KeySetId keySetId = null;
|
|
||||||
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
|
|
||||||
if (keySetIdLong != null) {
|
|
||||||
keySetId = new KeySetId(keySetIdLong.intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Session(state, lastLocalMessageId, localKeyPair,
|
|
||||||
localTimestamp, keySetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
enum State {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We've sent a key message and are awaiting the contact's key message.
|
|
||||||
*/
|
|
||||||
AWAIT_KEY(0),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We've exchanged key messages, derived the transport keys and sent an
|
|
||||||
* activate message, and now we're awaiting the contact's activate message.
|
|
||||||
*/
|
|
||||||
AWAIT_ACTIVATE(1),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We've exchanged key messages and activate messages, and have derived and
|
|
||||||
* activated the transport keys. This is the end state.
|
|
||||||
*/
|
|
||||||
ACTIVATED(2);
|
|
||||||
|
|
||||||
private final int value;
|
|
||||||
|
|
||||||
State(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static State fromValue(int value) throws FormatException {
|
|
||||||
for (State s : values()) if (s.value == value) return s;
|
|
||||||
throw new FormatException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface TransportKeyAgreementConstants {
|
|
||||||
|
|
||||||
String MSG_KEY_IS_SESSION = "isSession";
|
|
||||||
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
|
||||||
String MSG_KEY_TRANSPORT_ID = "transportId";
|
|
||||||
String MSG_KEY_PUBLIC_KEY = "publicKey";
|
|
||||||
String MSG_KEY_LOCAL = "local";
|
|
||||||
|
|
||||||
String SESSION_KEY_STATE = "state";
|
|
||||||
String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
|
|
||||||
String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey";
|
|
||||||
String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey";
|
|
||||||
String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
|
|
||||||
String SESSION_KEY_KEY_SET_ID = "keySetId";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving the root key from key pairs.
|
|
||||||
*/
|
|
||||||
String ROOT_KEY_LABEL =
|
|
||||||
"org.briarproject.bramble.transport.agreement/ROOT_KEY";
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface TransportKeyAgreementCrypto {
|
|
||||||
|
|
||||||
KeyPair generateKeyPair();
|
|
||||||
|
|
||||||
SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey)
|
|
||||||
throws GeneralSecurityException;
|
|
||||||
|
|
||||||
PublicKey parsePublicKey(byte[] encoded) throws FormatException;
|
|
||||||
|
|
||||||
PrivateKey parsePrivateKey(byte[] encoded) throws FormatException;
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.Bytes.compare;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.ROOT_KEY_LABEL;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
TransportKeyAgreementCryptoImpl(CryptoComponent crypto) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyPair generateKeyPair() {
|
|
||||||
return crypto.generateAgreementKeyPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SecretKey deriveRootKey(KeyPair localKeyPair,
|
|
||||||
PublicKey remotePublicKey) throws GeneralSecurityException {
|
|
||||||
byte[] theirPublic = remotePublicKey.getEncoded();
|
|
||||||
byte[] ourPublic = localKeyPair.getPublic().getEncoded();
|
|
||||||
boolean alice = compare(ourPublic, theirPublic) < 0;
|
|
||||||
byte[][] inputs = {
|
|
||||||
alice ? ourPublic : theirPublic,
|
|
||||||
alice ? theirPublic : ourPublic
|
|
||||||
};
|
|
||||||
return crypto.deriveSharedSecret(ROOT_KEY_LABEL, remotePublicKey,
|
|
||||||
localKeyPair, inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PublicKey parsePublicKey(byte[] encoded) throws FormatException {
|
|
||||||
try {
|
|
||||||
return crypto.getAgreementKeyParser().parsePublicKey(encoded);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new FormatException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PrivateKey parsePrivateKey(byte[] encoded) throws FormatException {
|
|
||||||
try {
|
|
||||||
return crypto.getAgreementKeyParser().parsePrivateKey(encoded);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new FormatException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
|
||||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
|
||||||
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
|
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.lang.Math.min;
|
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.api.Bytes.compare;
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
|
|
||||||
implements TransportKeyAgreementManager, OpenDatabaseHook, ContactHook,
|
|
||||||
ClientVersioningHook {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(TransportKeyAgreementManagerImpl.class.getName());
|
|
||||||
|
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
|
||||||
private final ClientVersioningManager clientVersioningManager;
|
|
||||||
private final IdentityManager identityManager;
|
|
||||||
private final KeyManager keyManager;
|
|
||||||
private final MessageEncoder messageEncoder;
|
|
||||||
private final SessionEncoder sessionEncoder;
|
|
||||||
private final SessionParser sessionParser;
|
|
||||||
private final TransportKeyAgreementCrypto crypto;
|
|
||||||
|
|
||||||
private final List<TransportId> transports;
|
|
||||||
private final Group localGroup;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
TransportKeyAgreementManagerImpl(
|
|
||||||
DatabaseComponent db,
|
|
||||||
ClientHelper clientHelper,
|
|
||||||
MetadataParser metadataParser,
|
|
||||||
ContactGroupFactory contactGroupFactory,
|
|
||||||
ClientVersioningManager clientVersioningManager,
|
|
||||||
IdentityManager identityManager,
|
|
||||||
KeyManager keyManager,
|
|
||||||
MessageEncoder messageEncoder,
|
|
||||||
SessionEncoder sessionEncoder,
|
|
||||||
SessionParser sessionParser,
|
|
||||||
TransportKeyAgreementCrypto crypto,
|
|
||||||
PluginConfig config) {
|
|
||||||
super(db, clientHelper, metadataParser);
|
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
|
||||||
this.clientVersioningManager = clientVersioningManager;
|
|
||||||
this.identityManager = identityManager;
|
|
||||||
this.keyManager = keyManager;
|
|
||||||
this.messageEncoder = messageEncoder;
|
|
||||||
this.sessionEncoder = sessionEncoder;
|
|
||||||
this.sessionParser = sessionParser;
|
|
||||||
this.crypto = crypto;
|
|
||||||
transports = new ArrayList<>();
|
|
||||||
for (DuplexPluginFactory duplex : config.getDuplexFactories()) {
|
|
||||||
transports.add(duplex.getId());
|
|
||||||
}
|
|
||||||
for (SimplexPluginFactory simplex : config.getSimplexFactories()) {
|
|
||||||
transports.add(simplex.getId());
|
|
||||||
}
|
|
||||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
|
||||||
MAJOR_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
|
||||||
Collection<Contact> contacts = db.getContacts(txn);
|
|
||||||
if (!db.containsGroup(txn, localGroup.getId())) {
|
|
||||||
db.addGroup(txn, localGroup);
|
|
||||||
// Set things up for any pre-existing contacts
|
|
||||||
for (Contact c : contacts) addingContact(txn, c);
|
|
||||||
}
|
|
||||||
// Find any contacts and transports that need keys
|
|
||||||
Map<ContactId, Collection<TransportId>> transportsWithKeys =
|
|
||||||
db.getTransportsWithKeys(txn);
|
|
||||||
for (Contact c : contacts) {
|
|
||||||
Collection<TransportId> withKeys =
|
|
||||||
transportsWithKeys.get(c.getId());
|
|
||||||
for (TransportId t : transports) {
|
|
||||||
if (withKeys == null || !withKeys.contains(t)) {
|
|
||||||
// We need keys for this contact and transport
|
|
||||||
GroupId contactGroupId = getContactGroup(c).getId();
|
|
||||||
SavedSession ss = loadSession(txn, contactGroupId, t);
|
|
||||||
if (ss == null) {
|
|
||||||
// Start a session by sending our key message
|
|
||||||
startSession(txn, contactGroupId, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
|
||||||
// Create a group to share with the contact
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
db.addGroup(txn, g);
|
|
||||||
// Attach the contact ID to the group
|
|
||||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
|
||||||
// Apply the client's visibility to the contact group
|
|
||||||
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
|
||||||
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
|
||||||
db.removeGroup(txn, getContactGroup(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
|
||||||
Visibility v) throws DbException {
|
|
||||||
// Apply the client's visibility to the contact group
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
|
||||||
BdfList body, BdfDictionary meta)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
MessageType type = MessageType.fromValue(
|
|
||||||
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
|
|
||||||
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Received " + type + " message for " + t);
|
|
||||||
}
|
|
||||||
if (!transports.contains(t)) {
|
|
||||||
// Defer handling the message until we support the transport
|
|
||||||
return DEFER;
|
|
||||||
}
|
|
||||||
SavedSession ss = loadSession(txn, m.getGroupId(), t);
|
|
||||||
if (type == KEY) return handleKeyMessage(txn, t, m, meta, ss);
|
|
||||||
else if (type == ACTIVATE) return handleActivateMessage(txn, t, ss);
|
|
||||||
else throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeliveryAction handleKeyMessage(Transaction txn, TransportId t,
|
|
||||||
Message m, BdfDictionary meta, @Nullable SavedSession ss)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
|
||||||
boolean haveKeys = db.containsTransportKeys(txn, c, t);
|
|
||||||
if (ss == null) {
|
|
||||||
if (haveKeys) {
|
|
||||||
// We have keys but no session, so we must have derived keys
|
|
||||||
// when adding the contact. If the contact didn't support
|
|
||||||
// the transport when they added us, they wouldn't have
|
|
||||||
// derived keys at that time. If they later added support for
|
|
||||||
// the transport then they would have started a session, so a
|
|
||||||
// key message is valid in this case
|
|
||||||
return handleKeyMessageForNewSession(txn, c, t, m, meta);
|
|
||||||
} else {
|
|
||||||
// We don't have keys, so we should have created a session at
|
|
||||||
// startup
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
} else if (ss.session.getState() == AWAIT_KEY) {
|
|
||||||
if (haveKeys) {
|
|
||||||
// We have keys, so we shouldn't be in the AWAIT_KEY state,
|
|
||||||
// even if the contact didn't derive keys when adding us and
|
|
||||||
// later started a session
|
|
||||||
throw new IllegalStateException();
|
|
||||||
} else {
|
|
||||||
// This is the key message we're waiting for
|
|
||||||
return handleKeyMessageForExistingSession(txn, c, t, m, meta,
|
|
||||||
ss);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return REJECT; // Not valid in this state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeliveryAction handleActivateMessage(Transaction txn,
|
|
||||||
TransportId t, @Nullable SavedSession ss) throws DbException {
|
|
||||||
if (ss != null && ss.session.getState() == AWAIT_ACTIVATE) {
|
|
||||||
// Activate the keys and finish the session
|
|
||||||
KeySetId keySetId = requireNonNull(ss.session.getKeySetId());
|
|
||||||
keyManager.activateKeys(txn, singletonMap(t, keySetId));
|
|
||||||
Session session = new Session(ACTIVATED,
|
|
||||||
ss.session.getLastLocalMessageId(), null, null, null);
|
|
||||||
saveSession(txn, t, ss.storageId, session);
|
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
|
||||||
} else {
|
|
||||||
return REJECT; // Not valid in this state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeliveryAction handleKeyMessageForNewSession(Transaction txn,
|
|
||||||
ContactId c, TransportId t, Message m, BdfDictionary meta)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
KeyPair localKeyPair = crypto.generateKeyPair();
|
|
||||||
PublicKey remotePublicKey =
|
|
||||||
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
|
|
||||||
Message keyMessage = sendKeyMessage(txn, m.getGroupId(), t,
|
|
||||||
localKeyPair.getPublic());
|
|
||||||
long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp());
|
|
||||||
SecretKey rootKey;
|
|
||||||
try {
|
|
||||||
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
return REJECT; // Invalid public key
|
|
||||||
}
|
|
||||||
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
|
|
||||||
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
|
|
||||||
minTimestamp, alice, false);
|
|
||||||
Message activateMessage =
|
|
||||||
sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId());
|
|
||||||
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
|
|
||||||
null, null, keySetId);
|
|
||||||
saveNewSession(txn, m.getGroupId(), t, session);
|
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeliveryAction handleKeyMessageForExistingSession(Transaction txn,
|
|
||||||
ContactId c, TransportId t, Message m, BdfDictionary meta,
|
|
||||||
SavedSession ss) throws DbException, FormatException {
|
|
||||||
KeyPair localKeyPair = requireNonNull(ss.session.getLocalKeyPair());
|
|
||||||
PublicKey remotePublicKey =
|
|
||||||
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
|
|
||||||
long localTimestamp = requireNonNull(ss.session.getLocalTimestamp());
|
|
||||||
long minTimestamp = min(localTimestamp, m.getTimestamp());
|
|
||||||
SecretKey rootKey;
|
|
||||||
try {
|
|
||||||
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
return REJECT; // Invalid public key
|
|
||||||
}
|
|
||||||
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
|
|
||||||
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
|
|
||||||
minTimestamp, alice, false);
|
|
||||||
MessageId previousMessageId =
|
|
||||||
requireNonNull(ss.session.getLastLocalMessageId());
|
|
||||||
Message activateMessage =
|
|
||||||
sendActivateMessage(txn, m.getGroupId(), t, previousMessageId);
|
|
||||||
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
|
|
||||||
null, null, keySetId);
|
|
||||||
saveSession(txn, t, ss.storageId, session);
|
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startSession(Transaction txn, GroupId contactGroupId,
|
|
||||||
TransportId t) throws DbException {
|
|
||||||
KeyPair localKeyPair = crypto.generateKeyPair();
|
|
||||||
Message keyMessage = sendKeyMessage(txn, contactGroupId, t,
|
|
||||||
localKeyPair.getPublic());
|
|
||||||
Session session = new Session(AWAIT_KEY, keyMessage.getId(),
|
|
||||||
localKeyPair, keyMessage.getTimestamp(), null);
|
|
||||||
saveNewSession(txn, contactGroupId, t, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private SavedSession loadSession(Transaction txn, GroupId contactGroupId,
|
|
||||||
TransportId t) throws DbException {
|
|
||||||
try {
|
|
||||||
BdfDictionary query = sessionEncoder.getSessionQuery(t);
|
|
||||||
Collection<MessageId> ids =
|
|
||||||
clientHelper.getMessageIds(txn, contactGroupId, query);
|
|
||||||
if (ids.size() > 1) throw new DbException();
|
|
||||||
if (ids.isEmpty()) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("No session for " + t);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
MessageId storageId = ids.iterator().next();
|
|
||||||
BdfDictionary bdfSession =
|
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, storageId);
|
|
||||||
Session session = sessionParser.parseSession(bdfSession);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Loaded session in state " + session.getState()
|
|
||||||
+ " for " + t);
|
|
||||||
}
|
|
||||||
return new SavedSession(session, storageId);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveNewSession(Transaction txn, GroupId contactGroupId,
|
|
||||||
TransportId t, Session session) throws DbException {
|
|
||||||
Message m =
|
|
||||||
clientHelper.createMessageForStoringMetadata(contactGroupId);
|
|
||||||
db.addLocalMessage(txn, m, new Metadata(), false, false);
|
|
||||||
MessageId storageId = m.getId();
|
|
||||||
saveSession(txn, t, storageId, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveSession(Transaction txn, TransportId t,
|
|
||||||
MessageId storageId, Session session) throws DbException {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Saving session in state " + session.getState()
|
|
||||||
+ " for " + t);
|
|
||||||
}
|
|
||||||
BdfDictionary meta = sessionEncoder.encodeSession(session, t);
|
|
||||||
try {
|
|
||||||
clientHelper.mergeMessageMetadata(txn, storageId, meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message sendKeyMessage(Transaction txn, GroupId contactGroupId,
|
|
||||||
TransportId t, PublicKey publicKey) throws DbException {
|
|
||||||
Message m = messageEncoder.encodeKeyMessage(contactGroupId, t,
|
|
||||||
publicKey);
|
|
||||||
sendMessage(txn, t, m, KEY);
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message sendActivateMessage(Transaction txn,
|
|
||||||
GroupId contactGroupId, TransportId t, MessageId previousMessageId)
|
|
||||||
throws DbException {
|
|
||||||
Message m = messageEncoder.encodeActivateMessage(contactGroupId, t,
|
|
||||||
previousMessageId);
|
|
||||||
sendMessage(txn, t, m, ACTIVATE);
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessage(Transaction txn, TransportId t, Message m,
|
|
||||||
MessageType type) throws DbException {
|
|
||||||
BdfDictionary meta =
|
|
||||||
messageEncoder.encodeMessageMetadata(t, type, true);
|
|
||||||
try {
|
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
|
||||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
|
||||||
MAJOR_VERSION, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLocalPartyAlice(Transaction txn, Contact c)
|
|
||||||
throws DbException {
|
|
||||||
Author local = identityManager.getLocalAuthor(txn);
|
|
||||||
Author remote = c.getAuthor();
|
|
||||||
return compare(local.getId().getBytes(), remote.getId().getBytes()) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SavedSession {
|
|
||||||
|
|
||||||
private final Session session;
|
|
||||||
private final MessageId storageId;
|
|
||||||
|
|
||||||
private SavedSession(Session session, MessageId storageId) {
|
|
||||||
this.session = session;
|
|
||||||
this.storageId = storageId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
|
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
|
|
||||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
|
|
||||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MINOR_VERSION;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public class TransportKeyAgreementModule {
|
|
||||||
|
|
||||||
public static class EagerSingletons {
|
|
||||||
@Inject
|
|
||||||
TransportKeyAgreementManager transportKeyAgreementManager;
|
|
||||||
@Inject
|
|
||||||
TransportKeyAgreementValidator transportKeyAgreementValidator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
TransportKeyAgreementManager provideTransportKeyAgreementManager(
|
|
||||||
LifecycleManager lifecycleManager,
|
|
||||||
ValidationManager validationManager,
|
|
||||||
ContactManager contactManager,
|
|
||||||
ClientVersioningManager clientVersioningManager,
|
|
||||||
TransportKeyAgreementManagerImpl transportKeyAgreementManager) {
|
|
||||||
lifecycleManager.registerOpenDatabaseHook(transportKeyAgreementManager);
|
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
|
||||||
MAJOR_VERSION, transportKeyAgreementManager);
|
|
||||||
contactManager.registerContactHook(transportKeyAgreementManager);
|
|
||||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
|
||||||
MINOR_VERSION, transportKeyAgreementManager);
|
|
||||||
return transportKeyAgreementManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
TransportKeyAgreementValidator provideTransportKeyAgreementValidator(
|
|
||||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
|
||||||
Clock clock, MessageEncoder messageEncoder,
|
|
||||||
ValidationManager validationManager) {
|
|
||||||
TransportKeyAgreementValidator validator =
|
|
||||||
new TransportKeyAgreementValidator(clientHelper,
|
|
||||||
metadataEncoder, clock, messageEncoder);
|
|
||||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
|
||||||
validator);
|
|
||||||
return validator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
|
|
||||||
return messageEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
|
|
||||||
return sessionEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
|
|
||||||
return sessionParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
TransportKeyAgreementCrypto provideTransportKeyAgreementCrypto(
|
|
||||||
TransportKeyAgreementCryptoImpl transportKeyAgreementCrypto) {
|
|
||||||
return transportKeyAgreementCrypto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
|
||||||
import org.briarproject.bramble.api.client.BdfMessageValidator;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
|
||||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class TransportKeyAgreementValidator extends BdfMessageValidator {
|
|
||||||
|
|
||||||
private final MessageEncoder messageEncoder;
|
|
||||||
|
|
||||||
TransportKeyAgreementValidator(ClientHelper clientHelper,
|
|
||||||
MetadataEncoder metadataEncoder, Clock clock,
|
|
||||||
MessageEncoder messageEncoder) {
|
|
||||||
super(clientHelper, metadataEncoder, clock);
|
|
||||||
this.messageEncoder = messageEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
|
||||||
BdfList body) throws FormatException {
|
|
||||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
|
||||||
if (type == KEY) return validateKeyMessage(body);
|
|
||||||
else if (type == ACTIVATE) return validateActivateMessage(body);
|
|
||||||
else throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BdfMessageContext validateKeyMessage(BdfList body)
|
|
||||||
throws FormatException {
|
|
||||||
// Message type, transport ID, public key
|
|
||||||
checkSize(body, 3);
|
|
||||||
String transportId = body.getString(1);
|
|
||||||
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
|
||||||
byte[] publicKey = body.getRaw(2);
|
|
||||||
checkLength(publicKey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
|
||||||
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
|
|
||||||
new TransportId(transportId), KEY, false);
|
|
||||||
meta.put(MSG_KEY_PUBLIC_KEY, publicKey);
|
|
||||||
return new BdfMessageContext(meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BdfMessageContext validateActivateMessage(BdfList body)
|
|
||||||
throws FormatException {
|
|
||||||
// Message type, transport ID, previous message ID
|
|
||||||
checkSize(body, 3);
|
|
||||||
String transportId = body.getString(1);
|
|
||||||
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
|
||||||
byte[] previousMessageId = body.getRaw(2);
|
|
||||||
checkLength(previousMessageId, MessageId.LENGTH);
|
|
||||||
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
|
|
||||||
new TransportId(transportId), ACTIVATE, false);
|
|
||||||
MessageId dependency = new MessageId(previousMessageId);
|
|
||||||
return new BdfMessageContext(meta, singletonList(dependency));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,6 @@ import static java.util.Collections.emptyList;
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
|
|
||||||
@@ -174,8 +173,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
// Parse the new remote update
|
// Parse the new remote update
|
||||||
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
||||||
@@ -188,7 +187,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
// Load and parse the latest local update
|
// Load and parse the latest local update
|
||||||
if (latest.local == null) throw new DbException();
|
if (latest.local == null) throw new DbException();
|
||||||
@@ -242,7 +241,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeClientVersions(Transaction txn,
|
private void storeClientVersions(Transaction txn,
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getUnackedMessageBytesToSend(transaction, contactId));
|
db.getMessageBytesToSend(transaction, contactId, 123));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singleton;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
@@ -223,13 +222,21 @@ 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);
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -249,23 +256,37 @@ 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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);
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -284,28 +305,45 @@ 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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);
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -325,13 +363,21 @@ 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());
|
||||||
|
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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);
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -351,17 +397,19 @@ 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());
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// 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);
|
||||||
assertEquals(singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -379,12 +427,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 +434,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 +441,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 +449,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 +469,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 +698,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 +709,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 +721,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 +732,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 +743,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 +1947,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 +1967,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 +2599,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;
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase {
|
|||||||
ContactId aliceId = setUp(bob, bobIdentity,
|
ContactId aliceId = setUp(bob, bobIdentity,
|
||||||
aliceIdentity.getLocalAuthor(), false);
|
aliceIdentity.getLocalAuthor(), false);
|
||||||
// Sync Alice's client versions and transport properties
|
// Sync Alice's client versions and transport properties
|
||||||
read(bob, write(alice, bobId), 2);
|
read(bob, aliceId, write(alice, bobId), 2);
|
||||||
// Sync Bob's client versions and transport properties
|
// Sync Bob's client versions and transport properties
|
||||||
read(alice, write(bob, aliceId), 2);
|
read(alice, bobId, write(bob, aliceId), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId setUp(RemovableDriveIntegrationTestComponent device,
|
private ContactId setUp(RemovableDriveIntegrationTestComponent device,
|
||||||
@@ -92,7 +92,7 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
private void read(RemovableDriveIntegrationTestComponent device,
|
private void read(RemovableDriveIntegrationTestComponent device,
|
||||||
File file, int deliveries) throws Exception {
|
ContactId contactId, File file, int deliveries) throws Exception {
|
||||||
// Listen for message deliveries
|
// Listen for message deliveries
|
||||||
MessageDeliveryListener listener =
|
MessageDeliveryListener listener =
|
||||||
new MessageDeliveryListener(deliveries);
|
new MessageDeliveryListener(deliveries);
|
||||||
@@ -100,8 +100,8 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase {
|
|||||||
// Read the incoming stream
|
// Read the incoming stream
|
||||||
TransportProperties p = new TransportProperties();
|
TransportProperties p = new TransportProperties();
|
||||||
p.put(PROP_PATH, file.getAbsolutePath());
|
p.put(PROP_PATH, file.getAbsolutePath());
|
||||||
RemovableDriveTask reader =
|
RemovableDriveTask reader = device.getRemovableDriveManager()
|
||||||
device.getRemovableDriveManager().startReaderTask(p);
|
.startReaderTask(contactId, p);
|
||||||
CountDownLatch disposedLatch = new CountDownLatch(1);
|
CountDownLatch disposedLatch = new CountDownLatch(1);
|
||||||
reader.addObserver(state -> {
|
reader.addObserver(state -> {
|
||||||
if (state.isFinished()) disposedLatch.countDown();
|
if (state.isFinished()) disposedLatch.countDown();
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
|
|||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -231,8 +230,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TransportPropertyManagerImpl t = createInstance();
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(t.incomingMessage(txn, message, meta));
|
||||||
t.incomingMessage(txn, message, meta));
|
|
||||||
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +269,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TransportPropertyManagerImpl t = createInstance();
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(t.incomingMessage(txn, message, meta));
|
||||||
t.incomingMessage(txn, message, meta));
|
|
||||||
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,8 +308,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TransportPropertyManagerImpl t = createInstance();
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(t.incomingMessage(txn, message, meta));
|
||||||
t.incomingMessage(txn, message, meta));
|
|
||||||
assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,9 @@ import org.briarproject.bramble.test.DbExpectations;
|
|||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -44,19 +39,14 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
private final Executor dbExecutor = new ImmediateExecutor();
|
private final Executor dbExecutor = new ImmediateExecutor();
|
||||||
private final ContactId contactId = getContactId();
|
private final ContactId contactId = getContactId();
|
||||||
private final TransportId transportId = getTransportId();
|
private final TransportId transportId = getTransportId();
|
||||||
private final Ack ack =
|
private final Message message = getMessage(new GroupId(getRandomId()));
|
||||||
new Ack(singletonList(new MessageId(getRandomId())));
|
private final MessageId messageId = message.getId();
|
||||||
private final Message message = getMessage(new GroupId(getRandomId()),
|
|
||||||
MAX_MESSAGE_BODY_LENGTH);
|
|
||||||
private final Message message1 = getMessage(new GroupId(getRandomId()),
|
|
||||||
MAX_MESSAGE_BODY_LENGTH);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNothingToSend() throws Exception {
|
public void testNothingToSend() throws Exception {
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
false, streamWriter, recordWriter);
|
streamWriter, recordWriter);
|
||||||
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
Transaction noMsgTxn = new Transaction(null, false);
|
Transaction noMsgTxn = new Transaction(null, false);
|
||||||
|
|
||||||
@@ -73,8 +63,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
// No messages to send
|
// No messages to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noMsgTxn));
|
withNullableDbCallable(noMsgTxn));
|
||||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
with(any(int.class)), with(MAX_LATENCY));
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
// Send the end of stream marker
|
// Send the end of stream marker
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
@@ -85,45 +75,12 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
session.run();
|
session.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNothingToSendEagerly() throws Exception {
|
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
|
||||||
true, streamWriter, recordWriter);
|
|
||||||
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
|
||||||
Transaction noIdsTxn = new Transaction(null, true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
// Add listener
|
|
||||||
oneOf(eventBus).addListener(session);
|
|
||||||
// Send the protocol versions
|
|
||||||
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
|
||||||
// No acks to send
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(noAckTxn));
|
|
||||||
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(null));
|
|
||||||
// No messages to send
|
|
||||||
oneOf(db).transactionWithResult(with(true),
|
|
||||||
withDbCallable(noIdsTxn));
|
|
||||||
oneOf(db).getUnackedMessagesToSend(noIdsTxn, contactId);
|
|
||||||
will(returnValue(emptyMap()));
|
|
||||||
// Send the end of stream marker
|
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
|
||||||
// Remove listener
|
|
||||||
oneOf(eventBus).removeListener(session);
|
|
||||||
}});
|
|
||||||
|
|
||||||
session.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSomethingToSend() throws Exception {
|
public void testSomethingToSend() throws Exception {
|
||||||
|
Ack ack = new Ack(singletonList(messageId));
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
false, streamWriter, recordWriter);
|
streamWriter, recordWriter);
|
||||||
|
|
||||||
Transaction ackTxn = new Transaction(null, false);
|
Transaction ackTxn = new Transaction(null, false);
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
Transaction msgTxn = new Transaction(null, false);
|
Transaction msgTxn = new Transaction(null, false);
|
||||||
@@ -143,8 +100,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
// One message to send
|
// One message to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(msgTxn));
|
withNullableDbCallable(msgTxn));
|
||||||
oneOf(db).generateBatch(msgTxn, contactId,
|
oneOf(db).generateBatch(with(msgTxn), with(contactId),
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
with(any(int.class)), with(MAX_LATENCY));
|
||||||
will(returnValue(singletonList(message)));
|
will(returnValue(singletonList(message)));
|
||||||
oneOf(recordWriter).writeMessage(message);
|
oneOf(recordWriter).writeMessage(message);
|
||||||
// No more acks
|
// No more acks
|
||||||
@@ -155,8 +112,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
// No more messages
|
// No more messages
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noMsgTxn));
|
withNullableDbCallable(noMsgTxn));
|
||||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
with(any(int.class)), with(MAX_LATENCY));
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
// Send the end of stream marker
|
// Send the end of stream marker
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
@@ -166,63 +123,4 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
session.run();
|
session.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSomethingToSendEagerly() throws Exception {
|
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
|
||||||
true, streamWriter, recordWriter);
|
|
||||||
|
|
||||||
Map<MessageId, Integer> unacked = new LinkedHashMap<>();
|
|
||||||
unacked.put(message.getId(), message.getRawLength());
|
|
||||||
unacked.put(message1.getId(), message1.getRawLength());
|
|
||||||
|
|
||||||
Transaction ackTxn = new Transaction(null, false);
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
|
||||||
Transaction idsTxn = new Transaction(null, true);
|
|
||||||
Transaction msgTxn = new Transaction(null, false);
|
|
||||||
Transaction msgTxn1 = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
// Add listener
|
|
||||||
oneOf(eventBus).addListener(session);
|
|
||||||
// Send the protocol versions
|
|
||||||
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
|
||||||
// One ack to send
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(ackTxn));
|
|
||||||
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(ack));
|
|
||||||
oneOf(recordWriter).writeAck(ack);
|
|
||||||
// No more acks
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(noAckTxn));
|
|
||||||
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(null));
|
|
||||||
// Two messages to send
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(idsTxn));
|
|
||||||
oneOf(db).getUnackedMessagesToSend(idsTxn, contactId);
|
|
||||||
will(returnValue(unacked));
|
|
||||||
// Send the first message
|
|
||||||
oneOf(db).transactionWithResult(with(false),
|
|
||||||
withDbCallable(msgTxn));
|
|
||||||
oneOf(db).generateBatch(msgTxn, contactId,
|
|
||||||
singletonList(message.getId()), MAX_LATENCY);
|
|
||||||
will(returnValue(singletonList(message)));
|
|
||||||
oneOf(recordWriter).writeMessage(message);
|
|
||||||
// Send the second message
|
|
||||||
oneOf(db).transactionWithResult(with(false),
|
|
||||||
withDbCallable(msgTxn1));
|
|
||||||
oneOf(db).generateBatch(msgTxn1, contactId,
|
|
||||||
singletonList(message1.getId()), MAX_LATENCY);
|
|
||||||
will(returnValue(singletonList(message1)));
|
|
||||||
oneOf(recordWriter).writeMessage(message1);
|
|
||||||
// Send the end of stream marker
|
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
|
||||||
// Remove listener
|
|
||||||
oneOf(eventBus).removeListener(session);
|
|
||||||
}});
|
|
||||||
|
|
||||||
session.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||||
@@ -113,7 +111,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the first message
|
// Deliver the first message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -169,7 +167,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(new Metadata()));
|
will(returnValue(new Metadata()));
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn, message, metadata);
|
oneOf(hook).incomingMessage(txn, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn, messageId);
|
oneOf(db).getMessageDependents(txn, messageId);
|
||||||
@@ -189,7 +187,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver the dependent
|
// Deliver the dependent
|
||||||
oneOf(hook).incomingMessage(txn1, message2, metadata);
|
oneOf(hook).incomingMessage(txn1, message2, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId2);
|
oneOf(db).getMessageDependents(txn1, messageId2);
|
||||||
@@ -249,7 +247,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_SHARE));
|
will(returnValue(true));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -369,7 +367,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -434,7 +432,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -604,7 +602,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// The message has two pending dependents: 1 and 2
|
// The message has two pending dependents: 1 and 2
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
@@ -624,7 +622,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 1
|
// Deliver message 1
|
||||||
oneOf(hook).incomingMessage(txn2, message1, metadata);
|
oneOf(hook).incomingMessage(txn2, message1, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
|
oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
|
||||||
// Message 1 has one pending dependent: 3
|
// Message 1 has one pending dependent: 3
|
||||||
oneOf(db).getMessageDependents(txn2, messageId1);
|
oneOf(db).getMessageDependents(txn2, messageId1);
|
||||||
@@ -644,7 +642,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 2
|
// Deliver message 2
|
||||||
oneOf(hook).incomingMessage(txn3, message2, metadata);
|
oneOf(hook).incomingMessage(txn3, message2, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
|
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
|
||||||
// Message 2 has one pending dependent: 3 (same dependent as 1)
|
// Message 2 has one pending dependent: 3 (same dependent as 1)
|
||||||
oneOf(db).getMessageDependents(txn3, messageId2);
|
oneOf(db).getMessageDependents(txn3, messageId2);
|
||||||
@@ -664,7 +662,6 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 3
|
// Deliver message 3
|
||||||
oneOf(hook).incomingMessage(txn4, message3, metadata);
|
oneOf(hook).incomingMessage(txn4, message3, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
|
||||||
oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
|
oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
|
||||||
// Message 3 has one pending dependent: 4
|
// Message 3 has one pending dependent: 4
|
||||||
oneOf(db).getMessageDependents(txn4, messageId3);
|
oneOf(db).getMessageDependents(txn4, messageId3);
|
||||||
@@ -688,7 +685,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver message 4
|
// Deliver message 4
|
||||||
oneOf(hook).incomingMessage(txn6, message4, metadata);
|
oneOf(hook).incomingMessage(txn6, message4, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
|
oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
|
||||||
// Message 4 has no pending dependents
|
// Message 4 has no pending dependents
|
||||||
oneOf(db).getMessageDependents(txn6, messageId4);
|
oneOf(db).getMessageDependents(txn6, messageId4);
|
||||||
@@ -720,7 +717,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn1, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(ACCEPT_DO_NOT_SHARE));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn1, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class TestDuplexTransportConnection
|
|||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public TestDuplexTransportConnection(InputStream in, OutputStream out) {
|
public TestDuplexTransportConnection(InputStream in, OutputStream out) {
|
||||||
reader = new TestTransportConnectionReader(in);
|
reader = new TestTransportConnectionReader(in);
|
||||||
writer = new TestTransportConnectionWriter(out, false);
|
writer = new TestTransportConnectionWriter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,13 +15,10 @@ public class TestTransportConnectionWriter
|
|||||||
implements TransportConnectionWriter {
|
implements TransportConnectionWriter {
|
||||||
|
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
private final boolean lossyAndCheap;
|
|
||||||
private final CountDownLatch disposed = new CountDownLatch(1);
|
private final CountDownLatch disposed = new CountDownLatch(1);
|
||||||
|
|
||||||
public TestTransportConnectionWriter(OutputStream out,
|
public TestTransportConnectionWriter(OutputStream out) {
|
||||||
boolean lossyAndCheap) {
|
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.lossyAndCheap = lossyAndCheap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountDownLatch getDisposedLatch() {
|
public CountDownLatch getDisposedLatch() {
|
||||||
@@ -38,11 +35,6 @@ public class TestTransportConnectionWriter
|
|||||||
return 60_000;
|
return 60_000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return lossyAndCheap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() {
|
public OutputStream getOutputStream() {
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
@@ -72,7 +71,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final SecretKey rootKey = getSecretKey();
|
private final SecretKey rootKey = getSecretKey();
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
|
|
||||||
private KeyManagerImpl keyManager;
|
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor,
|
||||||
|
pluginConfig, transportKeyManagerFactory, transportCrypto);
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void testStartService() throws Exception {
|
public void testStartService() throws Exception {
|
||||||
@@ -83,25 +83,18 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
singletonList(pluginFactory);
|
singletonList(pluginFactory);
|
||||||
int maxLatency = 1337;
|
int maxLatency = 1337;
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
allowing(pluginConfig).getSimplexFactories();
|
oneOf(pluginConfig).getSimplexFactories();
|
||||||
will(returnValue(factories));
|
will(returnValue(factories));
|
||||||
allowing(pluginFactory).getId();
|
oneOf(pluginFactory).getId();
|
||||||
will(returnValue(transportId));
|
will(returnValue(transportId));
|
||||||
allowing(pluginFactory).getMaxLatency();
|
oneOf(pluginFactory).getMaxLatency();
|
||||||
will(returnValue(maxLatency));
|
will(returnValue(maxLatency));
|
||||||
allowing(pluginConfig).getDuplexFactories();
|
oneOf(db).addTransport(txn, transportId, maxLatency);
|
||||||
will(returnValue(emptyList()));
|
|
||||||
oneOf(transportKeyManagerFactory)
|
oneOf(transportKeyManagerFactory)
|
||||||
.createTransportKeyManager(transportId, maxLatency);
|
.createTransportKeyManager(transportId, maxLatency);
|
||||||
will(returnValue(transportKeyManager));
|
will(returnValue(transportKeyManager));
|
||||||
}});
|
oneOf(pluginConfig).getDuplexFactories();
|
||||||
|
|
||||||
keyManager = new KeyManagerImpl(db, executor,
|
|
||||||
pluginConfig, transportCrypto, transportKeyManagerFactory);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
oneOf(db).addTransport(txn, transportId, maxLatency);
|
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(transportKeyManager).start(txn);
|
oneOf(transportKeyManager).start(txn);
|
||||||
}});
|
}});
|
||||||
@@ -242,37 +235,4 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
keyManager.eventOccurred(event);
|
keyManager.eventOccurred(event);
|
||||||
executor.runUntilIdle();
|
executor.runUntilIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddMultipleRotationKeySets() throws Exception {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
boolean alice = random.nextBoolean();
|
|
||||||
boolean active = random.nextBoolean();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(transportKeyManager).addRotationKeys(txn, contactId,
|
|
||||||
rootKey, timestamp, alice, active);
|
|
||||||
will(returnValue(keySetId));
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertEquals(singletonMap(transportId, keySetId),
|
|
||||||
keyManager.addRotationKeys(txn, contactId, rootKey, timestamp,
|
|
||||||
alice, active));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddSingleRotationKeySet() throws Exception {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
boolean alice = random.nextBoolean();
|
|
||||||
boolean active = random.nextBoolean();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(transportKeyManager).addRotationKeys(txn, contactId,
|
|
||||||
rootKey, timestamp, alice, active);
|
|
||||||
will(returnValue(keySetId));
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertEquals(keySetId, keyManager.addRotationKeys(txn, contactId,
|
|
||||||
transportId, rootKey, timestamp, alice, active));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,554 +0,0 @@
|
|||||||
package org.briarproject.bramble.transport.agreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
|
||||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
|
||||||
import org.jmock.Expectations;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import static java.lang.Math.min;
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static org.briarproject.bramble.api.Bytes.compare;
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
|
||||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
|
|
||||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
|
||||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
|
|
||||||
public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase {
|
|
||||||
|
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
|
||||||
private final MetadataParser metadataParser =
|
|
||||||
context.mock(MetadataParser.class);
|
|
||||||
private final ContactGroupFactory contactGroupFactory =
|
|
||||||
context.mock(ContactGroupFactory.class);
|
|
||||||
private final ClientVersioningManager clientVersioningManager =
|
|
||||||
context.mock(ClientVersioningManager.class);
|
|
||||||
private final IdentityManager identityManager =
|
|
||||||
context.mock(IdentityManager.class);
|
|
||||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
|
||||||
private final MessageEncoder messageEncoder =
|
|
||||||
context.mock(MessageEncoder.class);
|
|
||||||
private final SessionEncoder sessionEncoder =
|
|
||||||
context.mock(SessionEncoder.class);
|
|
||||||
private final SessionParser sessionParser =
|
|
||||||
context.mock(SessionParser.class);
|
|
||||||
private final TransportKeyAgreementCrypto crypto =
|
|
||||||
context.mock(TransportKeyAgreementCrypto.class);
|
|
||||||
private final PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
|
||||||
private final SimplexPluginFactory simplexFactory =
|
|
||||||
context.mock(SimplexPluginFactory.class);
|
|
||||||
private final DuplexPluginFactory duplexFactory =
|
|
||||||
context.mock(DuplexPluginFactory.class);
|
|
||||||
|
|
||||||
private final TransportId simplexTransportId = getTransportId();
|
|
||||||
private final TransportId duplexTransportId = getTransportId();
|
|
||||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
private final Contact contact = getContact();
|
|
||||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
|
||||||
private final boolean alice = compare(localAuthor.getId().getBytes(),
|
|
||||||
contact.getAuthor().getId().getBytes()) < 0;
|
|
||||||
private final KeyPair localKeyPair =
|
|
||||||
new KeyPair(getAgreementPublicKey(), getAgreementPrivateKey());
|
|
||||||
private final PublicKey remotePublicKey = getAgreementPublicKey();
|
|
||||||
private final SecretKey rootKey = getSecretKey();
|
|
||||||
private final KeySetId keySetId = new KeySetId(123);
|
|
||||||
|
|
||||||
private final Message storageMessage = getMessage(contactGroup.getId());
|
|
||||||
private final Message localKeyMessage = getMessage(contactGroup.getId());
|
|
||||||
private final Message localActivateMessage =
|
|
||||||
getMessage(contactGroup.getId());
|
|
||||||
private final Message remoteKeyMessage = getMessage(contactGroup.getId());
|
|
||||||
private final Message remoteActivateMessage =
|
|
||||||
getMessage(contactGroup.getId());
|
|
||||||
private final long localTimestamp = localKeyMessage.getTimestamp();
|
|
||||||
private final long remoteTimestamp = remoteKeyMessage.getTimestamp();
|
|
||||||
|
|
||||||
// These query and metadata dictionaries are handled by the manager without
|
|
||||||
// inspecting their contents, so we can use empty dictionaries for testing
|
|
||||||
private final BdfDictionary sessionQuery = new BdfDictionary();
|
|
||||||
private final BdfDictionary sessionMeta = new BdfDictionary();
|
|
||||||
private final BdfDictionary localKeyMeta = new BdfDictionary();
|
|
||||||
private final BdfDictionary localActivateMeta = new BdfDictionary();
|
|
||||||
|
|
||||||
// The manager doesn't use the incoming message body, so it can be empty
|
|
||||||
private final BdfList remoteMessageBody = new BdfList();
|
|
||||||
|
|
||||||
private final BdfDictionary remoteKeyMeta = BdfDictionary.of(
|
|
||||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, KEY.getValue()),
|
|
||||||
new BdfEntry(MSG_KEY_TRANSPORT_ID,
|
|
||||||
simplexTransportId.getString()),
|
|
||||||
new BdfEntry(MSG_KEY_PUBLIC_KEY, remotePublicKey.getEncoded()));
|
|
||||||
|
|
||||||
private final BdfDictionary remoteActivateMeta = BdfDictionary.of(
|
|
||||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, ACTIVATE.getValue()),
|
|
||||||
new BdfEntry(MSG_KEY_TRANSPORT_ID,
|
|
||||||
simplexTransportId.getString()));
|
|
||||||
|
|
||||||
private TransportKeyAgreementManagerImpl manager;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(pluginConfig).getSimplexFactories();
|
|
||||||
will(returnValue(singletonList(simplexFactory)));
|
|
||||||
oneOf(simplexFactory).getId();
|
|
||||||
will(returnValue(simplexTransportId));
|
|
||||||
oneOf(pluginConfig).getDuplexFactories();
|
|
||||||
will(returnValue(singletonList(duplexFactory)));
|
|
||||||
oneOf(duplexFactory).getId();
|
|
||||||
will(returnValue(duplexTransportId));
|
|
||||||
oneOf(contactGroupFactory)
|
|
||||||
.createLocalGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
will(returnValue(localGroup));
|
|
||||||
}});
|
|
||||||
|
|
||||||
manager = new TransportKeyAgreementManagerImpl(db, clientHelper,
|
|
||||||
metadataParser, contactGroupFactory, clientVersioningManager,
|
|
||||||
identityManager, keyManager, messageEncoder, sessionEncoder,
|
|
||||||
sessionParser, crypto, pluginConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreatesContactGroupAtStartupIfLocalGroupDoesNotExist()
|
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).getContacts(txn);
|
|
||||||
will(returnValue(singletonList(contact)));
|
|
||||||
// The local group doesn't exist so we need to create contact groups
|
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
|
||||||
will(returnValue(false));
|
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
|
||||||
// Create the contact group and set it up
|
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
|
||||||
MAJOR_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
|
||||||
oneOf(db).addGroup(txn, contactGroup);
|
|
||||||
oneOf(clientHelper)
|
|
||||||
.setContactId(txn, contactGroup.getId(), contact.getId());
|
|
||||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
|
||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
|
||||||
will(returnValue(VISIBLE));
|
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
|
||||||
contactGroup.getId(), VISIBLE);
|
|
||||||
// We already have keys for both transports
|
|
||||||
oneOf(db).getTransportsWithKeys(txn);
|
|
||||||
will(returnValue(singletonMap(contact.getId(),
|
|
||||||
asList(simplexTransportId, duplexTransportId))));
|
|
||||||
}});
|
|
||||||
|
|
||||||
manager.onDatabaseOpened(txn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoesNotCreateContactGroupAtStartupIfLocalGroupExists()
|
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).getContacts(txn);
|
|
||||||
will(returnValue(singletonList(contact)));
|
|
||||||
// The local group exists so we don't need to create contact groups
|
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
|
||||||
will(returnValue(true));
|
|
||||||
// We already have keys for both transports
|
|
||||||
oneOf(db).getTransportsWithKeys(txn);
|
|
||||||
will(returnValue(singletonMap(contact.getId(),
|
|
||||||
asList(simplexTransportId, duplexTransportId))));
|
|
||||||
}});
|
|
||||||
|
|
||||||
manager.onDatabaseOpened(txn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartsSessionAtStartup() throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).getContacts(txn);
|
|
||||||
will(returnValue(singletonList(contact)));
|
|
||||||
// The local group exists so we don't need to create contact groups
|
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
|
||||||
will(returnValue(true));
|
|
||||||
// We need keys for the simplex transport
|
|
||||||
oneOf(db).getTransportsWithKeys(txn);
|
|
||||||
will(returnValue(singletonMap(contact.getId(),
|
|
||||||
singletonList(duplexTransportId))));
|
|
||||||
// Get the contact group ID
|
|
||||||
oneOf(contactGroupFactory)
|
|
||||||
.createContactGroup(CLIENT_ID, MAJOR_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
|
||||||
}});
|
|
||||||
|
|
||||||
// Check whether a session exists - it doesn't
|
|
||||||
expectSessionDoesNotExist(txn);
|
|
||||||
// Generate the local key pair
|
|
||||||
expectGenerateLocalKeyPair();
|
|
||||||
// Send a key message
|
|
||||||
expectSendKeyMessage(txn);
|
|
||||||
// Save the session
|
|
||||||
expectCreateStorageMessage(txn);
|
|
||||||
AtomicReference<Session> savedSession = expectSaveSession(txn);
|
|
||||||
|
|
||||||
manager.onDatabaseOpened(txn);
|
|
||||||
|
|
||||||
assertEquals(AWAIT_KEY, savedSession.get().getState());
|
|
||||||
assertEquals(localKeyMessage.getId(),
|
|
||||||
savedSession.get().getLastLocalMessageId());
|
|
||||||
assertEquals(localKeyPair, savedSession.get().getLocalKeyPair());
|
|
||||||
assertEquals(Long.valueOf(localTimestamp),
|
|
||||||
savedSession.get().getLocalTimestamp());
|
|
||||||
assertNull(savedSession.get().getKeySetId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDefersMessageIfTransportIsNotSupported() throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
TransportId unknownTransportId = getTransportId();
|
|
||||||
BdfDictionary meta = new BdfDictionary(remoteKeyMeta);
|
|
||||||
meta.put(MSG_KEY_TRANSPORT_ID, unknownTransportId.getString());
|
|
||||||
|
|
||||||
assertEquals(DEFER, manager.incomingMessage(txn, remoteKeyMessage,
|
|
||||||
remoteMessageBody, meta));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAcceptsKeyMessageInAwaitKeyState() throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
Session loadedSession = new Session(AWAIT_KEY,
|
|
||||||
localKeyMessage.getId(), localKeyPair, localTimestamp, null);
|
|
||||||
|
|
||||||
// Check whether a session exists - it does
|
|
||||||
expectLoadSession(txn, loadedSession);
|
|
||||||
// Load the contact ID
|
|
||||||
expectLoadContactId(txn);
|
|
||||||
// Check whether we already have keys - we don't
|
|
||||||
expectKeysExist(txn, false);
|
|
||||||
// Parse the remote public key
|
|
||||||
expectParseRemotePublicKey();
|
|
||||||
// Derive and store the transport keys
|
|
||||||
expectDeriveAndStoreTransportKeys(txn);
|
|
||||||
// Send an activate message
|
|
||||||
expectSendActivateMessage(txn);
|
|
||||||
// Save the session
|
|
||||||
AtomicReference<Session> savedSession = expectSaveSession(txn);
|
|
||||||
|
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn,
|
|
||||||
remoteKeyMessage, remoteMessageBody, remoteKeyMeta));
|
|
||||||
|
|
||||||
assertEquals(AWAIT_ACTIVATE, savedSession.get().getState());
|
|
||||||
assertEquals(localActivateMessage.getId(),
|
|
||||||
savedSession.get().getLastLocalMessageId());
|
|
||||||
assertNull(savedSession.get().getLocalKeyPair());
|
|
||||||
assertNull(savedSession.get().getLocalTimestamp());
|
|
||||||
assertEquals(keySetId, savedSession.get().getKeySetId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAcceptsKeyMessageIfWeHaveTransportKeysButNoSession()
|
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
// Check whether a session exists - it doesn't
|
|
||||||
expectSessionDoesNotExist(txn);
|
|
||||||
// Load the contact ID
|
|
||||||
expectLoadContactId(txn);
|
|
||||||
// Check whether we already have keys - we do
|
|
||||||
expectKeysExist(txn, true);
|
|
||||||
// Generate the local key pair
|
|
||||||
expectGenerateLocalKeyPair();
|
|
||||||
// Parse the remote public key
|
|
||||||
expectParseRemotePublicKey();
|
|
||||||
// Send a key message
|
|
||||||
expectSendKeyMessage(txn);
|
|
||||||
// Derive and store the transport keys
|
|
||||||
expectDeriveAndStoreTransportKeys(txn);
|
|
||||||
// Send an activate message
|
|
||||||
expectSendActivateMessage(txn);
|
|
||||||
// Save the session
|
|
||||||
expectCreateStorageMessage(txn);
|
|
||||||
AtomicReference<Session> savedSession = expectSaveSession(txn);
|
|
||||||
|
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn,
|
|
||||||
remoteKeyMessage, remoteMessageBody, remoteKeyMeta));
|
|
||||||
|
|
||||||
assertEquals(AWAIT_ACTIVATE, savedSession.get().getState());
|
|
||||||
assertEquals(localActivateMessage.getId(),
|
|
||||||
savedSession.get().getLastLocalMessageId());
|
|
||||||
assertNull(savedSession.get().getLocalKeyPair());
|
|
||||||
assertNull(savedSession.get().getLocalTimestamp());
|
|
||||||
assertEquals(keySetId, savedSession.get().getKeySetId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRejectsKeyMessageInAwaitActivateState() throws Exception {
|
|
||||||
Session loadedSession = new Session(AWAIT_ACTIVATE,
|
|
||||||
localActivateMessage.getId(), null, null, keySetId);
|
|
||||||
testRejectsKeyMessageWithExistingSession(loadedSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRejectsKeyMessageInActivatedState() throws Exception {
|
|
||||||
Session loadedSession = new Session(ACTIVATED,
|
|
||||||
localActivateMessage.getId(), null, null, null);
|
|
||||||
testRejectsKeyMessageWithExistingSession(loadedSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testRejectsKeyMessageWithExistingSession(Session loadedSession)
|
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
// Check whether a session exists - it does
|
|
||||||
expectLoadSession(txn, loadedSession);
|
|
||||||
// Load the contact ID
|
|
||||||
expectLoadContactId(txn);
|
|
||||||
// Check whether we already have keys - we don't
|
|
||||||
expectKeysExist(txn, false);
|
|
||||||
|
|
||||||
assertEquals(REJECT, manager.incomingMessage(txn,
|
|
||||||
remoteKeyMessage, remoteMessageBody, remoteKeyMeta));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAcceptsActivateMessageInAwaitActivateState()
|
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
Session loadedSession = new Session(AWAIT_ACTIVATE,
|
|
||||||
localActivateMessage.getId(), null, null, keySetId);
|
|
||||||
|
|
||||||
// Check whether a session exists - it does
|
|
||||||
expectLoadSession(txn, loadedSession);
|
|
||||||
|
|
||||||
// Activate the transport keys
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(keyManager).activateKeys(txn,
|
|
||||||
singletonMap(simplexTransportId, keySetId));
|
|
||||||
}});
|
|
||||||
|
|
||||||
// Save the session
|
|
||||||
AtomicReference<Session> savedSession = expectSaveSession(txn);
|
|
||||||
|
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn,
|
|
||||||
remoteActivateMessage, remoteMessageBody, remoteActivateMeta));
|
|
||||||
|
|
||||||
assertEquals(ACTIVATED, savedSession.get().getState());
|
|
||||||
assertEquals(localActivateMessage.getId(),
|
|
||||||
savedSession.get().getLastLocalMessageId());
|
|
||||||
assertNull(savedSession.get().getLocalKeyPair());
|
|
||||||
assertNull(savedSession.get().getLocalTimestamp());
|
|
||||||
assertNull(savedSession.get().getKeySetId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRejectsActivateMessageWithNoSession() throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
// Check whether a session exists - it doesn't
|
|
||||||
expectSessionDoesNotExist(txn);
|
|
||||||
|
|
||||||
assertEquals(REJECT, manager.incomingMessage(txn,
|
|
||||||
remoteActivateMessage, remoteMessageBody, remoteActivateMeta));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRejectsActivateMessageInAwaitKeyState() throws Exception {
|
|
||||||
Session loadedSession = new Session(AWAIT_KEY,
|
|
||||||
localKeyMessage.getId(), localKeyPair, localTimestamp, null);
|
|
||||||
testRejectsActivateMessageWithExistingSession(loadedSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRejectsActivateMessageInActivatedState() throws Exception {
|
|
||||||
Session loadedSession = new Session(ACTIVATED,
|
|
||||||
localActivateMessage.getId(), null, null, null);
|
|
||||||
testRejectsActivateMessageWithExistingSession(loadedSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testRejectsActivateMessageWithExistingSession(
|
|
||||||
Session loadedSession) throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
// Check whether a session exists - it does
|
|
||||||
expectLoadSession(txn, loadedSession);
|
|
||||||
|
|
||||||
assertEquals(REJECT, manager.incomingMessage(txn,
|
|
||||||
remoteActivateMessage, remoteMessageBody, remoteActivateMeta));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectSessionDoesNotExist(Transaction txn) throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(sessionEncoder).getSessionQuery(simplexTransportId);
|
|
||||||
will(returnValue(sessionQuery));
|
|
||||||
oneOf(clientHelper)
|
|
||||||
.getMessageIds(txn, contactGroup.getId(), sessionQuery);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectLoadSession(Transaction txn, Session loadedSession)
|
|
||||||
throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(sessionEncoder).getSessionQuery(simplexTransportId);
|
|
||||||
will(returnValue(sessionQuery));
|
|
||||||
oneOf(clientHelper)
|
|
||||||
.getMessageIds(txn, contactGroup.getId(), sessionQuery);
|
|
||||||
will(returnValue(singletonList(storageMessage.getId())));
|
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
|
||||||
storageMessage.getId());
|
|
||||||
will(returnValue(sessionMeta));
|
|
||||||
oneOf(sessionParser).parseSession(sessionMeta);
|
|
||||||
will(returnValue(loadedSession));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectSendKeyMessage(Transaction txn) throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(),
|
|
||||||
simplexTransportId, localKeyPair.getPublic());
|
|
||||||
will(returnValue(localKeyMessage));
|
|
||||||
oneOf(messageEncoder)
|
|
||||||
.encodeMessageMetadata(simplexTransportId, KEY, true);
|
|
||||||
will(returnValue(localKeyMeta));
|
|
||||||
oneOf(clientHelper).addLocalMessage(txn, localKeyMessage,
|
|
||||||
localKeyMeta, true, false);
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectSendActivateMessage(Transaction txn) throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(),
|
|
||||||
simplexTransportId, localKeyMessage.getId());
|
|
||||||
will(returnValue(localActivateMessage));
|
|
||||||
oneOf(messageEncoder)
|
|
||||||
.encodeMessageMetadata(simplexTransportId, ACTIVATE, true);
|
|
||||||
will(returnValue(localActivateMeta));
|
|
||||||
oneOf(clientHelper).addLocalMessage(txn, localActivateMessage,
|
|
||||||
localActivateMeta, true, false);
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectCreateStorageMessage(Transaction txn) throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(clientHelper)
|
|
||||||
.createMessageForStoringMetadata(contactGroup.getId());
|
|
||||||
will(returnValue(storageMessage));
|
|
||||||
oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(),
|
|
||||||
false, false);
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AtomicReference<Session> expectSaveSession(Transaction txn)
|
|
||||||
throws Exception {
|
|
||||||
AtomicReference<Session> savedSession = new AtomicReference<>();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(sessionEncoder).encodeSession(with(any(Session.class)),
|
|
||||||
with(simplexTransportId));
|
|
||||||
will(doAll(
|
|
||||||
new CaptureArgumentAction<>(savedSession, Session.class, 0),
|
|
||||||
returnValue(sessionMeta)));
|
|
||||||
oneOf(clientHelper).mergeMessageMetadata(txn,
|
|
||||||
storageMessage.getId(), sessionMeta);
|
|
||||||
}});
|
|
||||||
|
|
||||||
return savedSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectLoadContactId(Transaction txn) throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
|
||||||
will(returnValue(contact.getId()));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectGenerateLocalKeyPair() {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(crypto).generateKeyPair();
|
|
||||||
will(returnValue(localKeyPair));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectParseRemotePublicKey() throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded());
|
|
||||||
will(returnValue(remotePublicKey));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectDeriveAndStoreTransportKeys(Transaction txn)
|
|
||||||
throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey);
|
|
||||||
will(returnValue(rootKey));
|
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
|
||||||
will(returnValue(contact));
|
|
||||||
oneOf(identityManager).getLocalAuthor(txn);
|
|
||||||
will(returnValue(localAuthor));
|
|
||||||
oneOf(keyManager).addRotationKeys(txn, contact.getId(),
|
|
||||||
simplexTransportId, rootKey,
|
|
||||||
min(localTimestamp, remoteTimestamp), alice, false);
|
|
||||||
will(returnValue(keySetId));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectKeysExist(Transaction txn, boolean exist)
|
|
||||||
throws Exception {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).containsTransportKeys(txn, contact.getId(),
|
|
||||||
simplexTransportId);
|
|
||||||
will(returnValue(exist));
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,7 +31,6 @@ import static java.util.Collections.singletonMap;
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
@@ -42,6 +41,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
|||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -419,8 +419,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -465,8 +464,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -498,8 +496,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -582,8 +579,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -653,8 +649,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
ClientVersioningManagerImpl c = createInstance();
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -437,6 +437,15 @@
|
|||||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
|
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="org.briarproject.briar.android.removabledrive.RemovableDriveActivity"
|
||||||
|
android:label="TODO Removable Drive"
|
||||||
|
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".android.contact.add.remote.PendingContactListActivity"
|
android:name=".android.contact.add.remote.PendingContactListActivity"
|
||||||
android:label="@string/pending_contact_requests"
|
android:label="@string/pending_contact_requests"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
|
|||||||
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
||||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
|
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
|
||||||
|
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
|
||||||
import org.briarproject.briar.android.reporting.CrashFragment;
|
import org.briarproject.briar.android.reporting.CrashFragment;
|
||||||
import org.briarproject.briar.android.reporting.CrashReportActivity;
|
import org.briarproject.briar.android.reporting.CrashReportActivity;
|
||||||
import org.briarproject.briar.android.reporting.ReportFormFragment;
|
import org.briarproject.briar.android.reporting.ReportFormFragment;
|
||||||
@@ -176,6 +177,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(CrashReportActivity crashReportActivity);
|
void inject(CrashReportActivity crashReportActivity);
|
||||||
|
|
||||||
|
void inject(RemovableDriveActivity activity);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
|
|
||||||
void inject(SetupFragment fragment);
|
void inject(SetupFragment fragment);
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ public interface RequestCodes {
|
|||||||
int REQUEST_ATTACH_IMAGE = 13;
|
int REQUEST_ATTACH_IMAGE = 13;
|
||||||
int REQUEST_SAVE_ATTACHMENT = 14;
|
int REQUEST_SAVE_ATTACHMENT = 14;
|
||||||
int REQUEST_AVATAR_IMAGE = 15;
|
int REQUEST_AVATAR_IMAGE = 15;
|
||||||
|
int REQUEST_REMOVABLE_DRIVE_WRITE = 16;
|
||||||
|
int REQUEST_REMOVABLE_DRIVE_READ = 17;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.event.EventBus;
|
|||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
@@ -54,6 +53,7 @@ import org.briarproject.briar.android.forum.ForumActivity;
|
|||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||||
|
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.android.view.ImagePreview;
|
import org.briarproject.briar.android.view.ImagePreview;
|
||||||
@@ -421,6 +421,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
} else if (itemId == R.id.action_social_remove_person) {
|
} else if (itemId == R.id.action_social_remove_person) {
|
||||||
askToRemoveContact();
|
askToRemoveContact();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (itemId == R.id.action_removable_drive_write) {
|
||||||
|
Intent intent = new Intent(this, RemovableDriveActivity.class);
|
||||||
|
intent.putExtra(CONTACT_ID, contactId.getInt());
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package org.briarproject.briar.android.removabledrive;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
|
|
||||||
|
// TODO 19 will be our requirement for sneakernet support, right. The file apis
|
||||||
|
// used require this.
|
||||||
|
@RequiresApi(api = 19)
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class RemovableDriveActivity extends BriarActivity
|
||||||
|
implements BaseFragmentListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
private RemovableDriveViewModel viewModel;
|
||||||
|
private TextView text;
|
||||||
|
private Button writeButton;
|
||||||
|
private Button readButton;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(RemovableDriveViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_removable_drive);
|
||||||
|
text = findViewById(R.id.sneaker_text);
|
||||||
|
writeButton = findViewById(R.id.sneaker_write);
|
||||||
|
readButton = findViewById(R.id.sneaker_read);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
int contactId = intent.getIntExtra(CONTACT_ID, -1);
|
||||||
|
if (contactId == -1) {
|
||||||
|
writeButton.setEnabled(false);
|
||||||
|
readButton.setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO we can pass an extra named DocumentsContract.EXTRA_INITIAL_URI
|
||||||
|
// to have the filepicker start on the usb-stick -- if get hold of URI
|
||||||
|
// of the same. USB manager API?
|
||||||
|
// Overall, passing this extra requires extending the ready-made
|
||||||
|
// contracts and overriding createIntent.
|
||||||
|
|
||||||
|
writeButton.setText("Write for contactId " + contactId);
|
||||||
|
ActivityResultLauncher<String> createDocument =
|
||||||
|
registerForActivityResult(
|
||||||
|
new ActivityResultContracts.CreateDocument(),
|
||||||
|
uri -> write(contactId, uri));
|
||||||
|
writeButton.setOnClickListener(
|
||||||
|
v -> createDocument.launch(viewModel.getFileName()));
|
||||||
|
|
||||||
|
readButton.setText("Read for contactId " + contactId);
|
||||||
|
ActivityResultLauncher<String> getContent =
|
||||||
|
registerForActivityResult(
|
||||||
|
new ActivityResultContracts.GetContent(),
|
||||||
|
uri -> read(contactId, uri));
|
||||||
|
readButton.setOnClickListener(
|
||||||
|
v -> getContent.launch("application/octet-stream"));
|
||||||
|
|
||||||
|
LiveData<State> state;
|
||||||
|
state = viewModel.ongoingWrite(new ContactId(contactId));
|
||||||
|
if (state == null) {
|
||||||
|
writeButton.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
say("\nOngoing write:");
|
||||||
|
writeButton.setEnabled(false);
|
||||||
|
state.observe(this, (taskState) -> handleState("write", taskState));
|
||||||
|
}
|
||||||
|
state = viewModel.ongoingRead(new ContactId(contactId));
|
||||||
|
if (state == null) {
|
||||||
|
readButton.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
say("\nOngoing read:");
|
||||||
|
readButton.setEnabled(false);
|
||||||
|
state.observe(this, (taskState) -> handleState("read", taskState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(int contactId, @Nullable Uri uri) {
|
||||||
|
if (contactId == -1) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (uri == null) {
|
||||||
|
say("no URI picked for write");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
say("\nWriting to URI: " + uri);
|
||||||
|
writeButton.setEnabled(false);
|
||||||
|
LiveData<State> state = viewModel.write(new ContactId(contactId), uri);
|
||||||
|
state.observe(this, (taskState) -> handleState("write", taskState));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void read(int contactId, @Nullable Uri uri) {
|
||||||
|
if (contactId == -1) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (uri == null) {
|
||||||
|
say("no URI picked for read");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
say("\nReading from URI: " + uri);
|
||||||
|
readButton.setEnabled(false);
|
||||||
|
LiveData<State> state = viewModel.read(new ContactId(contactId), uri);
|
||||||
|
state.observe(this, (taskState) -> handleState("read", taskState));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleState(String action, State taskState) {
|
||||||
|
say(String.format(Locale.getDefault(),
|
||||||
|
"%s: bytes done: %d of %d. %s. %s.",
|
||||||
|
action, taskState.getDone(), taskState.getTotal(),
|
||||||
|
taskState.isFinished() ? "Finished" : "Ongoing",
|
||||||
|
taskState.isFinished() ?
|
||||||
|
(taskState.isSuccess() ? "Success" : "Failed") : ".."));
|
||||||
|
if (taskState.isFinished()) {
|
||||||
|
if (action.equals("write")) {
|
||||||
|
writeButton.setEnabled(true);
|
||||||
|
} else if (action.equals("read")) {
|
||||||
|
readButton.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void say(String txt) {
|
||||||
|
String time = new SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||||
|
.format(new Date());
|
||||||
|
txt = String.format("%s %s\n", time, txt);
|
||||||
|
text.setText(text.getText().toString().concat(txt));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,15 +59,15 @@ class RemovableDriveViewModel extends AndroidViewModel {
|
|||||||
return observe(manager.startWriterTask(contactId, p));
|
return observe(manager.startWriterTask(contactId, p));
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<State> read(Uri uri) {
|
LiveData<State> read(ContactId contactId, Uri uri) {
|
||||||
TransportProperties p = new TransportProperties();
|
TransportProperties p = new TransportProperties();
|
||||||
p.put(PROP_URI, uri.toString());
|
p.put(PROP_URI, uri.toString());
|
||||||
return observe(manager.startReaderTask(p));
|
return observe(manager.startReaderTask(contactId, p));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
LiveData<State> ongoingWrite() {
|
LiveData<State> ongoingWrite(ContactId contactId) {
|
||||||
RemovableDriveTask task = manager.getCurrentWriterTask();
|
RemovableDriveTask task = manager.getCurrentWriterTask(contactId);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -75,8 +75,8 @@ class RemovableDriveViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
LiveData<State> ongoingRead() {
|
LiveData<State> ongoingRead(ContactId contactId) {
|
||||||
RemovableDriveTask task = manager.getCurrentReaderTask();
|
RemovableDriveTask task = manager.getCurrentReaderTask(contactId);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".android.removabledrive.RemovableDriveActivity">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/margin_large">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/sneaker_write"
|
||||||
|
style="@style/BriarButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text=""
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:enabled="true" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/sneaker_read"
|
||||||
|
style="@style/BriarButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text=""
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sneaker_write"
|
||||||
|
tools:enabled="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sneaker_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/margin_large"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sneaker_read" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -40,4 +40,9 @@
|
|||||||
android:title="@string/delete_contact"
|
android:title="@string/delete_contact"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_removable_drive_write"
|
||||||
|
android:title="Transfer via removable drive"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
|
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
|
||||||
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
||||||
@@ -125,8 +124,8 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
Group ourGroup = getOurGroup(txn);
|
Group ourGroup = getOurGroup(txn);
|
||||||
if (m.getGroupId().equals(ourGroup.getId())) {
|
if (m.getGroupId().equals(ourGroup.getId())) {
|
||||||
throw new InvalidMessageException(
|
throw new InvalidMessageException(
|
||||||
@@ -145,7 +144,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
|||||||
// We've already received a newer update - delete this one
|
// We've already received a newer update - delete this one
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false; // don't broadcast update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContactId contactId = getContactId(txn, m.getGroupId());
|
ContactId contactId = getContactId(txn, m.getGroupId());
|
||||||
@@ -156,7 +155,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
|||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||||
@@ -111,9 +109,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
protected boolean incomingMessage(Transaction txn, Message m, BdfList list,
|
||||||
BdfList list, BdfDictionary meta)
|
BdfDictionary meta) throws DbException, FormatException {
|
||||||
throws DbException, FormatException {
|
|
||||||
|
|
||||||
GroupId groupId = m.getGroupId();
|
GroupId groupId = m.getGroupId();
|
||||||
MessageType type = getMessageType(meta);
|
MessageType type = getMessageType(meta);
|
||||||
@@ -141,7 +138,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
txn.attach(event);
|
txn.attach(event);
|
||||||
|
|
||||||
// shares message and its dependencies
|
// shares message and its dependencies
|
||||||
return ACCEPT_SHARE;
|
return true;
|
||||||
} else if (type == WRAPPED_COMMENT) {
|
} else if (type == WRAPPED_COMMENT) {
|
||||||
// Check that the original message ID in the dependency's metadata
|
// Check that the original message ID in the dependency's metadata
|
||||||
// matches the original parent ID of the wrapped comment
|
// matches the original parent ID of the wrapped comment
|
||||||
@@ -156,7 +153,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// don't share message until parent arrives
|
// don't share message until parent arrives
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
|
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
|
import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
|
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
|
||||||
@@ -76,9 +75,8 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||||
BdfList body, BdfDictionary meta)
|
BdfDictionary meta) throws DbException, FormatException {
|
||||||
throws DbException, FormatException {
|
|
||||||
|
|
||||||
messageTracker.trackIncomingMessage(txn, m);
|
messageTracker.trackIncomingMessage(txn, m);
|
||||||
|
|
||||||
@@ -88,7 +86,8 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
new ForumPostReceivedEvent(m.getGroupId(), header, text);
|
new ForumPostReceivedEvent(m.getGroupId(), header, text);
|
||||||
txn.attach(event);
|
txn.attach(event);
|
||||||
|
|
||||||
return ACCEPT_SHARE;
|
// share message
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
|
|||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
@@ -450,26 +449,31 @@ class IntroduceeProtocolEngine
|
|||||||
s.getRemote().acceptTimestamp);
|
s.getRemote().acceptTimestamp);
|
||||||
if (timestamp == -1) throw new AssertionError();
|
if (timestamp == -1) throw new AssertionError();
|
||||||
|
|
||||||
ContactId contactId;
|
Map<TransportId, KeySetId> keys = null;
|
||||||
try {
|
try {
|
||||||
contactId = contactManager.addContact(txn, s.getRemote().author,
|
contactManager.addContact(txn, s.getRemote().author,
|
||||||
localAuthor.getId(), false);
|
localAuthor.getId(), false);
|
||||||
|
|
||||||
|
// Only add transport properties and keys when the contact was added
|
||||||
|
// This will be changed once we have a way to reset state for peers
|
||||||
|
// that were contacts already at some point in the past.
|
||||||
|
Contact c = contactManager.getContact(txn,
|
||||||
|
s.getRemote().author.getId(), localAuthor.getId());
|
||||||
|
|
||||||
|
// add the keys to the new contact
|
||||||
|
keys = keyManager.addRotationKeys(txn, c.getId(),
|
||||||
|
new SecretKey(s.getMasterKey()), timestamp,
|
||||||
|
s.getLocal().alice, false);
|
||||||
|
|
||||||
// add signed transport properties for the contact
|
// add signed transport properties for the contact
|
||||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
transportPropertyManager.addRemoteProperties(txn, c.getId(),
|
||||||
s.getRemote().transportProperties);
|
s.getRemote().transportProperties);
|
||||||
} catch (ContactExistsException e) {
|
} catch (ContactExistsException e) {
|
||||||
// The other introducee might have deleted us and been
|
// Ignore this, because the other introducee might have deleted us.
|
||||||
// reintroduced. In that case we can ignore the transport
|
// So we still want updated transport properties
|
||||||
// properties, but we still want the new transport keys as the
|
// and new transport keys.
|
||||||
// contact will no longer have the old keys
|
|
||||||
contactId = contactManager.getContact(txn,
|
|
||||||
s.getRemote().author.getId(), localAuthor.getId()).getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TransportId, KeySetId> keys = keyManager.addRotationKeys(txn,
|
|
||||||
contactId, new SecretKey(s.getMasterKey()), timestamp,
|
|
||||||
s.getLocal().alice, false);
|
|
||||||
|
|
||||||
// send ACTIVATE message with a MAC
|
// send ACTIVATE message with a MAC
|
||||||
byte[] mac = crypto.activateMac(s);
|
byte[] mac = crypto.activateMac(s);
|
||||||
long localTimestamp = getTimestampForInvisibleMessage(s);
|
long localTimestamp = getTimestampForInvisibleMessage(s);
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||||
@@ -172,9 +171,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||||
BdfList body, BdfDictionary bdfMeta)
|
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||||
throws DbException, FormatException {
|
|
||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||||
// set the clean-up timer that will be started when message gets read
|
// set the clean-up timer that will be started when message gets read
|
||||||
@@ -215,7 +213,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, storageId, session);
|
storeSession(txn, storageId, session);
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ import static java.util.Collections.emptyList;
|
|||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
@@ -173,8 +172,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
BdfDictionary metaDict = metadataParser.parse(meta);
|
BdfDictionary metaDict = metadataParser.parse(meta);
|
||||||
// Message type is null for version 0.0 private messages
|
// Message type is null for version 0.0 private messages
|
||||||
@@ -194,7 +193,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
// Don't share message
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void incomingPrivateMessage(Transaction txn, Message m,
|
private void incomingPrivateMessage(Transaction txn, Message m,
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNVERIFIED;
|
import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED;
|
import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
||||||
@@ -519,19 +518,18 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||||
BdfList body, BdfDictionary meta)
|
BdfDictionary meta) throws DbException, FormatException {
|
||||||
throws DbException, FormatException {
|
|
||||||
|
|
||||||
MessageType type =
|
MessageType type =
|
||||||
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case JOIN:
|
case JOIN:
|
||||||
handleJoinMessage(txn, m, meta);
|
handleJoinMessage(txn, m, meta);
|
||||||
return ACCEPT_SHARE;
|
return true;
|
||||||
case POST:
|
case POST:
|
||||||
handleGroupMessage(txn, m, meta);
|
handleGroupMessage(txn, m, meta);
|
||||||
return ACCEPT_SHARE;
|
return true;
|
||||||
default:
|
default:
|
||||||
// the validator should only let valid types pass
|
// the validator should only let valid types pass
|
||||||
throw new RuntimeException("Unknown MessageType");
|
throw new RuntimeException("Unknown MessageType");
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
||||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||||
@@ -148,9 +147,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||||
BdfList body, BdfDictionary bdfMeta)
|
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||||
throws DbException, FormatException {
|
|
||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||||
// set the clean-up timer that will be started when message gets read
|
// set the clean-up timer that will be started when message gets read
|
||||||
@@ -173,7 +171,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, storageId, session);
|
storeSession(txn, storageId, session);
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionId getSessionId(GroupId privateGroupId) {
|
private SessionId getSessionId(GroupId privateGroupId) {
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import java.util.Set;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||||
import static org.briarproject.briar.sharing.MessageType.ABORT;
|
import static org.briarproject.briar.sharing.MessageType.ABORT;
|
||||||
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
|
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
|
||||||
@@ -134,8 +133,8 @@ abstract class SharingManagerImpl<S extends Shareable>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||||
BdfList body, BdfDictionary d) throws DbException, FormatException {
|
BdfDictionary d) throws DbException, FormatException {
|
||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
MessageMetadata meta = messageParser.parseMetadata(d);
|
MessageMetadata meta = messageParser.parseMetadata(d);
|
||||||
// set the clean-up timer that will be started when message gets read
|
// set the clean-up timer that will be started when message gets read
|
||||||
@@ -158,7 +157,7 @@ abstract class SharingManagerImpl<S extends Shareable>
|
|||||||
}
|
}
|
||||||
// Store the updated session
|
// Store the updated session
|
||||||
storeSession(txn, storageId, session);
|
storeSession(txn, storageId, session);
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import javax.annotation.Nullable;
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
@@ -60,6 +59,7 @@ import static org.briarproject.briar.api.avatar.AvatarManager.MAJOR_VERSION;
|
|||||||
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class AvatarManagerImplTest extends BrambleMockTestCase {
|
public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -196,8 +196,7 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
|||||||
null);
|
null);
|
||||||
expectGetContactId(txn, contactGroupId, contact.getId());
|
expectGetContactId(txn, contactGroupId, contact.getId());
|
||||||
|
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
|
||||||
assertEquals(1, txn.getActions().size());
|
assertEquals(1, txn.getActions().size());
|
||||||
Event event = ((EventAction) txn.getActions().get(0)).getEvent();
|
Event event = ((EventAction) txn.getActions().get(0)).getEvent();
|
||||||
AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event;
|
AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event;
|
||||||
@@ -231,8 +230,7 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
|||||||
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
||||||
expectGetContactId(txn, contactGroupId, contact.getId());
|
expectGetContactId(txn, contactGroupId, contact.getId());
|
||||||
|
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
|
||||||
|
|
||||||
// event to broadcast
|
// event to broadcast
|
||||||
assertEquals(1, txn.getActions().size());
|
assertEquals(1, txn.getActions().size());
|
||||||
@@ -262,8 +260,7 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
||||||
|
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
|
||||||
|
|
||||||
// no event to broadcast
|
// no event to broadcast
|
||||||
assertEquals(0, txn.getActions().size());
|
assertEquals(0, txn.getActions().size());
|
||||||
@@ -274,8 +271,7 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
|||||||
throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
expectGetOurGroup(txn);
|
expectGetOurGroup(txn);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
avatarManager.incomingMessage(txn, ourMsg, meta);
|
||||||
avatarManager.incomingMessage(txn, ourMsg, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import org.jmock.Expectations;
|
|||||||
import org.jmock.Mockery;
|
import org.jmock.Mockery;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
@@ -185,8 +184,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(verifiedInfo));
|
will(returnValue(verifiedInfo));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(ACCEPT_SHARE,
|
blogManager.incomingMessage(txn, message, body, meta);
|
||||||
blogManager.incomingMessage(txn, message, body, meta));
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
assertEquals(1, txn.getActions().size());
|
assertEquals(1, txn.getActions().size());
|
||||||
@@ -227,8 +225,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(rssLocalAuthor));
|
will(returnValue(rssLocalAuthor));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(ACCEPT_SHARE,
|
blogManager.incomingMessage(txn, rssMessage, body, meta);
|
||||||
blogManager.incomingMessage(txn, rssMessage, body, meta));
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
assertEquals(1, txn.getActions().size());
|
assertEquals(1, txn.getActions().size());
|
||||||
|
|||||||
@@ -1667,126 +1667,6 @@ public class IntroductionIntegrationTest
|
|||||||
assertTrue(deleteMessages0From1(emptySet()).allDeleted());
|
assertTrue(deleteMessages0From1(emptySet()).allDeleted());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReintroductionAfterBothIntroduceesDeleteEachOther()
|
|
||||||
throws Exception {
|
|
||||||
addListeners(true, true);
|
|
||||||
|
|
||||||
// Introduce 1 to 2
|
|
||||||
makeIntroduction(true);
|
|
||||||
updateContactIdsFor1And2();
|
|
||||||
|
|
||||||
// Sync client versions and transport properties - this shows that
|
|
||||||
// 1 and 2 share a set of transport keys
|
|
||||||
sync1To2(1, true);
|
|
||||||
sync2To1(1, true);
|
|
||||||
sync1To2(2, true);
|
|
||||||
sync2To1(1, true);
|
|
||||||
ack1To2(1);
|
|
||||||
|
|
||||||
// Both introducees delete each other
|
|
||||||
contactManager1.removeContact(contactId2From1);
|
|
||||||
contactManager2.removeContact(contactId1From2);
|
|
||||||
|
|
||||||
// Introduce 1 to 2 again
|
|
||||||
makeIntroduction(true);
|
|
||||||
updateContactIdsFor1And2();
|
|
||||||
|
|
||||||
// Sync client versions and transport properties again
|
|
||||||
sync1To2(1, true);
|
|
||||||
sync2To1(1, true);
|
|
||||||
sync1To2(2, true);
|
|
||||||
sync2To1(1, true);
|
|
||||||
ack1To2(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReintroductionAfterOneIntroduceeDeletesTheOther()
|
|
||||||
throws Exception {
|
|
||||||
addListeners(true, true);
|
|
||||||
|
|
||||||
// Introduce 1 to 2
|
|
||||||
makeIntroduction(true);
|
|
||||||
updateContactIdsFor1And2();
|
|
||||||
|
|
||||||
// Sync client versions and transport properties - this shows that
|
|
||||||
// 1 and 2 share a set of transport keys
|
|
||||||
sync1To2(1, true);
|
|
||||||
sync2To1(1, true);
|
|
||||||
sync1To2(2, true);
|
|
||||||
sync2To1(1, true);
|
|
||||||
ack1To2(1);
|
|
||||||
|
|
||||||
// 1 deletes 2, but not vice versa
|
|
||||||
contactManager1.removeContact(contactId2From1);
|
|
||||||
|
|
||||||
// Introduce 1 to 2 again
|
|
||||||
makeIntroduction(false);
|
|
||||||
updateContactIdsFor1And2();
|
|
||||||
|
|
||||||
// Sync client versioning update from 1 to 2 only - this shows that
|
|
||||||
// although 1 and 2 share transport keys, the connection won't be
|
|
||||||
// fully functional until we have a way for 1 to reset the state it's
|
|
||||||
// holding about 2
|
|
||||||
sync1To2(1, true);
|
|
||||||
ack2To1(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeIntroduction(boolean expectAddContact2From1)
|
|
||||||
throws Exception {
|
|
||||||
// Make introduction
|
|
||||||
Contact introducee1 = contact1From0;
|
|
||||||
Contact introducee2 = contact2From0;
|
|
||||||
introductionManager0.makeIntroduction(introducee1, introducee2, "Hi!");
|
|
||||||
|
|
||||||
// Request to 1
|
|
||||||
sync0To1(1, true);
|
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
|
||||||
assertTrue(listener1.requestReceived);
|
|
||||||
|
|
||||||
// Request to 2
|
|
||||||
sync0To2(1, true);
|
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
|
||||||
assertTrue(listener2.requestReceived);
|
|
||||||
|
|
||||||
// Response from 1, forwarded
|
|
||||||
sync1To0(1, true);
|
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
|
||||||
assertTrue(listener0.response1Received);
|
|
||||||
sync0To2(1, true);
|
|
||||||
|
|
||||||
// Response and auth from 2, forwarded
|
|
||||||
sync2To0(2, true);
|
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
|
||||||
assertTrue(listener0.response2Received);
|
|
||||||
sync0To1(2, true);
|
|
||||||
if (expectAddContact2From1) {
|
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
|
||||||
assertTrue(listener1.succeeded);
|
|
||||||
}
|
|
||||||
assertTrue(contactManager1
|
|
||||||
.contactExists(author2.getId(), author1.getId()));
|
|
||||||
|
|
||||||
// Auth and activate from 1, forwarded
|
|
||||||
sync1To0(2, true);
|
|
||||||
sync0To2(2, true);
|
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
|
||||||
assertTrue(listener2.succeeded);
|
|
||||||
assertTrue(contactManager2
|
|
||||||
.contactExists(author1.getId(), author2.getId()));
|
|
||||||
|
|
||||||
// Activate from 2, forwarded
|
|
||||||
sync2To0(1, true);
|
|
||||||
sync0To1(1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateContactIdsFor1And2() throws Exception {
|
|
||||||
contactId2From1 = contactManager1.getContact(author2.getId(),
|
|
||||||
author1.getId()).getId();
|
|
||||||
contactId1From2 = contactManager2.getContact(author1.getId(),
|
|
||||||
author2.getId()).getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeletionResult deleteAllMessages1From0() throws DbException {
|
private DeletionResult deleteAllMessages1From0() throws DbException {
|
||||||
return db0.transactionWithResult(false, txn -> introductionManager0
|
return db0.transactionWithResult(false, txn -> introductionManager0
|
||||||
.deleteAllMessages(txn, contactId1From0));
|
.deleteAllMessages(txn, contactId1From0));
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
|
||||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||||
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
||||||
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
||||||
@@ -73,16 +71,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteAndReadWithLazyRetransmission() throws Exception {
|
public void testWriteAndRead() throws Exception {
|
||||||
testWriteAndRead(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteAndReadWithEagerRetransmission() throws Exception {
|
|
||||||
testWriteAndRead(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testWriteAndRead(boolean eager) throws Exception {
|
|
||||||
// Create the identities
|
// Create the identities
|
||||||
Identity aliceIdentity =
|
Identity aliceIdentity =
|
||||||
alice.getIdentityManager().createIdentity("Alice");
|
alice.getIdentityManager().createIdentity("Alice");
|
||||||
@@ -97,21 +86,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
bob.getEventBus().addListener(listener);
|
bob.getEventBus().addListener(listener);
|
||||||
// Alice sends a private message to Bob
|
// Alice sends a private message to Bob
|
||||||
sendMessage(alice, bobId);
|
sendMessage(alice, bobId);
|
||||||
// Sync Alice's client versions
|
// Sync Alice's client versions and transport properties
|
||||||
read(bob, write(alice, bobId, eager, 1), 1);
|
read(bob, write(alice, bobId), 2);
|
||||||
// Sync Bob's client versions
|
// Sync Bob's client versions and transport properties
|
||||||
read(alice, write(bob, aliceId, eager, 1), 1);
|
read(alice, write(bob, aliceId), 2);
|
||||||
// Sync Alice's second client versioning update (with the active flag
|
// Sync the private message and the attachment
|
||||||
// raised), the private message and the attachment
|
read(bob, write(alice, bobId), 2);
|
||||||
read(bob, write(alice, bobId, eager, 3), 3);
|
|
||||||
// Bob should have received the private message
|
// Bob should have received the private message
|
||||||
assertTrue(listener.messageAdded);
|
assertTrue(listener.messageAdded);
|
||||||
// Bob should have received the attachment
|
// Bob should have received the attachment
|
||||||
assertTrue(listener.attachmentAdded);
|
assertTrue(listener.attachmentAdded);
|
||||||
// Sync messages from Alice to Bob again. If using eager
|
|
||||||
// retransmission, the three unacked messages should be sent again.
|
|
||||||
// They're all duplicates, so no further deliveries should occur
|
|
||||||
read(bob, write(alice, bobId, eager, eager ? 3 : 0), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
|
private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
|
||||||
@@ -165,24 +149,15 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] write(SimplexMessagingIntegrationTestComponent device,
|
private byte[] write(SimplexMessagingIntegrationTestComponent device,
|
||||||
ContactId contactId, boolean eager, int transmissions)
|
ContactId contactId) throws Exception {
|
||||||
throws Exception {
|
|
||||||
// Listen for message transmissions
|
|
||||||
MessageTransmissionListener listener =
|
|
||||||
new MessageTransmissionListener(transmissions);
|
|
||||||
device.getEventBus().addListener(listener);
|
|
||||||
// Write the outgoing stream
|
// Write the outgoing stream
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
TestTransportConnectionWriter writer =
|
TestTransportConnectionWriter writer =
|
||||||
new TestTransportConnectionWriter(out, eager);
|
new TestTransportConnectionWriter(out);
|
||||||
device.getConnectionManager().manageOutgoingConnection(contactId,
|
device.getConnectionManager().manageOutgoingConnection(contactId,
|
||||||
SIMPLEX_TRANSPORT_ID, writer);
|
SIMPLEX_TRANSPORT_ID, writer);
|
||||||
// Wait for the writer to be disposed
|
// Wait for the writer to be disposed
|
||||||
writer.getDisposedLatch().await(TIMEOUT_MS, MILLISECONDS);
|
writer.getDisposedLatch().await(TIMEOUT_MS, MILLISECONDS);
|
||||||
// Check that the expected number of messages were sent
|
|
||||||
assertTrue(listener.sent.await(TIMEOUT_MS, MILLISECONDS));
|
|
||||||
// Clean up the listener
|
|
||||||
device.getEventBus().removeListener(listener);
|
|
||||||
// Return the contents of the stream
|
// Return the contents of the stream
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
@@ -203,24 +178,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
deleteTestDirectory(testDir);
|
deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
private static class MessageTransmissionListener implements EventListener {
|
|
||||||
|
|
||||||
private final CountDownLatch sent;
|
|
||||||
|
|
||||||
private MessageTransmissionListener(int transmissions) {
|
|
||||||
sent = new CountDownLatch(transmissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof MessagesSentEvent) {
|
|
||||||
MessagesSentEvent m = (MessagesSentEvent) e;
|
|
||||||
for (MessageId ignored : m.getMessageIds()) sent.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
private static class MessageDeliveryListener implements EventListener {
|
private static class MessageDeliveryListener implements EventListener {
|
||||||
|
|
||||||
@@ -234,9 +191,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof MessageStateChangedEvent) {
|
if (e instanceof MessageStateChangedEvent) {
|
||||||
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
||||||
if (!m.isLocal() && m.getState().equals(DELIVERED)) {
|
if (m.getState().equals(DELIVERED)) delivered.countDown();
|
||||||
delivered.countDown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import javax.annotation.Nullable;
|
|||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static junit.framework.TestCase.fail;
|
import static junit.framework.TestCase.fail;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
@@ -276,50 +275,43 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testIncomingFirstInviteMessage() throws Exception {
|
public void testIncomingFirstInviteMessage() throws Exception {
|
||||||
expectFirstIncomingMessage(Role.INVITEE, INVITE);
|
expectFirstIncomingMessage(Role.INVITEE, INVITE);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingFirstJoinMessage() throws Exception {
|
public void testIncomingFirstJoinMessage() throws Exception {
|
||||||
expectFirstIncomingMessage(Role.PEER, JOIN);
|
expectFirstIncomingMessage(Role.PEER, JOIN);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingInviteMessage() throws Exception {
|
public void testIncomingInviteMessage() throws Exception {
|
||||||
expectIncomingMessage(Role.INVITEE, INVITE);
|
expectIncomingMessage(Role.INVITEE, INVITE);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingJoinMessage() throws Exception {
|
public void testIncomingJoinMessage() throws Exception {
|
||||||
expectIncomingMessage(Role.INVITEE, JOIN);
|
expectIncomingMessage(Role.INVITEE, JOIN);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingJoinMessageForCreator() throws Exception {
|
public void testIncomingJoinMessageForCreator() throws Exception {
|
||||||
expectIncomingMessage(Role.CREATOR, JOIN);
|
expectIncomingMessage(Role.CREATOR, JOIN);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingLeaveMessage() throws Exception {
|
public void testIncomingLeaveMessage() throws Exception {
|
||||||
expectIncomingMessage(Role.INVITEE, LEAVE);
|
expectIncomingMessage(Role.INVITEE, LEAVE);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingAbortMessage() throws Exception {
|
public void testIncomingAbortMessage() throws Exception {
|
||||||
expectIncomingMessage(Role.INVITEE, ABORT);
|
expectIncomingMessage(Role.INVITEE, ABORT);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||||
.incomingMessage(txn, message, body, meta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectFirstIncomingMessage(Role role, MessageType type)
|
private void expectFirstIncomingMessage(Role role, MessageType type)
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
|||||||
// Write the messages to a transport stream
|
// Write the messages to a transport stream
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
TestTransportConnectionWriter writer =
|
TestTransportConnectionWriter writer =
|
||||||
new TestTransportConnectionWriter(out, false);
|
new TestTransportConnectionWriter(out);
|
||||||
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
||||||
SIMPLEX_TRANSPORT_ID, writer);
|
SIMPLEX_TRANSPORT_ID, writer);
|
||||||
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
||||||
@@ -487,7 +487,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
|||||||
// start outgoing connection
|
// start outgoing connection
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
TestTransportConnectionWriter writer =
|
TestTransportConnectionWriter writer =
|
||||||
new TestTransportConnectionWriter(out, false);
|
new TestTransportConnectionWriter(out);
|
||||||
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
||||||
SIMPLEX_TRANSPORT_ID, writer);
|
SIMPLEX_TRANSPORT_ID, writer);
|
||||||
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
||||||
|
|||||||
Reference in New Issue
Block a user