mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
1 Commits
release-1.
...
introducti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aec948f18 |
@@ -14,8 +14,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 10001
|
||||
versionName "1.0.1"
|
||||
versionCode 1700
|
||||
versionName "0.17.0"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
|
||||
@@ -55,12 +55,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||
Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
|
||||
maxLatency);
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
@@ -156,8 +154,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new AndroidBluetoothTransportConnection(this,
|
||||
connectionLimiter, s);
|
||||
return new AndroidBluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -59,13 +59,11 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
BluetoothConnectionLimiter connectionLimiter =
|
||||
new BluetoothConnectionLimiterImpl();
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||
connectionLimiter, ioExecutor, androidExecutor, appContext,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -14,14 +14,10 @@ import java.io.OutputStream;
|
||||
class AndroidBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothConnectionLimiter connectionManager;
|
||||
private final BluetoothSocket socket;
|
||||
|
||||
AndroidBluetoothTransportConnection(Plugin plugin,
|
||||
BluetoothConnectionLimiter connectionManager,
|
||||
BluetoothSocket socket) {
|
||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
super(plugin);
|
||||
this.connectionManager = connectionManager;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@@ -37,10 +33,6 @@ class AndroidBluetoothTransportConnection
|
||||
|
||||
@Override
|
||||
protected void closeConnection(boolean exception) throws IOException {
|
||||
try {
|
||||
socket.close();
|
||||
} finally {
|
||||
connectionManager.connectionClosed(this);
|
||||
}
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,19 @@ public interface ContactGroupFactory {
|
||||
/**
|
||||
* Creates a group that is not shared with any contacts.
|
||||
*/
|
||||
Group createLocalGroup(ClientId clientId, int majorVersion);
|
||||
Group createLocalGroup(ClientId clientId, int clientVersion);
|
||||
|
||||
/**
|
||||
* Creates a group for the given client to share with the given contact.
|
||||
*/
|
||||
Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
Group createContactGroup(ClientId clientId, int clientVersion,
|
||||
Contact contact);
|
||||
|
||||
/**
|
||||
* Creates a group for the given client to share between the given authors
|
||||
* identified by their AuthorIds.
|
||||
*/
|
||||
Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
Group createContactGroup(ClientId clientId, int clientVersion,
|
||||
AuthorId authorId1, AuthorId authorId2);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
public interface ContactExchangeTask {
|
||||
|
||||
/**
|
||||
* The current version of the contact exchange protocol.
|
||||
* The current version of the contact exchange protocol
|
||||
*/
|
||||
byte PROTOCOL_VERSION = 1;
|
||||
int PROTOCOL_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's header key from the master secret.
|
||||
|
||||
@@ -24,8 +24,6 @@ public interface ContactManager {
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* derives and stores transport keys for each transport, and returns an ID
|
||||
* for the contact.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||
@@ -40,10 +38,7 @@ public interface ContactManager {
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* derives and stores transport keys for each transport, and returns an ID
|
||||
* for the contact.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
* and returns an ID for the contact.
|
||||
*/
|
||||
ContactId addContact(Author remote, AuthorId local,
|
||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
/**
|
||||
* Record types for the contact exchange protocol.
|
||||
*/
|
||||
public interface RecordTypes {
|
||||
|
||||
byte CONTACT_INFO = 0;
|
||||
}
|
||||
@@ -24,9 +24,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
|
||||
* );
|
||||
* </pre>
|
||||
*/
|
||||
public static BdfDictionary of(Entry<String, ?>... entries) {
|
||||
public static BdfDictionary of(Entry<String, Object>... entries) {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
for (Entry<String, ?> e : entries) d.put(e.getKey(), e.getValue());
|
||||
for (Entry<String, Object> e : entries) d.put(e.getKey(), e.getValue());
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,8 +136,8 @@ public interface DatabaseComponent {
|
||||
|
||||
/**
|
||||
* Deletes the message with the given ID. Unlike
|
||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
||||
* dependencies, metadata, and any other associated state are not deleted.
|
||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID and any
|
||||
* other associated data are not deleted.
|
||||
*/
|
||||
void deleteMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
@@ -241,8 +241,7 @@ public interface DatabaseComponent {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<Group> getGroups(Transaction txn, ClientId c, int majorVersion)
|
||||
throws DbException;
|
||||
Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the given group's visibility to the given contact, or
|
||||
@@ -267,14 +266,6 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all delivered messages in the given group.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that need to be validated.
|
||||
* <p/>
|
||||
@@ -319,9 +310,9 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for any delivered messages in the given group with
|
||||
* metadata that matches all entries in the given query. If the query is
|
||||
* empty, the metadata for all delivered messages is returned.
|
||||
* Returns the metadata for any messages in the given group with metadata
|
||||
* that matches all entries in the given query. If the query is empty, the
|
||||
* metadata for all messages is returned.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -337,8 +328,8 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given delivered or pending message.
|
||||
* This is only meant to be used by the ValidationManager.
|
||||
* Returns the metadata for the given delivered and pending message.
|
||||
* This is meant to be only used by the ValidationManager
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -346,8 +337,8 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the status of all delivered messages in the given group with
|
||||
* respect to the given contact.
|
||||
* Returns the status of all messages in the given group with respect to
|
||||
* the given contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -382,8 +373,8 @@ public interface DatabaseComponent {
|
||||
State getMessageState(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the status of the given delivered message with respect to the
|
||||
* given contact.
|
||||
* Returns the status of the given message with respect to the given
|
||||
* contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.briarproject.bramble.api.keyagreement.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a BQP task stops listening.
|
||||
*/
|
||||
public class KeyAgreementStoppedListeningEvent extends Event {
|
||||
}
|
||||
@@ -43,20 +43,17 @@ public interface LifecycleManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Service} to be started and stopped. This method
|
||||
* should be called before {@link #startServices(String)}.
|
||||
* Registers a {@link Service} to be started and stopped.
|
||||
*/
|
||||
void registerService(Service s);
|
||||
|
||||
/**
|
||||
* Registers a {@link Client} to be started. This method should be called
|
||||
* before {@link #startServices(String)}.
|
||||
* Registers a {@link Client} to be started.
|
||||
*/
|
||||
void registerClient(Client c);
|
||||
|
||||
/**
|
||||
* Registers an {@link ExecutorService} to be shut down. This method
|
||||
* should be called before {@link #startServices(String)}.
|
||||
* Registers an {@link ExecutorService} to be shut down.
|
||||
*/
|
||||
void registerForShutdown(ExecutorService e);
|
||||
|
||||
|
||||
@@ -15,17 +15,12 @@ public interface TransportPropertyManager {
|
||||
/**
|
||||
* The unique ID of the transport property client.
|
||||
*/
|
||||
ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.properties");
|
||||
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties");
|
||||
|
||||
/**
|
||||
* The current major version of the transport property client.
|
||||
* The current version of the transport property client.
|
||||
*/
|
||||
int MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The current minor version of the transport property client.
|
||||
*/
|
||||
int MINOR_VERSION = 0;
|
||||
int CLIENT_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Stores the given properties received while adding a contact - they will
|
||||
@@ -42,8 +37,8 @@ public interface TransportPropertyManager {
|
||||
|
||||
/**
|
||||
* Returns the local transport properties for all transports.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
* <br/>
|
||||
* TODO: Transaction can be read-only when code is simplified
|
||||
*/
|
||||
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.briarproject.bramble.api.record;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class Record {
|
||||
|
||||
public static final int RECORD_HEADER_BYTES = 4;
|
||||
public static final int MAX_RECORD_PAYLOAD_BYTES = 48 * 1024; // 48 KiB
|
||||
|
||||
private final byte protocolVersion, recordType;
|
||||
private final byte[] payload;
|
||||
|
||||
public Record(byte protocolVersion, byte recordType, byte[] payload) {
|
||||
if (payload.length > MAX_RECORD_PAYLOAD_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.recordType = recordType;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public byte getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public byte getRecordType() {
|
||||
return recordType;
|
||||
}
|
||||
|
||||
public byte[] getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.record;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface RecordReader {
|
||||
|
||||
/**
|
||||
* Reads and returns the next record.
|
||||
*
|
||||
* @throws EOFException if the end of the stream is reached without reading
|
||||
* a complete record
|
||||
*/
|
||||
Record readRecord() throws IOException;
|
||||
|
||||
void close() throws IOException;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.bramble.api.record;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface RecordReaderFactory {
|
||||
|
||||
RecordReader createRecordReader(InputStream in);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.record;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface RecordWriter {
|
||||
|
||||
void writeRecord(Record r) throws IOException;
|
||||
|
||||
void flush() throws IOException;
|
||||
|
||||
void close() throws IOException;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.bramble.api.record;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface RecordWriterFactory {
|
||||
|
||||
RecordWriter createRecordWriter(OutputStream out);
|
||||
}
|
||||
@@ -5,24 +5,9 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPT
|
||||
public class Group {
|
||||
|
||||
public enum Visibility {
|
||||
|
||||
INVISIBLE(0), // The group is not visible
|
||||
VISIBLE(1), // The group is visible, messages are accepted but not sent
|
||||
SHARED(2); // The group is visible, messages are accepted and sent
|
||||
|
||||
private final int value;
|
||||
|
||||
Visibility(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static Visibility min(Visibility a, Visibility b) {
|
||||
return a.getValue() < b.getValue() ? a : b;
|
||||
}
|
||||
INVISIBLE, // The group is not visible
|
||||
VISIBLE, // The group is visible but messages are not shared
|
||||
SHARED // The group is visible and messages are shared
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,16 +17,13 @@ public class Group {
|
||||
|
||||
private final GroupId id;
|
||||
private final ClientId clientId;
|
||||
private final int majorVersion;
|
||||
private final byte[] descriptor;
|
||||
|
||||
public Group(GroupId id, ClientId clientId, int majorVersion,
|
||||
byte[] descriptor) {
|
||||
public Group(GroupId id, ClientId clientId, byte[] descriptor) {
|
||||
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
this.id = id;
|
||||
this.clientId = clientId;
|
||||
this.majorVersion = majorVersion;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@@ -59,13 +41,6 @@ public class Group {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the major version of the client to which the group belongs.
|
||||
*/
|
||||
public int getMajorVersion() {
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the group's descriptor.
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
public interface GroupFactory {
|
||||
|
||||
/**
|
||||
* Creates a group with the given client ID, major version and descriptor.
|
||||
* Creates a group with the given client ID, client version and descriptor.
|
||||
*/
|
||||
Group createGroup(ClientId c, int majorVersion, byte[] descriptor);
|
||||
Group createGroup(ClientId c, int clientVersion, byte[] descriptor);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,5 @@ public interface MessageFactory {
|
||||
|
||||
Message createMessage(GroupId g, long timestamp, byte[] body);
|
||||
|
||||
Message createMessage(byte[] raw);
|
||||
|
||||
Message createMessage(MessageId m, byte[] raw);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface SyncRecordReader {
|
||||
public interface RecordReader {
|
||||
|
||||
boolean eof() throws IOException;
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.InputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface SyncRecordReaderFactory {
|
||||
public interface RecordReaderFactory {
|
||||
|
||||
SyncRecordReader createRecordReader(InputStream in);
|
||||
RecordReader createRecordReader(InputStream in);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface SyncRecordWriter {
|
||||
public interface RecordWriter {
|
||||
|
||||
void writeAck(Ack a) throws IOException;
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface SyncRecordWriterFactory {
|
||||
public interface RecordWriterFactory {
|
||||
|
||||
SyncRecordWriter createRecordWriter(OutputStream out);
|
||||
RecordWriter createRecordWriter(OutputStream out);
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package org.briarproject.bramble.api.sync;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
|
||||
public interface SyncConstants {
|
||||
|
||||
/**
|
||||
@@ -12,8 +10,16 @@ public interface SyncConstants {
|
||||
byte PROTOCOL_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The maximum length of a group descriptor in bytes.
|
||||
* The length of the record header in bytes.
|
||||
*/
|
||||
int RECORD_HEADER_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* The maximum length of the record payload in bytes.
|
||||
*/
|
||||
int MAX_RECORD_PAYLOAD_LENGTH = 48 * 1024; // 48 KiB
|
||||
|
||||
/** The maximum length of a group descriptor in bytes. */
|
||||
int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB
|
||||
|
||||
/**
|
||||
@@ -34,5 +40,5 @@ public interface SyncConstants {
|
||||
/**
|
||||
* The maximum number of message IDs in an ack, offer or request record.
|
||||
*/
|
||||
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
|
||||
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.sync;
|
||||
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.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
@@ -34,20 +33,15 @@ public interface ValidationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the message validator for the given client. This method
|
||||
* should be called before {@link LifecycleManager#startServices(String)}.
|
||||
* Sets the message validator for the given client.
|
||||
*/
|
||||
void registerMessageValidator(ClientId c, int majorVersion,
|
||||
MessageValidator v);
|
||||
void registerMessageValidator(ClientId c, MessageValidator v);
|
||||
|
||||
/**
|
||||
* Registers the incoming message hook for the given client. The hook will
|
||||
* be called once for each incoming message that passes validation. This
|
||||
* method should be called before
|
||||
* {@link LifecycleManager#startServices(String)}.
|
||||
* Sets the incoming message hook for the given client. The hook will be
|
||||
* called once for each incoming message that passes validation.
|
||||
*/
|
||||
void registerIncomingMessageHook(ClientId c, int majorVersion,
|
||||
IncomingMessageHook hook);
|
||||
void registerIncomingMessageHook(ClientId c, IncomingMessageHook hook);
|
||||
|
||||
interface MessageValidator {
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ public interface KeyManager {
|
||||
* <p/>
|
||||
* {@link StreamContext StreamContexts} for the contact can be created
|
||||
* after this method has returned.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
*/
|
||||
void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException;
|
||||
@@ -35,8 +33,6 @@ public interface KeyManager {
|
||||
* <p/>
|
||||
* The keys must be bound before they can be used for incoming streams,
|
||||
* and also activated before they can be used for outgoing streams.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
*/
|
||||
Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException;
|
||||
@@ -59,7 +55,7 @@ public interface KeyManager {
|
||||
* the manager and the database.
|
||||
*/
|
||||
void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException;
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if we have keys that can be used for outgoing streams to
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.briarproject.bramble.api.versioning;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ClientMajorVersion implements Comparable<ClientMajorVersion> {
|
||||
|
||||
private final ClientId clientId;
|
||||
private final int majorVersion;
|
||||
|
||||
public ClientMajorVersion(ClientId clientId, int majorVersion) {
|
||||
this.clientId = clientId;
|
||||
this.majorVersion = majorVersion;
|
||||
}
|
||||
|
||||
public ClientId getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public int getMajorVersion() {
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof ClientMajorVersion) {
|
||||
ClientMajorVersion cv = (ClientMajorVersion) o;
|
||||
return clientId.equals(cv.clientId)
|
||||
&& majorVersion == cv.majorVersion;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (clientId.hashCode() << 16) + majorVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ClientMajorVersion cv) {
|
||||
int compare = clientId.compareTo(cv.clientId);
|
||||
if (compare != 0) return compare;
|
||||
return majorVersion - cv.majorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package org.briarproject.bramble.api.versioning;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ClientVersioningManager {
|
||||
|
||||
/**
|
||||
* The unique ID of the versioning client.
|
||||
*/
|
||||
ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.versioning");
|
||||
|
||||
/**
|
||||
* The current major version of the versioning client.
|
||||
*/
|
||||
int MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Registers a client that will be advertised to contacts. The hook will
|
||||
* be called when the visibility of the client changes. This method should
|
||||
* be called before {@link LifecycleManager#startServices(String)}.
|
||||
*/
|
||||
void registerClient(ClientId clientId, int majorVersion, int minorVersion,
|
||||
ClientVersioningHook hook);
|
||||
|
||||
/**
|
||||
* Returns the visibility of the given client with respect to the given
|
||||
* contact.
|
||||
*/
|
||||
Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||
ClientId clientId, int majorVersion) throws DbException;
|
||||
|
||||
interface ClientVersioningHook {
|
||||
|
||||
void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||
Visibility v) throws DbException;
|
||||
}
|
||||
}
|
||||
@@ -117,16 +117,15 @@ public class TestUtils {
|
||||
return new Author(id, FORMAT_VERSION, name, publicKey);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId, int majorVersion) {
|
||||
public static Group getGroup(ClientId clientId) {
|
||||
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||
return getGroup(clientId, majorVersion, descriptorLength);
|
||||
return getGroup(clientId, descriptorLength);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId, int majorVersion,
|
||||
int descriptorLength) {
|
||||
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
||||
GroupId groupId = new GroupId(getRandomId());
|
||||
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||
return new Group(groupId, clientId, majorVersion, descriptor);
|
||||
return new Group(groupId, clientId, descriptor);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
@@ -10,13 +10,12 @@ import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(ContactModule.EagerSingletons init);
|
||||
|
||||
void inject(CryptoExecutorModule.EagerSingletons init);
|
||||
void inject(CryptoModule.EagerSingletons init);
|
||||
|
||||
void inject(DatabaseExecutorModule.EagerSingletons init);
|
||||
|
||||
@@ -33,6 +32,4 @@ public interface BrambleCoreEagerSingletons {
|
||||
void inject(SystemModule.EagerSingletons init);
|
||||
|
||||
void inject(TransportModule.EagerSingletons init);
|
||||
|
||||
void inject(VersioningModule.EagerSingletons init);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.data.DataModule;
|
||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
@@ -13,7 +12,6 @@ import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.record.RecordModule;
|
||||
import org.briarproject.bramble.reliability.ReliabilityModule;
|
||||
import org.briarproject.bramble.reporting.ReportingModule;
|
||||
import org.briarproject.bramble.settings.SettingsModule;
|
||||
@@ -21,7 +19,6 @@ import org.briarproject.bramble.socks.SocksModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
@@ -29,7 +26,6 @@ import dagger.Module;
|
||||
ClientModule.class,
|
||||
ContactModule.class,
|
||||
CryptoModule.class,
|
||||
CryptoExecutorModule.class,
|
||||
DataModule.class,
|
||||
DatabaseModule.class,
|
||||
DatabaseExecutorModule.class,
|
||||
@@ -39,21 +35,19 @@ import dagger.Module;
|
||||
LifecycleModule.class,
|
||||
PluginModule.class,
|
||||
PropertiesModule.class,
|
||||
RecordModule.class,
|
||||
ReliabilityModule.class,
|
||||
ReportingModule.class,
|
||||
SettingsModule.class,
|
||||
SocksModule.class,
|
||||
SyncModule.class,
|
||||
SystemModule.class,
|
||||
TransportModule.class,
|
||||
VersioningModule.class
|
||||
TransportModule.class
|
||||
})
|
||||
public class BrambleCoreModule {
|
||||
|
||||
public static void initEagerSingletons(BrambleCoreEagerSingletons c) {
|
||||
c.inject(new ContactModule.EagerSingletons());
|
||||
c.inject(new CryptoExecutorModule.EagerSingletons());
|
||||
c.inject(new CryptoModule.EagerSingletons());
|
||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
@@ -62,6 +56,5 @@ public class BrambleCoreModule {
|
||||
c.inject(new SyncModule.EagerSingletons());
|
||||
c.inject(new SystemModule.EagerSingletons());
|
||||
c.inject(new TransportModule.EagerSingletons());
|
||||
c.inject(new VersioningModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,25 +32,25 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group createLocalGroup(ClientId clientId, int majorVersion) {
|
||||
return groupFactory.createGroup(clientId, majorVersion,
|
||||
public Group createLocalGroup(ClientId clientId, int clientVersion) {
|
||||
return groupFactory.createGroup(clientId, clientVersion,
|
||||
LOCAL_GROUP_DESCRIPTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
||||
Contact contact) {
|
||||
AuthorId local = contact.getLocalAuthorId();
|
||||
AuthorId remote = contact.getAuthor().getId();
|
||||
byte[] descriptor = createGroupDescriptor(local, remote);
|
||||
return groupFactory.createGroup(clientId, majorVersion, descriptor);
|
||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
||||
AuthorId authorId1, AuthorId authorId2) {
|
||||
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
||||
return groupFactory.createGroup(clientId, majorVersion, descriptor);
|
||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
||||
}
|
||||
|
||||
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
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.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
@@ -23,30 +26,30 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO;
|
||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -59,9 +62,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
"org.briarproject.briar.contact/EXCHANGE";
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final RecordReaderFactory recordReaderFactory;
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final Clock clock;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final ContactManager contactManager;
|
||||
@@ -78,17 +81,17 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
private volatile boolean alice;
|
||||
|
||||
@Inject
|
||||
ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory, Clock clock,
|
||||
ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||
ConnectionManager connectionManager, ContactManager contactManager,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
|
||||
StreamWriterFactory streamWriterFactory) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.recordReaderFactory = recordReaderFactory;
|
||||
this.recordWriterFactory = recordWriterFactory;
|
||||
this.authorFactory = authorFactory;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.clock = clock;
|
||||
this.connectionManager = connectionManager;
|
||||
this.contactManager = contactManager;
|
||||
@@ -123,18 +126,18 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn);
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the local transport properties
|
||||
Map<TransportId, TransportProperties> localProperties;
|
||||
Map<TransportId, TransportProperties> localProperties, remoteProperties;
|
||||
try {
|
||||
localProperties = transportPropertyManager.getLocalProperties();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn);
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,138 +151,159 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createContactExchangeStreamReader(in,
|
||||
alice ? bobHeaderKey : aliceHeaderKey);
|
||||
RecordReader recordReader =
|
||||
recordReaderFactory.createRecordReader(streamReader);
|
||||
|
||||
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
||||
// Create the writers
|
||||
OutputStream streamWriter =
|
||||
streamWriterFactory.createContactExchangeStreamWriter(out,
|
||||
alice ? aliceHeaderKey : bobHeaderKey);
|
||||
RecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(streamWriter);
|
||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||
|
||||
// Derive the nonces to be signed
|
||||
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
|
||||
new byte[] {PROTOCOL_VERSION});
|
||||
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
|
||||
new byte[] {PROTOCOL_VERSION});
|
||||
byte[] localNonce = alice ? aliceNonce : bobNonce;
|
||||
byte[] remoteNonce = alice ? bobNonce : aliceNonce;
|
||||
|
||||
// Sign the nonce
|
||||
byte[] localSignature = sign(localAuthor, localNonce);
|
||||
|
||||
// Exchange contact info
|
||||
// Exchange pseudonyms, signed nonces, and timestamps
|
||||
long localTimestamp = clock.currentTimeMillis();
|
||||
ContactInfo remoteInfo;
|
||||
Author remoteAuthor;
|
||||
long remoteTimestamp;
|
||||
try {
|
||||
if (alice) {
|
||||
sendContactInfo(recordWriter, localAuthor, localProperties,
|
||||
localSignature, localTimestamp);
|
||||
recordWriter.flush();
|
||||
remoteInfo = receiveContactInfo(recordReader);
|
||||
sendPseudonym(w, aliceNonce);
|
||||
sendTimestamp(w, localTimestamp);
|
||||
sendTransportProperties(w, localProperties);
|
||||
w.flush();
|
||||
remoteAuthor = receivePseudonym(r, bobNonce);
|
||||
remoteTimestamp = receiveTimestamp(r);
|
||||
remoteProperties = receiveTransportProperties(r);
|
||||
} else {
|
||||
remoteInfo = receiveContactInfo(recordReader);
|
||||
sendContactInfo(recordWriter, localAuthor, localProperties,
|
||||
localSignature, localTimestamp);
|
||||
recordWriter.flush();
|
||||
remoteAuthor = receivePseudonym(r, aliceNonce);
|
||||
remoteTimestamp = receiveTimestamp(r);
|
||||
remoteProperties = receiveTransportProperties(r);
|
||||
sendPseudonym(w, bobNonce);
|
||||
sendTimestamp(w, localTimestamp);
|
||||
sendTransportProperties(w, localProperties);
|
||||
w.flush();
|
||||
}
|
||||
// Close the outgoing stream
|
||||
recordWriter.close();
|
||||
// Skip any remaining records from the incoming stream
|
||||
try {
|
||||
while (true) recordReader.readRecord();
|
||||
} catch (EOFException expected) {
|
||||
LOG.info("End of stream");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Close the outgoing stream and expect EOF on the incoming stream
|
||||
w.close();
|
||||
if (!r.eof()) LOG.warning("Unexpected data at end of connection");
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the contact's signature
|
||||
if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) {
|
||||
LOG.warning("Invalid signature");
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn);
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// The agreed timestamp is the minimum of the peers' timestamps
|
||||
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
|
||||
long timestamp = Math.min(localTimestamp, remoteTimestamp);
|
||||
|
||||
try {
|
||||
// Add the contact
|
||||
ContactId contactId = addContact(remoteInfo.author, timestamp,
|
||||
remoteInfo.properties);
|
||||
ContactId contactId = addContact(remoteAuthor, timestamp,
|
||||
remoteProperties);
|
||||
// Reuse the connection as a transport connection
|
||||
connectionManager.manageOutgoingConnection(contactId, transportId,
|
||||
conn);
|
||||
// Pseudonym exchange succeeded
|
||||
LOG.info("Pseudonym exchange succeeded");
|
||||
listener.contactExchangeSucceeded(remoteInfo.author);
|
||||
listener.contactExchangeSucceeded(remoteAuthor);
|
||||
} catch (ContactExistsException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(conn);
|
||||
listener.duplicateContact(remoteInfo.author);
|
||||
tryToClose(conn, true);
|
||||
listener.duplicateContact(remoteAuthor);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(conn);
|
||||
tryToClose(conn, true);
|
||||
listener.contactExchangeFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] sign(LocalAuthor author, byte[] nonce) {
|
||||
try {
|
||||
return crypto.sign(SIGNING_LABEL_EXCHANGE, nonce,
|
||||
author.getPrivateKey());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new AssertionError();
|
||||
private void sendPseudonym(BdfWriter w, byte[] nonce)
|
||||
throws GeneralSecurityException, IOException {
|
||||
// Sign the nonce
|
||||
byte[] privateKey = localAuthor.getPrivateKey();
|
||||
byte[] sig = crypto.sign(SIGNING_LABEL_EXCHANGE, nonce, privateKey);
|
||||
|
||||
// Write the name, public key and signature
|
||||
w.writeListStart();
|
||||
w.writeLong(localAuthor.getFormatVersion());
|
||||
w.writeString(localAuthor.getName());
|
||||
w.writeRaw(localAuthor.getPublicKey());
|
||||
w.writeRaw(sig);
|
||||
w.writeListEnd();
|
||||
LOG.info("Sent pseudonym");
|
||||
}
|
||||
|
||||
private Author receivePseudonym(BdfReader r, byte[] nonce)
|
||||
throws GeneralSecurityException, IOException {
|
||||
// Read the format version, name, public key and signature
|
||||
r.readListStart();
|
||||
int formatVersion = (int) r.readLong();
|
||||
if (formatVersion != FORMAT_VERSION) throw new FormatException();
|
||||
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
|
||||
if (name.isEmpty()) throw new FormatException();
|
||||
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
||||
if (publicKey.length == 0) throw new FormatException();
|
||||
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
||||
if (sig.length == 0) throw new FormatException();
|
||||
r.readListEnd();
|
||||
LOG.info("Received pseudonym");
|
||||
// Verify the signature
|
||||
if (!crypto.verifySignature(sig, SIGNING_LABEL_EXCHANGE, nonce,
|
||||
publicKey)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Invalid signature");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
||||
}
|
||||
|
||||
private boolean verify(Author author, byte[] nonce, byte[] signature) {
|
||||
try {
|
||||
return crypto.verifySignature(signature, SIGNING_LABEL_EXCHANGE,
|
||||
nonce, author.getPublicKey());
|
||||
} catch (GeneralSecurityException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendContactInfo(RecordWriter recordWriter, Author author,
|
||||
Map<TransportId, TransportProperties> properties, byte[] signature,
|
||||
long timestamp) throws IOException {
|
||||
BdfList authorList = clientHelper.toList(author);
|
||||
BdfDictionary props = clientHelper.toDictionary(properties);
|
||||
BdfList payload = BdfList.of(authorList, props, signature, timestamp);
|
||||
recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO,
|
||||
clientHelper.toByteArray(payload)));
|
||||
LOG.info("Sent contact info");
|
||||
}
|
||||
|
||||
private ContactInfo receiveContactInfo(RecordReader recordReader)
|
||||
private void sendTimestamp(BdfWriter w, long timestamp)
|
||||
throws IOException {
|
||||
Record record;
|
||||
do {
|
||||
record = recordReader.readRecord();
|
||||
if (record.getProtocolVersion() != PROTOCOL_VERSION)
|
||||
throw new FormatException();
|
||||
} while (record.getRecordType() != CONTACT_INFO);
|
||||
LOG.info("Received contact info");
|
||||
BdfList payload = clientHelper.toList(record.getPayload());
|
||||
checkSize(payload, 4);
|
||||
Author author = clientHelper.parseAndValidateAuthor(payload.getList(0));
|
||||
BdfDictionary props = payload.getDictionary(1);
|
||||
Map<TransportId, TransportProperties> properties =
|
||||
clientHelper.parseAndValidateTransportPropertiesMap(props);
|
||||
byte[] signature = payload.getRaw(2);
|
||||
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
|
||||
long timestamp = payload.getLong(3);
|
||||
w.writeLong(timestamp);
|
||||
LOG.info("Sent timestamp");
|
||||
}
|
||||
|
||||
private long receiveTimestamp(BdfReader r) throws IOException {
|
||||
long timestamp = r.readLong();
|
||||
if (timestamp < 0) throw new FormatException();
|
||||
return new ContactInfo(author, properties, signature, timestamp);
|
||||
LOG.info("Received timestamp");
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
private void sendTransportProperties(BdfWriter w,
|
||||
Map<TransportId, TransportProperties> local) throws IOException {
|
||||
w.writeListStart();
|
||||
for (Entry<TransportId, TransportProperties> e : local.entrySet())
|
||||
w.writeList(BdfList.of(e.getKey().getString(), e.getValue()));
|
||||
w.writeListEnd();
|
||||
}
|
||||
|
||||
private Map<TransportId, TransportProperties> receiveTransportProperties(
|
||||
BdfReader r) throws IOException {
|
||||
Map<TransportId, TransportProperties> remote = new HashMap<>();
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
r.readListStart();
|
||||
String id = r.readString(MAX_TRANSPORT_ID_LENGTH);
|
||||
if (id.isEmpty()) throw new FormatException();
|
||||
TransportProperties p = new TransportProperties();
|
||||
r.readDictionaryStart();
|
||||
while (!r.hasDictionaryEnd()) {
|
||||
if (p.size() == MAX_PROPERTIES_PER_TRANSPORT)
|
||||
throw new FormatException();
|
||||
String key = r.readString(MAX_PROPERTY_LENGTH);
|
||||
String value = r.readString(MAX_PROPERTY_LENGTH);
|
||||
p.put(key, value);
|
||||
}
|
||||
r.readDictionaryEnd();
|
||||
r.readListEnd();
|
||||
remote.put(new TransportId(id), p);
|
||||
}
|
||||
r.readListEnd();
|
||||
return remote;
|
||||
}
|
||||
|
||||
private ContactId addContact(Author remoteAuthor, long timestamp,
|
||||
@@ -300,30 +324,13 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
LOG.info("Closing connection");
|
||||
conn.getReader().dispose(true, true);
|
||||
conn.getWriter().dispose(true);
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ContactInfo {
|
||||
|
||||
private final Author author;
|
||||
private final Map<TransportId, TransportProperties> properties;
|
||||
private final byte[] signature;
|
||||
private final long timestamp;
|
||||
|
||||
private ContactInfo(Author author,
|
||||
Map<TransportId, TransportProperties> properties,
|
||||
byte[] signature, long timestamp) {
|
||||
this.author = author;
|
||||
this.properties = properties;
|
||||
this.signature = signature;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
@Module
|
||||
public class CryptoExecutorModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
ExecutorService cryptoExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of executor threads.
|
||||
* <p>
|
||||
* The number of available processors can change during the lifetime of the
|
||||
* JVM, so this is just a reasonable guess.
|
||||
*/
|
||||
private static final int MAX_EXECUTOR_THREADS =
|
||||
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
private final ExecutorService cryptoExecutor;
|
||||
|
||||
public CryptoExecutorModule() {
|
||||
// Use an unbounded queue
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Create a limited # of threads and keep them in the pool for 60 secs
|
||||
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
|
||||
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@CryptoExecutor
|
||||
ExecutorService provideCryptoExecutorService(
|
||||
LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(cryptoExecutor);
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CryptoExecutor
|
||||
Executor provideCryptoExecutor() {
|
||||
return cryptoExecutor;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,64 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
@Module
|
||||
public class CryptoModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
ExecutorService cryptoExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of executor threads.
|
||||
* <p>
|
||||
* The number of available processors can change during the lifetime of the
|
||||
* JVM, so this is just a reasonable guess.
|
||||
*/
|
||||
private static final int MAX_EXECUTOR_THREADS =
|
||||
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
private final ExecutorService cryptoExecutor;
|
||||
|
||||
public CryptoModule() {
|
||||
// Use an unbounded queue
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Create a limited # of threads and keep them in the pool for 60 secs
|
||||
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
|
||||
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
AuthenticatedCipher provideAuthenticatedCipher() {
|
||||
return new XSalsa20Poly1305AuthenticatedCipher();
|
||||
@@ -63,6 +103,21 @@ public class CryptoModule {
|
||||
return keyAgreementCrypto;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@CryptoExecutor
|
||||
ExecutorService getCryptoExecutorService(
|
||||
LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(cryptoExecutor);
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CryptoExecutor
|
||||
Executor getCryptoExecutor() {
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SecureRandom getSecureRandom(CryptoComponent crypto) {
|
||||
return crypto.getSecureRandom();
|
||||
|
||||
@@ -266,8 +266,7 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<Group> getGroups(T txn, ClientId c, int majorVersion)
|
||||
throws DbException;
|
||||
Collection<Group> getGroups(T txn, ClientId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the given group's visibility to the given contact, or
|
||||
@@ -322,16 +321,16 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all delivered messages in the given group.
|
||||
* Returns the IDs of all messages in the given group.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any delivered messages in the given group with
|
||||
* metadata that matches all entries in the given query. If the query is
|
||||
* empty, the IDs of all delivered messages are returned.
|
||||
* Returns the IDs of any messages in the given group with metadata
|
||||
* matching all entries in the given query. If the query is empty, the IDs
|
||||
* of all messages are returned.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -347,9 +346,9 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for any delivered messages in the given group with
|
||||
* metadata that matches all entries in the given query. If the query is
|
||||
* empty, the metadata for all delivered messages is returned.
|
||||
* Returns the metadata for any messages in the given group with metadata
|
||||
* matching all entries in the given query. If the query is empty, the
|
||||
* metadata for all messages is returned.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -357,8 +356,7 @@ interface Database<T> {
|
||||
Metadata query) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given delivered or pending message.
|
||||
* This is only meant to be used by the ValidationManager.
|
||||
* Returns the metadata for the given delivered message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -366,7 +364,7 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given delivered message.
|
||||
* Returns the metadata for the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -380,8 +378,8 @@ interface Database<T> {
|
||||
State getMessageState(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the status of all delivered messages in the given group with
|
||||
* respect to the given contact.
|
||||
* Returns the status of all messages in the given group with respect
|
||||
* to the given contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -389,13 +387,11 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the status of the given delivered message with respect to the
|
||||
* given contact, or null if the message's group is invisible to the
|
||||
* Returns the status of the given message with respect to the given
|
||||
* contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@Nullable
|
||||
MessageStatus getMessageStatus(T txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
@@ -658,7 +654,7 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the given transport keys following key rotation.
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
*/
|
||||
void updateTransportKeys(T txn, KeySet ks) throws DbException;
|
||||
void updateTransportKeys(T txn, Collection<KeySet> keys) throws DbException;
|
||||
}
|
||||
|
||||
@@ -435,10 +435,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Group> getGroups(Transaction transaction, ClientId c,
|
||||
int majorVersion) throws DbException {
|
||||
public Collection<Group> getGroups(Transaction transaction, ClientId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getGroups(txn, c, majorVersion);
|
||||
return db.getGroups(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -466,15 +466,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getLocalAuthors(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Transaction transaction,
|
||||
GroupId g) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchGroupException();
|
||||
return db.getMessageIds(txn, g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||
throws DbException {
|
||||
@@ -560,13 +551,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchGroupException();
|
||||
if (db.getGroupVisibility(txn, c, g) == INVISIBLE) {
|
||||
// No status rows exist - return default statuses
|
||||
Collection<MessageStatus> statuses = new ArrayList<>();
|
||||
for (MessageId m : db.getMessageIds(txn, g))
|
||||
statuses.add(new MessageStatus(m, c, false, false));
|
||||
return statuses;
|
||||
}
|
||||
return db.getMessageStatus(txn, c, g);
|
||||
}
|
||||
|
||||
@@ -578,9 +562,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
MessageStatus status = db.getMessageStatus(txn, c, m);
|
||||
if (status == null) return new MessageStatus(m, c, false, false);
|
||||
return status;
|
||||
return db.getMessageStatus(txn, c, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -921,9 +903,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Collection<KeySet> keys) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
Collection<KeySet> filtered = new ArrayList<>();
|
||||
for (KeySet ks : keys) {
|
||||
TransportId t = ks.getTransportKeys().getTransportId();
|
||||
if (db.containsTransport(txn, t)) db.updateTransportKeys(txn, ks);
|
||||
if (db.containsTransport(txn, t)) filtered.add(ks);
|
||||
}
|
||||
db.updateTransportKeys(txn, filtered);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +74,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 38;
|
||||
|
||||
// Rotation period offsets for incoming transport keys
|
||||
private static final int OFFSET_PREV = -1;
|
||||
private static final int OFFSET_CURR = 0;
|
||||
private static final int OFFSET_NEXT = 1;
|
||||
static final int CODE_SCHEMA_VERSION = 36;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -117,7 +112,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE TABLE groups"
|
||||
+ " (groupId _HASH NOT NULL,"
|
||||
+ " clientId _STRING NOT NULL,"
|
||||
+ " majorVersion INT NOT NULL,"
|
||||
+ " descriptor _BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
@@ -260,8 +254,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " headerKey _SECRET NOT NULL,"
|
||||
+ " base BIGINT NOT NULL,"
|
||||
+ " bitmap _BINARY NOT NULL,"
|
||||
+ " periodOffset INT NOT NULL,"
|
||||
+ " PRIMARY KEY (transportId, keySetId, periodOffset),"
|
||||
+ " PRIMARY KEY (transportId, keySetId, rotationPeriod),"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
@@ -276,9 +269,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||
+ " ON contacts (authorId)";
|
||||
|
||||
private static final String INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION =
|
||||
"CREATE INDEX IF NOT EXISTS groupsByClientIdMajorVersion"
|
||||
+ " ON groups (clientId, majorVersion)";
|
||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
||||
+ " ON groups (clientId)";
|
||||
|
||||
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
@@ -445,7 +438,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||
@@ -613,14 +606,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public void addGroup(Connection txn, Group g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groups"
|
||||
+ " (groupId, clientId, majorVersion, descriptor)"
|
||||
+ " VALUES (?, ?, ?, ?)";
|
||||
String sql = "INSERT INTO groups (groupId, clientId, descriptor)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getId().getBytes());
|
||||
ps.setString(2, g.getClientId().getString());
|
||||
ps.setInt(3, g.getMajorVersion());
|
||||
ps.setBytes(4, g.getDescriptor());
|
||||
ps.setBytes(3, g.getDescriptor());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -917,9 +908,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.close();
|
||||
// Store the incoming keys
|
||||
sql = "INSERT INTO incomingKeys (keySetId, contactId, transportId,"
|
||||
+ " rotationPeriod, tagKey, headerKey, base, bitmap,"
|
||||
+ " periodOffset)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, keySetId.getInt());
|
||||
if (c == null) ps.setNull(2, INTEGER);
|
||||
@@ -932,7 +922,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(6, inPrev.getHeaderKey().getBytes());
|
||||
ps.setLong(7, inPrev.getWindowBase());
|
||||
ps.setBytes(8, inPrev.getWindowBitmap());
|
||||
ps.setInt(9, OFFSET_PREV);
|
||||
ps.addBatch();
|
||||
// Current rotation period
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
@@ -941,7 +930,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(6, inCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(7, inCurr.getWindowBase());
|
||||
ps.setBytes(8, inCurr.getWindowBitmap());
|
||||
ps.setInt(9, OFFSET_CURR);
|
||||
ps.addBatch();
|
||||
// Next rotation period
|
||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||
@@ -950,7 +938,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(6, inNext.getHeaderKey().getBytes());
|
||||
ps.setLong(7, inNext.getWindowBase());
|
||||
ps.setBytes(8, inNext.getWindowBitmap());
|
||||
ps.setInt(9, OFFSET_NEXT);
|
||||
ps.addBatch();
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != 3) throw new DbStateException();
|
||||
@@ -1349,18 +1336,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT clientId, majorVersion, descriptor"
|
||||
+ " FROM groups WHERE groupId = ?";
|
||||
String sql = "SELECT clientId, descriptor FROM groups"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
ClientId clientId = new ClientId(rs.getString(1));
|
||||
int majorVersion = rs.getInt(2);
|
||||
byte[] descriptor = rs.getBytes(3);
|
||||
byte[] descriptor = rs.getBytes(2);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return new Group(g, clientId, majorVersion, descriptor);
|
||||
return new Group(g, clientId, descriptor);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
@@ -1369,22 +1355,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Group> getGroups(Connection txn, ClientId c,
|
||||
int majorVersion) throws DbException {
|
||||
public Collection<Group> getGroups(Connection txn, ClientId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT groupId, descriptor FROM groups"
|
||||
+ " WHERE clientId = ? AND majorVersion = ?";
|
||||
+ " WHERE clientId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, c.getString());
|
||||
ps.setInt(2, majorVersion);
|
||||
rs = ps.executeQuery();
|
||||
List<Group> groups = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
GroupId id = new GroupId(rs.getBytes(1));
|
||||
byte[] descriptor = rs.getBytes(2);
|
||||
groups.add(new Group(id, c, majorVersion, descriptor));
|
||||
groups.add(new Group(id, c, descriptor));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1516,11 +1501,32 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId FROM messages"
|
||||
+ " WHERE groupId = ? AND state = ?";
|
||||
String sql = "SELECT messageId FROM messages WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return ids;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<MessageId> getMessageIds(Connection txn, GroupId g,
|
||||
State state) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId FROM messages"
|
||||
+ " WHERE state = ? AND groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
@@ -1538,7 +1544,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getMessageIds(Connection txn, GroupId g,
|
||||
Metadata query) throws DbException {
|
||||
// If there are no query terms, return all delivered messages
|
||||
if (query.isEmpty()) return getMessageIds(txn, g);
|
||||
if (query.isEmpty()) return getMessageIds(txn, g, DELIVERED);
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1697,11 +1703,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, txCount > 0, seen FROM statuses"
|
||||
+ " WHERE groupId = ? AND contactId = ? AND state = ?";
|
||||
+ " WHERE groupId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setInt(3, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageStatus> statuses = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
@@ -1721,29 +1726,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MessageStatus getMessageStatus(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT txCount > 0, seen FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ? AND state = ?";
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setInt(3, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
MessageStatus status = null;
|
||||
if (rs.next()) {
|
||||
boolean sent = rs.getBoolean(1);
|
||||
boolean seen = rs.getBoolean(2);
|
||||
status = new MessageStatus(m, c, sent, seen);
|
||||
}
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
boolean sent = rs.getBoolean(1);
|
||||
boolean seen = rs.getBoolean(2);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return status;
|
||||
return new MessageStatus(m, c, sent, seen);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
@@ -2141,7 +2141,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " base, bitmap"
|
||||
+ " FROM incomingKeys"
|
||||
+ " WHERE transportId = ?"
|
||||
+ " ORDER BY keySetId, periodOffset";
|
||||
+ " ORDER BY keySetId, rotationPeriod";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
@@ -2563,14 +2563,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Remove status rows for the messages in the group
|
||||
sql = "DELETE FROM statuses"
|
||||
+ " WHERE contactId = ? AND groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
for (MessageId m : getMessageIds(txn, g)) removeStatus(txn, c, m);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2654,6 +2647,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "DELETE FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransport(Connection txn, TransportId t)
|
||||
throws DbException {
|
||||
@@ -2933,69 +2944,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportKeys(Connection txn, KeySet ks)
|
||||
public void updateTransportKeys(Connection txn, Collection<KeySet> keys)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Update the outgoing keys
|
||||
String sql = "UPDATE outgoingKeys SET rotationPeriod = ?,"
|
||||
+ " tagKey = ?, headerKey = ?, stream = ?"
|
||||
+ " WHERE transportId = ? AND keySetId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
for (KeySet ks : keys) {
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
ps.setLong(1, outCurr.getRotationPeriod());
|
||||
ps.setBytes(2, outCurr.getTagKey().getBytes());
|
||||
ps.setBytes(3, outCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(4, outCurr.getStreamCounter());
|
||||
ps.setString(5, k.getTransportId().getString());
|
||||
ps.setInt(6, ks.getKeySetId().getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update the incoming keys
|
||||
sql = "UPDATE incomingKeys SET rotationPeriod = ?,"
|
||||
+ " tagKey = ?, headerKey = ?, base = ?, bitmap = ?"
|
||||
+ " WHERE transportId = ? AND keySetId = ?"
|
||||
+ " AND periodOffset = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(6, k.getTransportId().getString());
|
||||
ps.setInt(7, ks.getKeySetId().getInt());
|
||||
// Previous rotation period
|
||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||
ps.setLong(1, inPrev.getRotationPeriod());
|
||||
ps.setBytes(2, inPrev.getTagKey().getBytes());
|
||||
ps.setBytes(3, inPrev.getHeaderKey().getBytes());
|
||||
ps.setLong(4, inPrev.getWindowBase());
|
||||
ps.setBytes(5, inPrev.getWindowBitmap());
|
||||
ps.setInt(8, OFFSET_PREV);
|
||||
ps.addBatch();
|
||||
// Current rotation period
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
ps.setLong(1, inCurr.getRotationPeriod());
|
||||
ps.setBytes(2, inCurr.getTagKey().getBytes());
|
||||
ps.setBytes(3, inCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(4, inCurr.getWindowBase());
|
||||
ps.setBytes(5, inCurr.getWindowBitmap());
|
||||
ps.setInt(8, OFFSET_CURR);
|
||||
ps.addBatch();
|
||||
// Next rotation period
|
||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||
ps.setLong(1, inNext.getRotationPeriod());
|
||||
ps.setBytes(2, inNext.getTagKey().getBytes());
|
||||
ps.setBytes(3, inNext.getHeaderKey().getBytes());
|
||||
ps.setLong(4, inNext.getWindowBase());
|
||||
ps.setBytes(5, inNext.getWindowBitmap());
|
||||
ps.setInt(8, OFFSET_NEXT);
|
||||
ps.addBatch();
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != 3) throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows < 0 || rows > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
removeTransportKeys(txn, k.getTransportId(), ks.getKeySetId());
|
||||
addTransportKeys(txn, ks.getContactId(), k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -46,8 +44,6 @@ class KeyAgreementConnector {
|
||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||
private final PluginManager pluginManager;
|
||||
private final ConnectionChooser connectionChooser;
|
||||
private final RecordReaderFactory recordReaderFactory;
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
@@ -58,15 +54,11 @@ class KeyAgreementConnector {
|
||||
|
||||
KeyAgreementConnector(Callbacks callbacks,
|
||||
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser,
|
||||
RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
ConnectionChooser connectionChooser) {
|
||||
this.callbacks = callbacks;
|
||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionChooser = connectionChooser;
|
||||
this.recordReaderFactory = recordReaderFactory;
|
||||
this.recordWriterFactory = recordWriterFactory;
|
||||
}
|
||||
|
||||
Payload listen(KeyPair localKeyPair) {
|
||||
@@ -127,8 +119,7 @@ class KeyAgreementConnector {
|
||||
KeyAgreementConnection chosen =
|
||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||
if (chosen == null) return null;
|
||||
return new KeyAgreementTransport(recordReaderFactory,
|
||||
recordWriterFactory, chosen);
|
||||
return new KeyAgreementTransport(chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
@@ -14,13 +14,10 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
@@ -51,17 +48,14 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
KeyAgreementTaskImpl(CryptoComponent crypto,
|
||||
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser,
|
||||
RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
ConnectionChooser connectionChooser) {
|
||||
this.crypto = crypto;
|
||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||
this.eventBus = eventBus;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
localKeyPair = crypto.generateAgreementKeyPair();
|
||||
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
|
||||
pluginManager, connectionChooser, recordReaderFactory,
|
||||
recordWriterFactory);
|
||||
pluginManager, connectionChooser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,7 +71,6 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
if (localPayload != null) {
|
||||
if (remotePayload == null) connector.stopListening();
|
||||
else interrupt();
|
||||
eventBus.broadcast(new KeyAgreementStoppedListeningEvent());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,9 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -17,6 +14,8 @@ import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_PAYLOAD_LENGTH_OFFSET;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
|
||||
@@ -31,17 +30,14 @@ class KeyAgreementTransport {
|
||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||
|
||||
private final KeyAgreementConnection kac;
|
||||
private final RecordReader reader;
|
||||
private final RecordWriter writer;
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
|
||||
KeyAgreementTransport(RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory, KeyAgreementConnection kac)
|
||||
KeyAgreementTransport(KeyAgreementConnection kac)
|
||||
throws IOException {
|
||||
this.kac = kac;
|
||||
InputStream in = kac.getConnection().getReader().getInputStream();
|
||||
reader = recordReaderFactory.createRecordReader(in);
|
||||
OutputStream out = kac.getConnection().getWriter().getOutputStream();
|
||||
writer = recordWriterFactory.createRecordWriter(out);
|
||||
in = kac.getConnection().getReader().getInputStream();
|
||||
out = kac.getConnection().getWriter().getOutputStream();
|
||||
}
|
||||
|
||||
public DuplexTransportConnection getConnection() {
|
||||
@@ -78,8 +74,9 @@ class KeyAgreementTransport {
|
||||
tryToClose(exception);
|
||||
}
|
||||
|
||||
private void tryToClose(boolean exception) {
|
||||
public void tryToClose(boolean exception) {
|
||||
try {
|
||||
LOG.info("Closing connection");
|
||||
kac.getConnection().getReader().dispose(exception, true);
|
||||
kac.getConnection().getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
@@ -88,27 +85,59 @@ class KeyAgreementTransport {
|
||||
}
|
||||
|
||||
private void writeRecord(byte type, byte[] payload) throws IOException {
|
||||
writer.writeRecord(new Record(PROTOCOL_VERSION, type, payload));
|
||||
writer.flush();
|
||||
byte[] recordHeader = new byte[RECORD_HEADER_LENGTH];
|
||||
recordHeader[0] = PROTOCOL_VERSION;
|
||||
recordHeader[1] = type;
|
||||
ByteUtils.writeUint16(payload.length, recordHeader,
|
||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
||||
out.write(recordHeader);
|
||||
out.write(payload);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private byte[] readRecord(byte expectedType) throws AbortException {
|
||||
while (true) {
|
||||
byte[] header = readHeader();
|
||||
byte version = header[0], type = header[1];
|
||||
int len = ByteUtils.readUint16(header,
|
||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
||||
// Reject unrecognised protocol version
|
||||
if (version != PROTOCOL_VERSION) throw new AbortException(false);
|
||||
if (type == ABORT) throw new AbortException(true);
|
||||
if (type == expectedType) {
|
||||
try {
|
||||
return readData(len);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
// Reject recognised but unexpected record type
|
||||
if (type == KEY || type == CONFIRM) throw new AbortException(false);
|
||||
// Skip unrecognised record type
|
||||
try {
|
||||
Record record = reader.readRecord();
|
||||
// Reject unrecognised protocol version
|
||||
if (record.getProtocolVersion() != PROTOCOL_VERSION)
|
||||
throw new AbortException(false);
|
||||
byte type = record.getRecordType();
|
||||
if (type == ABORT) throw new AbortException(true);
|
||||
if (type == expectedType) return record.getPayload();
|
||||
// Reject recognised but unexpected record type
|
||||
if (type == KEY || type == CONFIRM)
|
||||
throw new AbortException(false);
|
||||
// Skip unrecognised record type
|
||||
readData(len);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readHeader() throws AbortException {
|
||||
try {
|
||||
return readData(RECORD_HEADER_LENGTH);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readData(int len) throws IOException {
|
||||
byte[] data = new byte[len];
|
||||
int offset = 0;
|
||||
while (offset < data.length) {
|
||||
int read = in.read(data, offset, data.length - offset);
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
@NotNullByDefault
|
||||
interface BluetoothConnectionLimiter {
|
||||
|
||||
/**
|
||||
* Informs the limiter that key agreement has started.
|
||||
*/
|
||||
void keyAgreementStarted();
|
||||
|
||||
/**
|
||||
* Informs the limiter that key agreement has ended.
|
||||
*/
|
||||
void keyAgreementEnded();
|
||||
|
||||
/**
|
||||
* Returns true if a contact connection can be opened. This method does not
|
||||
* need to be called for key agreement connections.
|
||||
*/
|
||||
boolean canOpenContactConnection();
|
||||
|
||||
/**
|
||||
* Informs the limiter that a contact connection has been opened. The
|
||||
* limiter may close the new connection if key agreement is in progress.
|
||||
* <p/>
|
||||
* Returns false if the limiter has closed the new connection.
|
||||
*/
|
||||
boolean contactConnectionOpened(DuplexTransportConnection conn);
|
||||
|
||||
/**
|
||||
* Informs the limiter that a key agreement connection has been opened.
|
||||
*/
|
||||
void keyAgreementConnectionOpened(DuplexTransportConnection conn);
|
||||
|
||||
/**
|
||||
* Informs the limiter that the given connection has been closed.
|
||||
*/
|
||||
void connectionClosed(DuplexTransportConnection conn);
|
||||
|
||||
/**
|
||||
* Informs the limiter that all connections have been closed.
|
||||
*/
|
||||
void allConnectionsClosed();
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
|
||||
|
||||
private final Object lock = new Object();
|
||||
// The following are locking: lock
|
||||
private final LinkedList<DuplexTransportConnection> connections =
|
||||
new LinkedList<>();
|
||||
private boolean keyAgreementInProgress = false;
|
||||
|
||||
@Override
|
||||
public void keyAgreementStarted() {
|
||||
List<DuplexTransportConnection> close;
|
||||
synchronized (lock) {
|
||||
keyAgreementInProgress = true;
|
||||
close = new ArrayList<>(connections);
|
||||
connections.clear();
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Key agreement started, closing " + close.size() +
|
||||
" connections");
|
||||
}
|
||||
for (DuplexTransportConnection conn : close) tryToClose(conn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementEnded() {
|
||||
synchronized (lock) {
|
||||
keyAgreementInProgress = false;
|
||||
}
|
||||
LOG.info("Key agreement ended");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOpenContactConnection() {
|
||||
synchronized (lock) {
|
||||
if (keyAgreementInProgress) {
|
||||
LOG.info("Can't open contact connection during key agreement");
|
||||
return false;
|
||||
} else {
|
||||
LOG.info("Can open contact connection");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
|
||||
boolean accept = true;
|
||||
synchronized (lock) {
|
||||
if (keyAgreementInProgress) {
|
||||
LOG.info("Refusing contact connection during key agreement");
|
||||
accept = false;
|
||||
} else {
|
||||
LOG.info("Accepting contact connection");
|
||||
connections.add(conn);
|
||||
}
|
||||
}
|
||||
if (!accept) tryToClose(conn);
|
||||
return accept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
|
||||
synchronized (lock) {
|
||||
LOG.info("Accepting key agreement connection");
|
||||
connections.add(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getWriter().dispose(false);
|
||||
conn.getReader().dispose(false, false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionClosed(DuplexTransportConnection conn) {
|
||||
synchronized (lock) {
|
||||
connections.remove(conn);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connection closed, " + connections.size() + " open");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allConnectionsClosed() {
|
||||
synchronized (lock) {
|
||||
connections.clear();
|
||||
LOG.info("All connections closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -53,8 +51,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
final BluetoothConnectionLimiter connectionLimiter;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
@@ -95,10 +91,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException;
|
||||
|
||||
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||
Executor ioExecutor, SecureRandom secureRandom,
|
||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
this.connectionLimiter = connectionLimiter;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
@@ -116,7 +110,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
connectionLimiter.allConnectionsClosed();
|
||||
callback.transportDisabled();
|
||||
}
|
||||
|
||||
@@ -220,8 +213,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
return;
|
||||
}
|
||||
backoff.reset();
|
||||
if (connectionLimiter.contactConnectionOpened(conn))
|
||||
callback.incomingConnectionCreated(conn);
|
||||
callback.incomingConnectionCreated(conn);
|
||||
if (!running) return;
|
||||
}
|
||||
}
|
||||
@@ -265,12 +257,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
if (!connectionLimiter.canOpenContactConnection()) return;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) {
|
||||
backoff.reset();
|
||||
if (connectionLimiter.contactConnectionOpened(conn))
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -310,16 +300,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn == null) return null;
|
||||
// TODO: Why don't we reset the backoff here?
|
||||
return connectionLimiter.contactConnectionOpened(conn) ? conn : null;
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -369,9 +355,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
|
||||
return conn;
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
@@ -392,10 +376,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString()))
|
||||
ioExecutor.execute(this::onSettingsUpdated);
|
||||
} else if (e instanceof KeyAgreementListeningEvent) {
|
||||
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
|
||||
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
|
||||
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +408,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
DuplexTransportConnection conn = acceptConnection(ss);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
connectionLimiter.keyAgreementConnectionOpened(conn);
|
||||
return new KeyAgreementConnection(conn, ID);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -16,8 +15,6 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
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.MINOR_VERSION;
|
||||
|
||||
@Module
|
||||
public class PropertiesModule {
|
||||
@@ -36,8 +33,7 @@ public class PropertiesModule {
|
||||
Clock clock) {
|
||||
TransportPropertyValidator validator = new TransportPropertyValidator(
|
||||
clientHelper, metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@@ -46,14 +42,11 @@ public class PropertiesModule {
|
||||
TransportPropertyManager getTransportPropertyManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager, ContactManager contactManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
TransportPropertyManagerImpl transportPropertyManager) {
|
||||
lifecycleManager.registerClient(transportPropertyManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
transportPropertyManager);
|
||||
contactManager.registerContactHook(transportPropertyManager);
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
MINOR_VERSION, transportPropertyManager);
|
||||
return transportPropertyManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,12 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
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.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -37,14 +34,15 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
Client, ContactHook, ClientVersioningHook, IncomingMessageHook {
|
||||
Client, ContactHook, IncomingMessageHook {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final MetadataParser metadataParser;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Clock clock;
|
||||
@@ -52,25 +50,22 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
@Inject
|
||||
TransportPropertyManagerImpl(DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MetadataParser metadataParser,
|
||||
ClientHelper clientHelper, MetadataParser metadataParser,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.metadataParser = metadataParser;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clock = clock;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
CLIENT_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
// Ensure we've set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
}
|
||||
|
||||
@@ -78,11 +73,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
// Return if we've already set things up for this contact
|
||||
if (db.containsGroup(txn, g.getId())) return;
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(txn, g);
|
||||
// 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);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Copy the latest local properties into the group
|
||||
Map<TransportId, TransportProperties> local = getLocalProperties(txn);
|
||||
for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
|
||||
@@ -96,14 +91,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
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
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
@@ -302,7 +289,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, c);
|
||||
CLIENT_VERSION, c);
|
||||
}
|
||||
|
||||
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class RecordModule {
|
||||
|
||||
@Provides
|
||||
RecordReaderFactory provideRecordReaderFactory() {
|
||||
return new RecordReaderFactoryImpl();
|
||||
}
|
||||
|
||||
@Provides
|
||||
RecordWriterFactory provideRecordWriterFactory() {
|
||||
return new RecordWriterFactoryImpl();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
class RecordReaderFactoryImpl implements RecordReaderFactory {
|
||||
|
||||
@Override
|
||||
public RecordReader createRecordReader(InputStream in) {
|
||||
return new RecordReaderImpl(in);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class RecordReaderImpl implements RecordReader {
|
||||
|
||||
private final DataInputStream in;
|
||||
private final byte[] header = new byte[RECORD_HEADER_BYTES];
|
||||
|
||||
RecordReaderImpl(InputStream in) {
|
||||
this.in = new DataInputStream(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record readRecord() throws IOException {
|
||||
in.readFully(header);
|
||||
byte protocolVersion = header[0];
|
||||
byte recordType = header[1];
|
||||
int payloadLength = ByteUtils.readUint16(header, 2);
|
||||
if (payloadLength < 0 || payloadLength > MAX_RECORD_PAYLOAD_BYTES)
|
||||
throw new FormatException();
|
||||
byte[] payload = new byte[payloadLength];
|
||||
in.readFully(payload);
|
||||
return new Record(protocolVersion, recordType, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
class RecordWriterFactoryImpl implements RecordWriterFactory {
|
||||
|
||||
@Override
|
||||
public RecordWriter createRecordWriter(OutputStream out) {
|
||||
return new RecordWriterImpl(out);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class RecordWriterImpl implements RecordWriter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final byte[] header = new byte[RECORD_HEADER_BYTES];
|
||||
|
||||
RecordWriterImpl(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRecord(Record r) throws IOException {
|
||||
byte[] payload = r.getPayload();
|
||||
header[0] = r.getProtocolVersion();
|
||||
header[1] = r.getRecordType();
|
||||
ByteUtils.writeUint16(payload.length, header, 2);
|
||||
out.write(header);
|
||||
out.write(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
|
||||
@@ -39,8 +39,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
/**
|
||||
* An outgoing {@link SyncSession} suitable for duplex transports. The session
|
||||
@@ -67,7 +67,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final Clock clock;
|
||||
private final ContactId contactId;
|
||||
private final int maxLatency, maxIdleTime;
|
||||
private final SyncRecordWriter recordWriter;
|
||||
private final RecordWriter recordWriter;
|
||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||
|
||||
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
||||
@@ -81,7 +81,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
|
||||
int maxIdleTime, SyncRecordWriter recordWriter) {
|
||||
int maxIdleTime, RecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
@@ -273,7 +273,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
b = db.generateRequestedBatch(txn, contactId,
|
||||
MAX_RECORD_PAYLOAD_BYTES, maxLatency);
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
|
||||
@@ -20,9 +20,6 @@ import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
@NotNullByDefault
|
||||
class GroupFactoryImpl implements GroupFactory {
|
||||
|
||||
private static final byte[] FORMAT_VERSION_BYTES =
|
||||
new byte[] {FORMAT_VERSION};
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
@@ -31,12 +28,12 @@ class GroupFactoryImpl implements GroupFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group createGroup(ClientId c, int majorVersion, byte[] descriptor) {
|
||||
byte[] majorVersionBytes = new byte[INT_32_BYTES];
|
||||
ByteUtils.writeUint32(majorVersion, majorVersionBytes, 0);
|
||||
byte[] hash = crypto.hash(LABEL, FORMAT_VERSION_BYTES,
|
||||
StringUtils.toUtf8(c.getString()), majorVersionBytes,
|
||||
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
||||
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
||||
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
||||
byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION},
|
||||
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
||||
descriptor);
|
||||
return new Group(new GroupId(hash), c, majorVersion, descriptor);
|
||||
return new Group(new GroupId(hash), c, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -43,13 +43,13 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
private final Executor dbExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final ContactId contactId;
|
||||
private final SyncRecordReader recordReader;
|
||||
private final RecordReader recordReader;
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
IncomingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, ContactId contactId,
|
||||
SyncRecordReader recordReader) {
|
||||
RecordReader recordReader) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
|
||||
@@ -16,7 +16,6 @@ import static org.briarproject.bramble.api.sync.Message.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.MessageId.BLOCK_LABEL;
|
||||
import static org.briarproject.bramble.api.sync.MessageId.ID_LABEL;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
|
||||
@@ -24,9 +23,6 @@ import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
@NotNullByDefault
|
||||
class MessageFactoryImpl implements MessageFactory {
|
||||
|
||||
private static final byte[] FORMAT_VERSION_BYTES =
|
||||
new byte[] {FORMAT_VERSION};
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
@@ -38,7 +34,14 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
||||
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
MessageId id = getMessageId(g, timestamp, body);
|
||||
byte[] versionBytes = new byte[] {FORMAT_VERSION};
|
||||
// There's only one block, so the root hash is the hash of the block
|
||||
byte[] rootHash = crypto.hash(BLOCK_LABEL, versionBytes, body);
|
||||
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||
byte[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(),
|
||||
timeBytes, rootHash);
|
||||
MessageId id = new MessageId(idHash);
|
||||
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||
@@ -46,38 +49,10 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
return new Message(id, g, timestamp, raw);
|
||||
}
|
||||
|
||||
private MessageId getMessageId(GroupId g, long timestamp, byte[] body) {
|
||||
// There's only one block, so the root hash is the hash of the block
|
||||
byte[] rootHash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, body);
|
||||
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||
byte[] idHash = crypto.hash(ID_LABEL, FORMAT_VERSION_BYTES,
|
||||
g.getBytes(), timeBytes, rootHash);
|
||||
return new MessageId(idHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(byte[] raw) {
|
||||
if (raw.length < MESSAGE_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (raw.length > MAX_MESSAGE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] groupId = new byte[UniqueId.LENGTH];
|
||||
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
|
||||
GroupId g = new GroupId(groupId);
|
||||
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
||||
byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
|
||||
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
|
||||
MessageId id = getMessageId(g, timestamp, body);
|
||||
return new Message(id, g, timestamp, raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(MessageId m, byte[] raw) {
|
||||
if (raw.length < MESSAGE_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (raw.length > MAX_MESSAGE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] groupId = new byte[UniqueId.LENGTH];
|
||||
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
|
||||
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RecordReaderFactoryImpl implements RecordReaderFactory {
|
||||
|
||||
private final MessageFactory messageFactory;
|
||||
|
||||
@Inject
|
||||
RecordReaderFactoryImpl(MessageFactory messageFactory) {
|
||||
this.messageFactory = messageFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordReader createRecordReader(InputStream in) {
|
||||
return new RecordReaderImpl(messageFactory, in);
|
||||
}
|
||||
}
|
||||
@@ -3,56 +3,82 @@ package org.briarproject.bramble.sync;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
class RecordReaderImpl implements RecordReader {
|
||||
|
||||
private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF}
|
||||
|
||||
private final MessageFactory messageFactory;
|
||||
private final RecordReader reader;
|
||||
private final InputStream in;
|
||||
private final byte[] header, payload;
|
||||
|
||||
@Nullable
|
||||
private Record nextRecord = null;
|
||||
private boolean eof = false;
|
||||
private State state = State.BUFFER_EMPTY;
|
||||
private int payloadLength = 0;
|
||||
|
||||
SyncRecordReaderImpl(MessageFactory messageFactory, RecordReader reader) {
|
||||
RecordReaderImpl(MessageFactory messageFactory, InputStream in) {
|
||||
this.messageFactory = messageFactory;
|
||||
this.reader = reader;
|
||||
this.in = in;
|
||||
header = new byte[RECORD_HEADER_LENGTH];
|
||||
payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
|
||||
}
|
||||
|
||||
private void readRecord() throws IOException {
|
||||
assert nextRecord == null;
|
||||
if (state != State.BUFFER_EMPTY) throw new IllegalStateException();
|
||||
while (true) {
|
||||
nextRecord = reader.readRecord();
|
||||
// Read the header
|
||||
int offset = 0;
|
||||
while (offset < RECORD_HEADER_LENGTH) {
|
||||
int read =
|
||||
in.read(header, offset, RECORD_HEADER_LENGTH - offset);
|
||||
if (read == -1) {
|
||||
if (offset > 0) throw new FormatException();
|
||||
state = State.EOF;
|
||||
return;
|
||||
}
|
||||
offset += read;
|
||||
}
|
||||
byte version = header[0], type = header[1];
|
||||
payloadLength = ByteUtils.readUint16(header, 2);
|
||||
// Check the protocol version
|
||||
byte version = nextRecord.getProtocolVersion();
|
||||
if (version != PROTOCOL_VERSION) throw new FormatException();
|
||||
byte type = nextRecord.getRecordType();
|
||||
// Check the payload length
|
||||
if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH)
|
||||
throw new FormatException();
|
||||
// Read the payload
|
||||
offset = 0;
|
||||
while (offset < payloadLength) {
|
||||
int read = in.read(payload, offset, payloadLength - offset);
|
||||
if (read == -1) throw new FormatException();
|
||||
offset += read;
|
||||
}
|
||||
state = State.BUFFER_FULL;
|
||||
// Return if this is a known record type, otherwise continue
|
||||
if (type == ACK || type == MESSAGE || type == OFFER ||
|
||||
type == REQUEST) {
|
||||
@@ -61,11 +87,6 @@ class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
}
|
||||
}
|
||||
|
||||
private byte getNextRecordType() {
|
||||
assert nextRecord != null;
|
||||
return nextRecord.getRecordType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there's another record available or false if we've
|
||||
* reached the end of the input stream.
|
||||
@@ -76,21 +97,14 @@ class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
*/
|
||||
@Override
|
||||
public boolean eof() throws IOException {
|
||||
if (nextRecord != null) return false;
|
||||
if (eof) return true;
|
||||
try {
|
||||
readRecord();
|
||||
return false;
|
||||
} catch (EOFException e) {
|
||||
nextRecord = null;
|
||||
eof = true;
|
||||
return true;
|
||||
}
|
||||
if (state == State.BUFFER_EMPTY) readRecord();
|
||||
if (state == State.BUFFER_EMPTY) throw new IllegalStateException();
|
||||
return state == State.EOF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAck() throws IOException {
|
||||
return !eof() && getNextRecordType() == ACK;
|
||||
return !eof() && header[1] == ACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,41 +114,45 @@ class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
}
|
||||
|
||||
private List<MessageId> readMessageIds() throws IOException {
|
||||
assert nextRecord != null;
|
||||
byte[] payload = nextRecord.getPayload();
|
||||
if (payload.length == 0) throw new FormatException();
|
||||
if (payload.length % UniqueId.LENGTH != 0) throw new FormatException();
|
||||
List<MessageId> ids = new ArrayList<>(payload.length / UniqueId.LENGTH);
|
||||
for (int off = 0; off < payload.length; off += UniqueId.LENGTH) {
|
||||
if (payloadLength == 0) throw new FormatException();
|
||||
if (payloadLength % UniqueId.LENGTH != 0) throw new FormatException();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
for (int off = 0; off < payloadLength; off += UniqueId.LENGTH) {
|
||||
byte[] id = new byte[UniqueId.LENGTH];
|
||||
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);
|
||||
ids.add(new MessageId(id));
|
||||
}
|
||||
nextRecord = null;
|
||||
state = State.BUFFER_EMPTY;
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMessage() throws IOException {
|
||||
return !eof() && getNextRecordType() == MESSAGE;
|
||||
return !eof() && header[1] == MESSAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message readMessage() throws IOException {
|
||||
if (!hasMessage()) throw new FormatException();
|
||||
assert nextRecord != null;
|
||||
byte[] payload = nextRecord.getPayload();
|
||||
if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException();
|
||||
// Validate timestamp
|
||||
if (payloadLength <= MESSAGE_HEADER_LENGTH) throw new FormatException();
|
||||
// Group ID
|
||||
byte[] id = new byte[UniqueId.LENGTH];
|
||||
System.arraycopy(payload, 0, id, 0, UniqueId.LENGTH);
|
||||
GroupId groupId = new GroupId(id);
|
||||
// Timestamp
|
||||
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
|
||||
if (timestamp < 0) throw new FormatException();
|
||||
nextRecord = null;
|
||||
return messageFactory.createMessage(payload);
|
||||
// Body
|
||||
byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH];
|
||||
System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
|
||||
payloadLength - MESSAGE_HEADER_LENGTH);
|
||||
state = State.BUFFER_EMPTY;
|
||||
return messageFactory.createMessage(groupId, timestamp, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOffer() throws IOException {
|
||||
return !eof() && getNextRecordType() == OFFER;
|
||||
return !eof() && header[1] == OFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,7 +163,7 @@ class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
|
||||
@Override
|
||||
public boolean hasRequest() throws IOException {
|
||||
return !eof() && getNextRecordType() == REQUEST;
|
||||
return !eof() && header[1] == REQUEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
class RecordWriterFactoryImpl implements RecordWriterFactory {
|
||||
|
||||
@Override
|
||||
public RecordWriter createRecordWriter(OutputStream out) {
|
||||
return new RecordWriterImpl(out);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +1,81 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.RecordTypes;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class SyncRecordWriterImpl implements SyncRecordWriter {
|
||||
class RecordWriterImpl implements RecordWriter {
|
||||
|
||||
private final RecordWriter writer;
|
||||
private final ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
||||
private final OutputStream out;
|
||||
private final byte[] header;
|
||||
private final ByteArrayOutputStream payload;
|
||||
|
||||
SyncRecordWriterImpl(RecordWriter writer) {
|
||||
this.writer = writer;
|
||||
RecordWriterImpl(OutputStream out) {
|
||||
this.out = out;
|
||||
header = new byte[RECORD_HEADER_LENGTH];
|
||||
header[0] = PROTOCOL_VERSION;
|
||||
payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
|
||||
}
|
||||
|
||||
private void writeRecord(byte recordType) throws IOException {
|
||||
writer.writeRecord(new Record(PROTOCOL_VERSION, recordType,
|
||||
payload.toByteArray()));
|
||||
header[1] = recordType;
|
||||
ByteUtils.writeUint16(payload.size(), header, 2);
|
||||
out.write(header);
|
||||
payload.writeTo(out);
|
||||
payload.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAck(Ack a) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
|
||||
writeRecord(ACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(byte[] raw) throws IOException {
|
||||
writer.writeRecord(new Record(PROTOCOL_VERSION, MESSAGE, raw));
|
||||
header[1] = RecordTypes.MESSAGE;
|
||||
ByteUtils.writeUint16(raw.length, header, 2);
|
||||
out.write(header);
|
||||
out.write(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeOffer(Offer o) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
|
||||
writeRecord(OFFER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRequest(Request r) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
|
||||
writeRecord(REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
writer.flush();
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -29,8 +29,8 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
/**
|
||||
* An outgoing {@link SyncSession} suitable for simplex transports. The session
|
||||
@@ -51,7 +51,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final EventBus eventBus;
|
||||
private final ContactId contactId;
|
||||
private final int maxLatency;
|
||||
private final SyncRecordWriter recordWriter;
|
||||
private final RecordWriter recordWriter;
|
||||
private final AtomicInteger outstandingQueries;
|
||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||
|
||||
@@ -59,7 +59,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, ContactId contactId,
|
||||
int maxLatency, SyncRecordWriter recordWriter) {
|
||||
int maxLatency, RecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
@@ -171,7 +171,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
b = db.generateBatch(txn, contactId,
|
||||
MAX_RECORD_PAYLOAD_BYTES, maxLatency);
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
|
||||
@@ -9,8 +9,8 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.sync.GroupFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -52,23 +52,22 @@ public class SyncModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
SyncRecordReaderFactory provideRecordReaderFactory(
|
||||
SyncRecordReaderFactoryImpl recordReaderFactory) {
|
||||
RecordReaderFactory provideRecordReaderFactory(
|
||||
RecordReaderFactoryImpl recordReaderFactory) {
|
||||
return recordReaderFactory;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SyncRecordWriterFactory provideRecordWriterFactory(
|
||||
SyncRecordWriterFactoryImpl recordWriterFactory) {
|
||||
return recordWriterFactory;
|
||||
RecordWriterFactory provideRecordWriterFactory() {
|
||||
return new RecordWriterFactoryImpl();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||
Clock clock, SyncRecordReaderFactory recordReaderFactory,
|
||||
SyncRecordWriterFactory recordWriterFactory) {
|
||||
Clock clock, RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
|
||||
recordReaderFactory, recordWriterFactory);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class SyncRecordReaderFactoryImpl implements SyncRecordReaderFactory {
|
||||
|
||||
private final MessageFactory messageFactory;
|
||||
private final RecordReaderFactory recordReaderFactory;
|
||||
|
||||
@Inject
|
||||
SyncRecordReaderFactoryImpl(MessageFactory messageFactory,
|
||||
RecordReaderFactory recordReaderFactory) {
|
||||
this.messageFactory = messageFactory;
|
||||
this.recordReaderFactory = recordReaderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncRecordReader createRecordReader(InputStream in) {
|
||||
RecordReader reader = recordReaderFactory.createRecordReader(in);
|
||||
return new SyncRecordReaderImpl(messageFactory, reader);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@NotNullByDefault
|
||||
class SyncRecordWriterFactoryImpl implements SyncRecordWriterFactory {
|
||||
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
@Inject
|
||||
SyncRecordWriterFactoryImpl(RecordWriterFactory recordWriterFactory) {
|
||||
this.recordWriterFactory = recordWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncRecordWriter createRecordWriter(OutputStream out) {
|
||||
RecordWriter writer = recordWriterFactory.createRecordWriter(out);
|
||||
return new SyncRecordWriterImpl(writer);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -28,14 +28,14 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
private final Executor dbExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final Clock clock;
|
||||
private final SyncRecordReaderFactory recordReaderFactory;
|
||||
private final SyncRecordWriterFactory recordWriterFactory;
|
||||
private final RecordReaderFactory recordReaderFactory;
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
@Inject
|
||||
SyncSessionFactoryImpl(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||
Clock clock, SyncRecordReaderFactory recordReaderFactory,
|
||||
SyncRecordWriterFactory recordWriterFactory) {
|
||||
Clock clock, RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
@@ -46,16 +46,14 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
|
||||
@Override
|
||||
public SyncSession createIncomingSession(ContactId c, InputStream in) {
|
||||
SyncRecordReader recordReader =
|
||||
recordReaderFactory.createRecordReader(in);
|
||||
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
|
||||
return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c,
|
||||
int maxLatency, OutputStream out) {
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
|
||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
|
||||
maxLatency, recordWriter);
|
||||
}
|
||||
@@ -63,8 +61,7 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
@Override
|
||||
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
|
||||
int maxIdleTime, OutputStream out) {
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
|
||||
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
|
||||
maxLatency, maxIdleTime, recordWriter);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
@@ -52,8 +51,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor, validationExecutor;
|
||||
private final MessageFactory messageFactory;
|
||||
private final Map<ClientMajorVersion, MessageValidator> validators;
|
||||
private final Map<ClientMajorVersion, IncomingMessageHook> hooks;
|
||||
private final Map<ClientId, MessageValidator> validators;
|
||||
private final Map<ClientId, IncomingMessageHook> hooks;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
@Inject
|
||||
@@ -82,15 +81,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerMessageValidator(ClientId c, int majorVersion,
|
||||
MessageValidator v) {
|
||||
validators.put(new ClientMajorVersion(c, majorVersion), v);
|
||||
public void registerMessageValidator(ClientId c, MessageValidator v) {
|
||||
validators.put(c, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIncomingMessageHook(ClientId c, int majorVersion,
|
||||
public void registerIncomingMessageHook(ClientId c,
|
||||
IncomingMessageHook hook) {
|
||||
hooks.put(new ClientMajorVersion(c, majorVersion), hook);
|
||||
hooks.put(c, hook);
|
||||
}
|
||||
|
||||
private void validateOutstandingMessagesAsync() {
|
||||
@@ -201,11 +199,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
Message m = messageFactory.createMessage(id, raw);
|
||||
Group g = db.getGroup(txn, m.getGroupId());
|
||||
ClientId c = g.getClientId();
|
||||
int majorVersion = g.getMajorVersion();
|
||||
Metadata meta =
|
||||
db.getMessageMetadataForValidator(txn, id);
|
||||
DeliveryResult result =
|
||||
deliverMessage(txn, m, c, majorVersion, meta);
|
||||
DeliveryResult result = deliverMessage(txn, m, c, meta);
|
||||
if (result.valid) {
|
||||
pending.addAll(getPendingDependents(txn, id));
|
||||
if (result.share) {
|
||||
@@ -241,16 +237,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
|
||||
@ValidationExecutor
|
||||
private void validateMessage(Message m, Group g) {
|
||||
ClientMajorVersion cv =
|
||||
new ClientMajorVersion(g.getClientId(), g.getMajorVersion());
|
||||
MessageValidator v = validators.get(cv);
|
||||
MessageValidator v = validators.get(g.getClientId());
|
||||
if (v == null) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv);
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("No validator for " + g.getClientId().getString());
|
||||
} else {
|
||||
try {
|
||||
MessageContext context = v.validateMessage(m, g);
|
||||
storeMessageContextAsync(m, g.getClientId(),
|
||||
g.getMajorVersion(), context);
|
||||
storeMessageContextAsync(m, g.getClientId(), context);
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
@@ -262,13 +256,12 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
|
||||
private void storeMessageContextAsync(Message m, ClientId c,
|
||||
int majorVersion, MessageContext result) {
|
||||
dbExecutor.execute(() ->
|
||||
storeMessageContext(m, c, majorVersion, result));
|
||||
MessageContext result) {
|
||||
dbExecutor.execute(() -> storeMessageContext(m, c, result));
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void storeMessageContext(Message m, ClientId c, int majorVersion,
|
||||
private void storeMessageContext(Message m, ClientId c,
|
||||
MessageContext context) {
|
||||
try {
|
||||
MessageId id = m.getId();
|
||||
@@ -299,8 +292,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
Metadata meta = context.getMetadata();
|
||||
db.mergeMessageMetadata(txn, id, meta);
|
||||
if (allDelivered) {
|
||||
DeliveryResult result =
|
||||
deliverMessage(txn, m, c, majorVersion, meta);
|
||||
DeliveryResult result = deliverMessage(txn, m, c, meta);
|
||||
if (result.valid) {
|
||||
pending = getPendingDependents(txn, id);
|
||||
if (result.share) {
|
||||
@@ -332,11 +324,10 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
|
||||
@DatabaseExecutor
|
||||
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
||||
ClientId c, int majorVersion, Metadata meta) throws DbException {
|
||||
ClientId c, Metadata meta) throws DbException {
|
||||
// Deliver the message to the client if it's registered a hook
|
||||
boolean shareMsg = false;
|
||||
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
||||
IncomingMessageHook hook = hooks.get(cv);
|
||||
IncomingMessageHook hook = hooks.get(c);
|
||||
if (hook != null) {
|
||||
try {
|
||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.briarproject.bramble.versioning;
|
||||
|
||||
interface ClientVersioningConstants {
|
||||
|
||||
// Metadata keys
|
||||
String MSG_KEY_UPDATE_VERSION = "version";
|
||||
String MSG_KEY_LOCAL = "local";
|
||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
||||
}
|
||||
|
||||
@@ -1,622 +0,0 @@
|
||||
package org.briarproject.bramble.versioning;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
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.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
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.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
|
||||
@NotNullByDefault
|
||||
class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
Service, ContactHook, IncomingMessageHook {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
|
||||
private final List<ClientVersion> clients = new CopyOnWriteArrayList<>();
|
||||
private final Map<ClientMajorVersion, ClientVersioningHook> hooks =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clock = clock;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerClient(ClientId clientId, int majorVersion,
|
||||
int minorVersion, ClientVersioningHook hook) {
|
||||
ClientMajorVersion cv = new ClientMajorVersion(clientId, majorVersion);
|
||||
clients.add(new ClientVersion(cv, minorVersion));
|
||||
hooks.put(cv, hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||
ClientId clientId, int majorVersion) throws DbException {
|
||||
try {
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
Group g = getContactGroup(contact);
|
||||
// Contact may be in the process of being added or removed, so
|
||||
// contact group may not exist
|
||||
if (!db.containsGroup(txn, g.getId())) return INVISIBLE;
|
||||
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
||||
if (latest.local == null) throw new DbException();
|
||||
if (latest.remote == null) return INVISIBLE;
|
||||
Update localUpdate = loadUpdate(txn, latest.local.messageId);
|
||||
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
||||
Map<ClientMajorVersion, Visibility> visibilities =
|
||||
getVisibilities(localUpdate.states, remoteUpdate.states);
|
||||
ClientMajorVersion cv =
|
||||
new ClientMajorVersion(clientId, majorVersion);
|
||||
Visibility v = visibilities.get(cv);
|
||||
return v == null ? INVISIBLE : v;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||
Collections.sort(versions);
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
if (updateClientVersions(txn, versions)) {
|
||||
for (Contact c : db.getContacts(txn))
|
||||
clientVersionsUpdated(txn, c, versions);
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group and share it with the contact
|
||||
Group g = getContactGroup(c);
|
||||
db.addGroup(txn, g);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
||||
try {
|
||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
// Create and store the first local update
|
||||
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||
Collections.sort(versions);
|
||||
storeFirstUpdate(txn, g.getId(), versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
try {
|
||||
// Parse the new remote update
|
||||
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
||||
List<ClientState> newRemoteStates = newRemoteUpdate.states;
|
||||
long newRemoteUpdateVersion = newRemoteUpdate.updateVersion;
|
||||
// Find the latest local and remote updates, if any
|
||||
LatestUpdates latest = findLatestUpdates(txn, m.getGroupId());
|
||||
// If this update is obsolete, delete it and return
|
||||
if (latest.remote != null
|
||||
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
return false;
|
||||
}
|
||||
// Load and parse the latest local update
|
||||
if (latest.local == null) throw new DbException();
|
||||
Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId);
|
||||
List<ClientState> oldLocalStates = oldLocalUpdate.states;
|
||||
long oldLocalUpdateVersion = oldLocalUpdate.updateVersion;
|
||||
// Load and parse the previous remote update, if any
|
||||
List<ClientState> oldRemoteStates;
|
||||
if (latest.remote == null) {
|
||||
oldRemoteStates = emptyList();
|
||||
} else {
|
||||
oldRemoteStates =
|
||||
loadUpdate(txn, latest.remote.messageId).states;
|
||||
// Delete the previous remote update
|
||||
db.deleteMessage(txn, latest.remote.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.remote.messageId);
|
||||
}
|
||||
// Update the local states from the remote states if necessary
|
||||
List<ClientState> newLocalStates = updateStatesFromRemoteStates(
|
||||
oldLocalStates, newRemoteStates);
|
||||
if (!oldLocalStates.equals(newLocalStates)) {
|
||||
// Delete the latest local update
|
||||
db.deleteMessage(txn, latest.local.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.local.messageId);
|
||||
// Store a new local update
|
||||
storeUpdate(txn, m.getGroupId(), newLocalStates,
|
||||
oldLocalUpdateVersion + 1);
|
||||
}
|
||||
// Calculate the old and new client visibilities
|
||||
Map<ClientMajorVersion, Visibility> before =
|
||||
getVisibilities(oldLocalStates, oldRemoteStates);
|
||||
Map<ClientMajorVersion, Visibility> after =
|
||||
getVisibilities(newLocalStates, newRemoteStates);
|
||||
// Call hooks for any visibilities that have changed
|
||||
if (!before.equals(after)) {
|
||||
Contact c = getContact(txn, m.getGroupId());
|
||||
callVisibilityHooks(txn, c, before, after);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void storeClientVersions(Transaction txn,
|
||||
List<ClientVersion> versions) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
BdfList body = encodeClientVersions(versions);
|
||||
try {
|
||||
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
||||
body);
|
||||
db.addLocalMessage(txn, m, new Metadata(), false);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private BdfList encodeClientVersions(List<ClientVersion> versions) {
|
||||
BdfList encoded = new BdfList();
|
||||
for (ClientVersion cv : versions) encoded.add(encodeClientVersion(cv));
|
||||
return encoded;
|
||||
}
|
||||
|
||||
private BdfList encodeClientVersion(ClientVersion cv) {
|
||||
return BdfList.of(cv.majorVersion.getClientId().getString(),
|
||||
cv.majorVersion.getMajorVersion(), cv.minorVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the local client versions and returns true if an update needs to
|
||||
* be sent to contacts.
|
||||
*/
|
||||
private boolean updateClientVersions(Transaction txn,
|
||||
List<ClientVersion> newVersions) throws DbException {
|
||||
Collection<MessageId> ids = db.getMessageIds(txn, localGroup.getId());
|
||||
if (ids.isEmpty()) {
|
||||
storeClientVersions(txn, newVersions);
|
||||
return true;
|
||||
}
|
||||
if (ids.size() != 1) throw new DbException();
|
||||
MessageId m = ids.iterator().next();
|
||||
List<ClientVersion> oldVersions = loadClientVersions(txn, m);
|
||||
if (oldVersions.equals(newVersions)) return false;
|
||||
db.removeMessage(txn, m);
|
||||
storeClientVersions(txn, newVersions);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<ClientVersion> loadClientVersions(Transaction txn,
|
||||
MessageId m) throws DbException {
|
||||
try {
|
||||
BdfList body = clientHelper.getMessageAsList(txn, m);
|
||||
if (body == null) throw new DbException();
|
||||
return parseClientVersions(body);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ClientVersion> parseClientVersions(BdfList body)
|
||||
throws FormatException {
|
||||
int size = body.size();
|
||||
List<ClientVersion> parsed = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
BdfList cv = body.getList(i);
|
||||
ClientId clientId = new ClientId(cv.getString(0));
|
||||
int majorVersion = cv.getLong(1).intValue();
|
||||
int minorVersion = cv.getLong(2).intValue();
|
||||
parsed.add(new ClientVersion(clientId, majorVersion,
|
||||
minorVersion));
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private void clientVersionsUpdated(Transaction txn, Contact c,
|
||||
List<ClientVersion> versions) throws DbException {
|
||||
try {
|
||||
// Find the latest local and remote updates
|
||||
Group g = getContactGroup(c);
|
||||
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
||||
// Load and parse the latest local update
|
||||
if (latest.local == null) throw new DbException();
|
||||
Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId);
|
||||
List<ClientState> oldLocalStates = oldLocalUpdate.states;
|
||||
long oldLocalUpdateVersion = oldLocalUpdate.updateVersion;
|
||||
// Load and parse the latest remote update, if any
|
||||
List<ClientState> remoteStates;
|
||||
if (latest.remote == null) remoteStates = emptyList();
|
||||
else remoteStates = loadUpdate(txn, latest.remote.messageId).states;
|
||||
// Update the local states if necessary
|
||||
List<ClientState> newLocalStates =
|
||||
updateStatesFromLocalVersions(oldLocalStates, versions);
|
||||
newLocalStates = updateStatesFromRemoteStates(newLocalStates,
|
||||
remoteStates);
|
||||
if (!oldLocalStates.equals(newLocalStates)) {
|
||||
// Delete the latest local update
|
||||
db.deleteMessage(txn, latest.local.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.local.messageId);
|
||||
// Store a new local update
|
||||
storeUpdate(txn, g.getId(), newLocalStates,
|
||||
oldLocalUpdateVersion + 1);
|
||||
}
|
||||
// Calculate the old and new client visibilities
|
||||
Map<ClientMajorVersion, Visibility> before =
|
||||
getVisibilities(oldLocalStates, remoteStates);
|
||||
Map<ClientMajorVersion, Visibility> after =
|
||||
getVisibilities(newLocalStates, remoteStates);
|
||||
// Call hooks for any visibilities that have changed
|
||||
callVisibilityHooks(txn, c, before, after);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, c);
|
||||
}
|
||||
|
||||
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
|
||||
throws DbException, FormatException {
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
LatestUpdate local = null, remote = null;
|
||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||
BdfDictionary meta = e.getValue();
|
||||
long updateVersion = meta.getLong(MSG_KEY_UPDATE_VERSION);
|
||||
if (meta.getBoolean(MSG_KEY_LOCAL))
|
||||
local = new LatestUpdate(e.getKey(), updateVersion);
|
||||
else remote = new LatestUpdate(e.getKey(), updateVersion);
|
||||
}
|
||||
return new LatestUpdates(local, remote);
|
||||
}
|
||||
|
||||
private Update loadUpdate(Transaction txn, MessageId m) throws DbException {
|
||||
try {
|
||||
BdfList body = clientHelper.getMessageAsList(txn, m);
|
||||
if (body == null) throw new DbException();
|
||||
return parseUpdate(body);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Update parseUpdate(BdfList body) throws FormatException {
|
||||
List<ClientState> states = parseClientStates(body);
|
||||
long updateVersion = parseUpdateVersion(body);
|
||||
return new Update(states, updateVersion);
|
||||
}
|
||||
|
||||
private List<ClientState> parseClientStates(BdfList body)
|
||||
throws FormatException {
|
||||
// Client states, update version
|
||||
BdfList states = body.getList(0);
|
||||
int size = states.size();
|
||||
List<ClientState> parsed = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++)
|
||||
parsed.add(parseClientState(states.getList(i)));
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private ClientState parseClientState(BdfList clientState)
|
||||
throws FormatException {
|
||||
// Client ID, major version, minor version, active
|
||||
ClientId clientId = new ClientId(clientState.getString(0));
|
||||
int majorVersion = clientState.getLong(1).intValue();
|
||||
int minorVersion = clientState.getLong(2).intValue();
|
||||
boolean active = clientState.getBoolean(3);
|
||||
return new ClientState(clientId, majorVersion, minorVersion, active);
|
||||
}
|
||||
|
||||
private long parseUpdateVersion(BdfList body) throws FormatException {
|
||||
// Client states, update version
|
||||
return body.getLong(1);
|
||||
}
|
||||
|
||||
private List<ClientState> updateStatesFromLocalVersions(
|
||||
List<ClientState> oldStates, List<ClientVersion> newVersions) {
|
||||
Map<ClientMajorVersion, ClientState> oldMap = new HashMap<>();
|
||||
for (ClientState cs : oldStates) oldMap.put(cs.majorVersion, cs);
|
||||
List<ClientState> newStates = new ArrayList<>(newVersions.size());
|
||||
for (ClientVersion newVersion : newVersions) {
|
||||
ClientState oldState = oldMap.get(newVersion.majorVersion);
|
||||
boolean active = oldState != null && oldState.active;
|
||||
newStates.add(new ClientState(newVersion.majorVersion,
|
||||
newVersion.minorVersion, active));
|
||||
}
|
||||
return newStates;
|
||||
}
|
||||
|
||||
private void storeUpdate(Transaction txn, GroupId g,
|
||||
List<ClientState> states, long updateVersion) throws DbException {
|
||||
try {
|
||||
BdfList body = encodeUpdate(states, updateVersion);
|
||||
long now = clock.currentTimeMillis();
|
||||
Message m = clientHelper.createMessage(g, now, body);
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
||||
meta.put(MSG_KEY_LOCAL, true);
|
||||
clientHelper.addLocalMessage(txn, m, meta, true);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private BdfList encodeUpdate(List<ClientState> states, long updateVersion) {
|
||||
BdfList encoded = new BdfList();
|
||||
for (ClientState cs : states) encoded.add(encodeClientState(cs));
|
||||
return BdfList.of(encoded, updateVersion);
|
||||
}
|
||||
|
||||
private BdfList encodeClientState(ClientState cs) {
|
||||
return BdfList.of(cs.majorVersion.getClientId().getString(),
|
||||
cs.majorVersion.getMajorVersion(), cs.minorVersion, cs.active);
|
||||
}
|
||||
|
||||
private Map<ClientMajorVersion, Visibility> getVisibilities(
|
||||
List<ClientState> localStates, List<ClientState> remoteStates) {
|
||||
Map<ClientMajorVersion, ClientState> remoteMap = new HashMap<>();
|
||||
for (ClientState cs : remoteStates) remoteMap.put(cs.majorVersion, cs);
|
||||
Map<ClientMajorVersion, Visibility> visibilities = new HashMap<>();
|
||||
for (ClientState local : localStates) {
|
||||
ClientState remote = remoteMap.get(local.majorVersion);
|
||||
if (remote == null) visibilities.put(local.majorVersion, INVISIBLE);
|
||||
else if (remote.active)
|
||||
visibilities.put(local.majorVersion, SHARED);
|
||||
else visibilities.put(local.majorVersion, VISIBLE);
|
||||
}
|
||||
return visibilities;
|
||||
}
|
||||
|
||||
private void callVisibilityHooks(Transaction txn, Contact c,
|
||||
Map<ClientMajorVersion, Visibility> before,
|
||||
Map<ClientMajorVersion, Visibility> after) throws DbException {
|
||||
Set<ClientMajorVersion> keys = new TreeSet<>();
|
||||
keys.addAll(before.keySet());
|
||||
keys.addAll(after.keySet());
|
||||
for (ClientMajorVersion cv : keys) {
|
||||
Visibility vBefore = before.get(cv), vAfter = after.get(cv);
|
||||
if (vAfter == null) {
|
||||
callVisibilityHook(txn, cv, c, INVISIBLE);
|
||||
} else if (vBefore == null || !vBefore.equals(vAfter)) {
|
||||
callVisibilityHook(txn, cv, c, vAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void callVisibilityHook(Transaction txn, ClientMajorVersion cv,
|
||||
Contact c, Visibility v) throws DbException {
|
||||
ClientVersioningHook hook = hooks.get(cv);
|
||||
if (hook != null) hook.onClientVisibilityChanging(txn, c, v);
|
||||
}
|
||||
|
||||
private void storeFirstUpdate(Transaction txn, GroupId g,
|
||||
List<ClientVersion> versions) throws DbException {
|
||||
List<ClientState> states = new ArrayList<>(versions.size());
|
||||
for (ClientVersion cv : versions) {
|
||||
states.add(new ClientState(cv.majorVersion, cv.minorVersion,
|
||||
false));
|
||||
}
|
||||
storeUpdate(txn, g, states, 1);
|
||||
}
|
||||
|
||||
private Contact getContact(Transaction txn, GroupId g) throws DbException {
|
||||
try {
|
||||
BdfDictionary meta =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||
int id = meta.getLong(GROUP_KEY_CONTACT_ID).intValue();
|
||||
return db.getContact(txn, new ContactId(id));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ClientState> updateStatesFromRemoteStates(
|
||||
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
||||
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
||||
for (ClientState cs : remoteStates) remoteSet.add(cs.majorVersion);
|
||||
List<ClientState> newLocalStates =
|
||||
new ArrayList<>(oldLocalStates.size());
|
||||
for (ClientState oldState : oldLocalStates) {
|
||||
boolean active = remoteSet.contains(oldState.majorVersion);
|
||||
newLocalStates.add(new ClientState(oldState.majorVersion,
|
||||
oldState.minorVersion, active));
|
||||
}
|
||||
return newLocalStates;
|
||||
}
|
||||
|
||||
private static class Update {
|
||||
|
||||
private final List<ClientState> states;
|
||||
private final long updateVersion;
|
||||
|
||||
private Update(List<ClientState> states, long updateVersion) {
|
||||
this.states = states;
|
||||
this.updateVersion = updateVersion;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LatestUpdate {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final long updateVersion;
|
||||
|
||||
private LatestUpdate(MessageId messageId, long updateVersion) {
|
||||
this.messageId = messageId;
|
||||
this.updateVersion = updateVersion;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LatestUpdates {
|
||||
|
||||
@Nullable
|
||||
private final LatestUpdate local, remote;
|
||||
|
||||
private LatestUpdates(@Nullable LatestUpdate local,
|
||||
@Nullable LatestUpdate remote) {
|
||||
this.local = local;
|
||||
this.remote = remote;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClientVersion implements Comparable<ClientVersion> {
|
||||
|
||||
private final ClientMajorVersion majorVersion;
|
||||
private final int minorVersion;
|
||||
|
||||
private ClientVersion(ClientMajorVersion majorVersion,
|
||||
int minorVersion) {
|
||||
this.majorVersion = majorVersion;
|
||||
this.minorVersion = minorVersion;
|
||||
}
|
||||
|
||||
private ClientVersion(ClientId clientId, int majorVersion,
|
||||
int minorVersion) {
|
||||
this(new ClientMajorVersion(clientId, majorVersion), minorVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof ClientVersion) {
|
||||
ClientVersion cv = (ClientVersion) o;
|
||||
return majorVersion.equals(cv.majorVersion)
|
||||
&& minorVersion == cv.minorVersion;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return majorVersion.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ClientVersion cv) {
|
||||
int compare = majorVersion.compareTo(cv.majorVersion);
|
||||
if (compare != 0) return compare;
|
||||
return minorVersion - cv.minorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClientState {
|
||||
|
||||
private final ClientMajorVersion majorVersion;
|
||||
private final int minorVersion;
|
||||
private final boolean active;
|
||||
|
||||
private ClientState(ClientMajorVersion majorVersion, int minorVersion,
|
||||
boolean active) {
|
||||
this.majorVersion = majorVersion;
|
||||
this.minorVersion = minorVersion;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
private ClientState(ClientId clientId, int majorVersion,
|
||||
int minorVersion, boolean active) {
|
||||
this(new ClientMajorVersion(clientId, majorVersion), minorVersion,
|
||||
active);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof ClientState) {
|
||||
ClientState cs = (ClientState) o;
|
||||
return majorVersion.equals(cs.majorVersion)
|
||||
&& minorVersion == cs.minorVersion
|
||||
&& active == cs.active;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return majorVersion.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.briarproject.bramble.versioning;
|
||||
|
||||
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.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class ClientVersioningValidator extends BdfMessageValidator {
|
||||
|
||||
ClientVersioningValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Client states, update version
|
||||
checkSize(body, 2);
|
||||
// Client states
|
||||
BdfList states = body.getList(0);
|
||||
int size = states.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
BdfList clientState = states.getList(i);
|
||||
// Client ID, major version, minor version, active
|
||||
checkSize(clientState, 4);
|
||||
String clientId = clientState.getString(0);
|
||||
checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH);
|
||||
int majorVersion = clientState.getLong(1).intValue();
|
||||
if (majorVersion < 0) throw new FormatException();
|
||||
int minorVersion = clientState.getLong(2).intValue();
|
||||
if (minorVersion < 0) throw new FormatException();
|
||||
clientState.getBoolean(3);
|
||||
}
|
||||
// Update version
|
||||
long updateVersion = body.getLong(1);
|
||||
if (updateVersion < 0) throw new FormatException();
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
||||
meta.put(MSG_KEY_LOCAL, false);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.briarproject.bramble.versioning;
|
||||
|
||||
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.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
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.versioning.ClientVersioningManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
||||
|
||||
@Module
|
||||
public class VersioningModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
ClientVersioningManager clientVersioningManager;
|
||||
@Inject
|
||||
ClientVersioningValidator clientVersioningValidator;
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ClientVersioningManager provideClientVersioningManager(
|
||||
ClientVersioningManagerImpl clientVersioningManager,
|
||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||
ValidationManager validationManager) {
|
||||
lifecycleManager.registerClient(clientVersioningManager);
|
||||
lifecycleManager.registerService(clientVersioningManager);
|
||||
contactManager.registerContactHook(clientVersioningManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||
clientVersioningManager);
|
||||
return clientVersioningManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ClientVersioningValidator provideClientVersioningValidator(
|
||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||
Clock clock, ValidationManager validationManager) {
|
||||
ClientVersioningValidator validator = new ClientVersioningValidator(
|
||||
clientHelper, metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import org.briarproject.bramble.api.sync.Group;
|
||||
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.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
|
||||
@@ -78,7 +77,6 @@ import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@@ -91,7 +89,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Object txn = new Object();
|
||||
private final ClientId clientId;
|
||||
private final int majorVersion;
|
||||
private final GroupId groupId;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
@@ -109,8 +106,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
public DatabaseComponentImplTest() {
|
||||
clientId = getClientId();
|
||||
majorVersion = 123;
|
||||
group = getGroup(clientId, majorVersion);
|
||||
group = getGroup(clientId);
|
||||
groupId = group.getId();
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
@@ -179,7 +175,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
// getGroups()
|
||||
oneOf(database).getGroups(txn, clientId, majorVersion);
|
||||
oneOf(database).getGroups(txn, clientId);
|
||||
will(returnValue(singletonList(group)));
|
||||
// removeGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
@@ -219,7 +215,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.addGroup(transaction, group); // First time - listeners called
|
||||
db.addGroup(transaction, group); // Second time - not called
|
||||
assertEquals(singletonList(group),
|
||||
db.getGroups(transaction, clientId, majorVersion));
|
||||
db.getGroups(transaction, clientId));
|
||||
db.removeGroup(transaction, group);
|
||||
db.removeContact(transaction, contactId);
|
||||
db.removeLocalAuthor(transaction, localAuthor.getId());
|
||||
@@ -1319,9 +1315,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
KeySet ks = new KeySet(keySetId, contactId, transportKeys);
|
||||
Collection<KeySet> keys = singletonList(ks);
|
||||
|
||||
Collection<KeySet> keys =
|
||||
singletonList(new KeySet(keySetId, contactId, transportKeys));
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1329,7 +1324,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// updateTransportKeys()
|
||||
oneOf(database).containsTransport(txn, transportId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).updateTransportKeys(txn, ks);
|
||||
oneOf(database).updateTransportKeys(txn, keys);
|
||||
// getTransportKeys()
|
||||
oneOf(database).containsTransport(txn, transportId);
|
||||
will(returnValue(true));
|
||||
@@ -1351,114 +1346,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMessageStatusByGroupId() throws Exception {
|
||||
MessageStatus status =
|
||||
new MessageStatus(messageId, contactId, true, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
// getMessageStatus()
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(VISIBLE));
|
||||
oneOf(database).getMessageStatus(txn, contactId, groupId);
|
||||
will(returnValue(singletonList(status)));
|
||||
// getMessageStatus() again
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(INVISIBLE));
|
||||
oneOf(database).getMessageIds(txn, groupId);
|
||||
will(returnValue(singletonList(messageId)));
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
Transaction transaction = db.startTransaction(true);
|
||||
try {
|
||||
// With visible group - return stored status
|
||||
Collection<MessageStatus> statuses =
|
||||
db.getMessageStatus(transaction, contactId, groupId);
|
||||
assertEquals(1, statuses.size());
|
||||
MessageStatus s = statuses.iterator().next();
|
||||
assertEquals(messageId, s.getMessageId());
|
||||
assertEquals(contactId, s.getContactId());
|
||||
assertTrue(s.isSent());
|
||||
assertTrue(s.isSeen());
|
||||
// With invisible group - return default status
|
||||
statuses = db.getMessageStatus(transaction, contactId, groupId);
|
||||
assertEquals(1, statuses.size());
|
||||
s = statuses.iterator().next();
|
||||
assertEquals(messageId, s.getMessageId());
|
||||
assertEquals(contactId, s.getContactId());
|
||||
assertFalse(s.isSent());
|
||||
assertFalse(s.isSeen());
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMessageStatusByMessageId() throws Exception {
|
||||
MessageStatus status =
|
||||
new MessageStatus(messageId, contactId, true, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
// getMessageStatus()
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getMessageStatus(txn, contactId, messageId);
|
||||
will(returnValue(status));
|
||||
// getMessageStatus() again
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getMessageStatus(txn, contactId, messageId);
|
||||
will(returnValue(null));
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
Transaction transaction = db.startTransaction(true);
|
||||
try {
|
||||
// With visible group - return stored status
|
||||
MessageStatus s =
|
||||
db.getMessageStatus(transaction, contactId, messageId);
|
||||
assertEquals(messageId, s.getMessageId());
|
||||
assertEquals(contactId, s.getContactId());
|
||||
assertTrue(s.isSent());
|
||||
assertTrue(s.isSeen());
|
||||
// With invisible group - return default status
|
||||
s = db.getMessageStatus(transaction, contactId, messageId);
|
||||
assertEquals(messageId, s.getMessageId());
|
||||
assertEquals(contactId, s.getContactId());
|
||||
assertFalse(s.isSent());
|
||||
assertFalse(s.isSeen());
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys() {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
|
||||
@@ -267,7 +267,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
String name = "getGroups(T, ClientId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
db.getGroups(txn, pickRandom(clientIds), 123);
|
||||
db.getGroups(txn, pickRandom(clientIds));
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
@@ -550,7 +550,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
contacts.add(db.getContact(txn, c));
|
||||
contactGroups.put(c, new ArrayList<>());
|
||||
for (int j = 0; j < GROUPS_PER_CONTACT; j++) {
|
||||
Group g = getGroup(clientIds.get(j % CLIENTS), 123);
|
||||
Group g = getGroup(clientIds.get(j % CLIENTS));
|
||||
groups.add(g);
|
||||
messageMeta.put(g.getId(), new ArrayList<>());
|
||||
contactGroups.get(c).add(g);
|
||||
@@ -584,7 +584,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < LOCAL_GROUPS; i++) {
|
||||
Group g = getGroup(clientIds.get(i % CLIENTS), 123);
|
||||
Group g = getGroup(clientIds.get(i % CLIENTS));
|
||||
groups.add(g);
|
||||
messageMeta.put(g.getId(), new ArrayList<>());
|
||||
groupMessages.put(g.getId(), new ArrayList<>());
|
||||
|
||||
@@ -82,7 +82,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final GroupId groupId;
|
||||
private final ClientId clientId;
|
||||
private final int majorVersion;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
private final LocalAuthor localAuthor;
|
||||
@@ -97,8 +96,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
JdbcDatabaseTest() throws Exception {
|
||||
clientId = getClientId();
|
||||
majorVersion = 123;
|
||||
group = getGroup(clientId, majorVersion);
|
||||
group = getGroup(clientId);
|
||||
groupId = group.getId();
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
@@ -669,9 +667,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
long rotationPeriod = 123, rotationPeriod1 = 234;
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod);
|
||||
TransportKeys keys1 = createTransportKeys(rotationPeriod1);
|
||||
TransportKeys keys = createTransportKeys();
|
||||
TransportKeys keys1 = createTransportKeys();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -700,25 +697,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the transport keys
|
||||
TransportKeys rotated = createTransportKeys(rotationPeriod + 1);
|
||||
TransportKeys rotated1 = createTransportKeys(rotationPeriod1 + 1);
|
||||
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
|
||||
db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1));
|
||||
|
||||
// Retrieve the transport keys again
|
||||
allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertKeysEquals(rotated, ks.getTransportKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(rotated1, ks.getTransportKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Removing the contact should remove the transport keys
|
||||
db.removeContact(txn, contactId);
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
@@ -729,9 +707,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnboundTransportKeys() throws Exception {
|
||||
long rotationPeriod = 123, rotationPeriod1 = 234;
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod);
|
||||
TransportKeys keys1 = createTransportKeys(rotationPeriod1);
|
||||
TransportKeys keys = createTransportKeys();
|
||||
TransportKeys keys1 = createTransportKeys();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -777,26 +754,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the transport keys
|
||||
TransportKeys rotated = createTransportKeys(rotationPeriod + 1);
|
||||
TransportKeys rotated1 = createTransportKeys(rotationPeriod1 + 1);
|
||||
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
|
||||
db.updateTransportKeys(txn, new KeySet(keySetId1, null, rotated1));
|
||||
|
||||
// Retrieve the transport keys again
|
||||
allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
assertKeysEquals(rotated, ks.getTransportKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertNull(ks.getContactId());
|
||||
assertKeysEquals(rotated1, ks.getTransportKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the unbound transport keys
|
||||
db.removeTransportKeys(txn, transportId, keySetId1);
|
||||
|
||||
@@ -806,7 +763,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
KeySet ks = allKeys.iterator().next();
|
||||
assertEquals(keySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
assertKeysEquals(rotated, ks.getTransportKeys());
|
||||
assertKeysEquals(keys, ks.getTransportKeys());
|
||||
|
||||
// Removing the transport should remove the remaining transport keys
|
||||
db.removeTransport(txn, transportId);
|
||||
@@ -852,8 +809,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testIncrementStreamCounter() throws Exception {
|
||||
long rotationPeriod = 123;
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod);
|
||||
TransportKeys keys = createTransportKeys();
|
||||
long rotationPeriod = keys.getCurrentOutgoingKeys().getRotationPeriod();
|
||||
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
@@ -864,7 +821,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||
db.updateTransportKeys(txn,
|
||||
singletonList(new KeySet(keySetId, contactId, keys)));
|
||||
|
||||
// Increment the stream counter twice and retrieve the transport keys
|
||||
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||
@@ -880,21 +838,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(rotationPeriod, outCurr.getRotationPeriod());
|
||||
assertEquals(streamCounter + 2, outCurr.getStreamCounter());
|
||||
|
||||
// The rest of the keys should be unaffected
|
||||
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
||||
k.getPreviousIncomingKeys());
|
||||
assertKeysEquals(keys.getCurrentIncomingKeys(),
|
||||
k.getCurrentIncomingKeys());
|
||||
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetReorderingWindow() throws Exception {
|
||||
long rotationPeriod = 123;
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod);
|
||||
TransportKeys keys = createTransportKeys();
|
||||
long rotationPeriod = keys.getCurrentIncomingKeys().getRotationPeriod();
|
||||
long base = keys.getCurrentIncomingKeys().getWindowBase();
|
||||
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
|
||||
|
||||
@@ -906,7 +857,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||
db.updateTransportKeys(txn,
|
||||
singletonList(new KeySet(keySetId, contactId, keys)));
|
||||
|
||||
// Update the reordering window and retrieve the transport keys
|
||||
new Random().nextBytes(bitmap);
|
||||
@@ -924,13 +876,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(base + 1, inCurr.getWindowBase());
|
||||
assertArrayEquals(bitmap, inCurr.getWindowBitmap());
|
||||
|
||||
// The rest of the keys should be unaffected
|
||||
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
||||
k.getPreviousIncomingKeys());
|
||||
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys());
|
||||
assertKeysEquals(keys.getCurrentOutgoingKeys(),
|
||||
k.getCurrentOutgoingKeys());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
@@ -1028,8 +973,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Attach some metadata to the group
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[] {'b', 'a', 'm'});
|
||||
metadata.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[]{'b', 'a', 'm'});
|
||||
db.mergeGroupMetadata(txn, groupId, metadata);
|
||||
|
||||
// Retrieve the metadata for the group
|
||||
@@ -1067,8 +1012,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[] {'b', 'a', 'm'});
|
||||
metadata.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[]{'b', 'a', 'm'});
|
||||
db.mergeMessageMetadata(txn, messageId, metadata);
|
||||
|
||||
// Retrieve the metadata for the message
|
||||
@@ -1138,8 +1083,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[] {'b', 'a', 'm'});
|
||||
metadata.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[]{'b', 'a', 'm'});
|
||||
db.mergeMessageMetadata(txn, messageId, metadata);
|
||||
|
||||
// Retrieve the metadata for the message
|
||||
@@ -1200,11 +1145,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[] {'b', 'a', 'm'});
|
||||
metadata.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[]{'b', 'a', 'm'});
|
||||
db.mergeMessageMetadata(txn, messageId, metadata);
|
||||
Metadata metadata1 = new Metadata();
|
||||
metadata1.put("foo", new byte[] {'q', 'u', 'x'});
|
||||
metadata1.put("foo", new byte[]{'q', 'u', 'x'});
|
||||
db.mergeMessageMetadata(txn, messageId1, metadata1);
|
||||
|
||||
// Retrieve all the metadata for the group
|
||||
@@ -1304,11 +1249,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[] {'b', 'a', 'm'});
|
||||
metadata.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
metadata.put("baz", new byte[]{'b', 'a', 'm'});
|
||||
db.mergeMessageMetadata(txn, messageId, metadata);
|
||||
Metadata metadata1 = new Metadata();
|
||||
metadata1.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
metadata1.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
db.mergeMessageMetadata(txn, messageId1, metadata1);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
@@ -1319,7 +1264,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
} else {
|
||||
// Query for foo
|
||||
query = new Metadata();
|
||||
query.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
query.put("foo", new byte[]{'b', 'a', 'r'});
|
||||
}
|
||||
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
@@ -1462,7 +1407,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
|
||||
// Add a second group
|
||||
Group group1 = getGroup(clientId, 123);
|
||||
Group group1 = getGroup(clientId);
|
||||
GroupId groupId1 = group1.getId();
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
@@ -1596,7 +1541,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The message should not be sent or seen
|
||||
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
|
||||
assertNotNull(status);
|
||||
assertEquals(messageId, status.getMessageId());
|
||||
assertEquals(contactId, status.getContactId());
|
||||
assertFalse(status.isSent());
|
||||
@@ -1617,7 +1561,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The message should be sent but not seen
|
||||
status = db.getMessageStatus(txn, contactId, messageId);
|
||||
assertNotNull(status);
|
||||
assertEquals(messageId, status.getMessageId());
|
||||
assertEquals(contactId, status.getContactId());
|
||||
assertTrue(status.isSent());
|
||||
@@ -1637,7 +1580,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The message should be sent and seen
|
||||
status = db.getMessageStatus(txn, contactId, messageId);
|
||||
assertNotNull(status);
|
||||
assertEquals(messageId, status.getMessageId());
|
||||
assertEquals(contactId, status.getContactId());
|
||||
assertTrue(status.isSent());
|
||||
@@ -1652,36 +1594,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(status.isSent());
|
||||
assertTrue(status.isSeen());
|
||||
|
||||
// Make the group invisible to the contact
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
|
||||
// Null should be returned when querying by message
|
||||
assertNull(db.getMessageStatus(txn, contactId, messageId));
|
||||
|
||||
// No statuses should be returned when querying by group
|
||||
statuses = db.getMessageStatus(txn, contactId, groupId);
|
||||
assertEquals(0, statuses.size());
|
||||
|
||||
// Make the group visible to the contact again
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
|
||||
// The default status should be returned when querying by message
|
||||
status = db.getMessageStatus(txn, contactId, messageId);
|
||||
assertNotNull(status);
|
||||
assertEquals(messageId, status.getMessageId());
|
||||
assertEquals(contactId, status.getContactId());
|
||||
assertFalse(status.isSent());
|
||||
assertFalse(status.isSeen());
|
||||
|
||||
// The default status should be returned when querying by group
|
||||
statuses = db.getMessageStatus(txn, contactId, groupId);
|
||||
assertEquals(1, statuses.size());
|
||||
status = statuses.iterator().next();
|
||||
assertEquals(messageId, status.getMessageId());
|
||||
assertEquals(contactId, status.getContactId());
|
||||
assertFalse(status.isSent());
|
||||
assertFalse(status.isSeen());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
@@ -1863,22 +1775,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroups() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
assertEquals(emptyList(), db.getGroups(txn, clientId, majorVersion));
|
||||
db.addGroup(txn, group);
|
||||
assertEquals(singletonList(group),
|
||||
db.getGroups(txn, clientId, majorVersion));
|
||||
db.removeGroup(txn, groupId);
|
||||
assertEquals(emptyList(), db.getGroups(txn, clientId, majorVersion));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionHandling() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1908,23 +1804,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
return db;
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys(long rotationPeriod) {
|
||||
private TransportKeys createTransportKeys() {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||
rotationPeriod - 1, 123, new byte[4]);
|
||||
1, 123, new byte[4]);
|
||||
SecretKey inCurrTagKey = getSecretKey();
|
||||
SecretKey inCurrHeaderKey = getSecretKey();
|
||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||
rotationPeriod, 234, new byte[4]);
|
||||
2, 234, new byte[4]);
|
||||
SecretKey inNextTagKey = getSecretKey();
|
||||
SecretKey inNextHeaderKey = getSecretKey();
|
||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||
rotationPeriod + 1, 345, new byte[4]);
|
||||
3, 345, new byte[4]);
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
rotationPeriod, 456, true);
|
||||
2, 456, true);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,31 +5,23 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -39,268 +31,222 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
context.mock(TransportConnectionReader.class);
|
||||
private final TransportConnectionWriter transportConnectionWriter =
|
||||
context.mock(TransportConnectionWriter.class);
|
||||
private final RecordReaderFactory recordReaderFactory =
|
||||
context.mock(RecordReaderFactory.class);
|
||||
private final RecordWriterFactory recordWriterFactory =
|
||||
context.mock(RecordWriterFactory.class);
|
||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
|
||||
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final KeyAgreementConnection keyAgreementConnection =
|
||||
new KeyAgreementConnection(duplexTransportConnection, transportId);
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final OutputStream outputStream;
|
||||
|
||||
private ByteArrayInputStream inputStream;
|
||||
private ByteArrayOutputStream outputStream;
|
||||
private KeyAgreementTransport kat;
|
||||
|
||||
public KeyAgreementTransportTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
inputStream = context.mock(InputStream.class);
|
||||
outputStream = context.mock(OutputStream.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendKey() throws Exception {
|
||||
byte[] key = getRandomBytes(123);
|
||||
|
||||
setup();
|
||||
AtomicReference<Record> written = expectWriteRecord();
|
||||
|
||||
setup(new byte[0]);
|
||||
byte[] key = TestUtils.getRandomBytes(123);
|
||||
kat.sendKey(key);
|
||||
assertNotNull(written.get());
|
||||
assertRecordEquals(PROTOCOL_VERSION, KEY, key, written.get());
|
||||
assertRecordSent(KEY, key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendConfirm() throws Exception {
|
||||
byte[] confirm = getRandomBytes(123);
|
||||
|
||||
setup();
|
||||
AtomicReference<Record> written = expectWriteRecord();
|
||||
|
||||
setup(new byte[0]);
|
||||
byte[] confirm = TestUtils.getRandomBytes(123);
|
||||
kat.sendConfirm(confirm);
|
||||
assertNotNull(written.get());
|
||||
assertRecordEquals(PROTOCOL_VERSION, CONFIRM, confirm, written.get());
|
||||
assertRecordSent(CONFIRM, confirm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbortWithException() throws Exception {
|
||||
setup();
|
||||
AtomicReference<Record> written = expectWriteRecord();
|
||||
setup(new byte[0]);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportConnectionReader).dispose(true, true);
|
||||
oneOf(transportConnectionWriter).dispose(true);
|
||||
}});
|
||||
|
||||
kat.sendAbort(true);
|
||||
assertNotNull(written.get());
|
||||
assertRecordEquals(PROTOCOL_VERSION, ABORT, new byte[0], written.get());
|
||||
assertRecordSent(ABORT, new byte[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbortWithoutException() throws Exception {
|
||||
setup();
|
||||
AtomicReference<Record> written = expectWriteRecord();
|
||||
setup(new byte[0]);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportConnectionReader).dispose(false, true);
|
||||
oneOf(transportConnectionWriter).dispose(false);
|
||||
}});
|
||||
|
||||
kat.sendAbort(false);
|
||||
assertNotNull(written.get());
|
||||
assertRecordEquals(PROTOCOL_VERSION, ABORT, new byte[0], written.get());
|
||||
assertRecordSent(ABORT, new byte[0]);
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(throwException(new EOFException()));
|
||||
}});
|
||||
setup(new byte[0]);
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfHeaderIsTooShort()
|
||||
throws Exception {
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = KEY;
|
||||
setup(input);
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfPayloadIsTooShort()
|
||||
throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = KEY;
|
||||
ByteUtils.writeUint16(payloadLength, input, 2);
|
||||
setup(input);
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
byte unknownVersion = (byte) (PROTOCOL_VERSION + 1);
|
||||
byte[] key = getRandomBytes(123);
|
||||
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(new Record(unknownVersion, KEY, key)));
|
||||
}});
|
||||
|
||||
setup(createRecord((byte) (PROTOCOL_VERSION + 1), KEY, new byte[123]));
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfAbortIsReceived()
|
||||
throws Exception {
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(new Record(PROTOCOL_VERSION, ABORT, new byte[0])));
|
||||
}});
|
||||
|
||||
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0]));
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfConfirmIsReceived()
|
||||
throws Exception {
|
||||
byte[] confirm = getRandomBytes(123);
|
||||
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(new Record(PROTOCOL_VERSION, CONFIRM, confirm)));
|
||||
}});
|
||||
|
||||
setup(createRecord(PROTOCOL_VERSION, CONFIRM, new byte[123]));
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveKeySkipsUnrecognisedRecordTypes() throws Exception {
|
||||
byte type1 = (byte) (ABORT + 1);
|
||||
byte[] payload1 = getRandomBytes(123);
|
||||
Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
|
||||
byte type2 = (byte) (ABORT + 2);
|
||||
byte[] payload2 = new byte[0];
|
||||
Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
|
||||
byte[] key = getRandomBytes(123);
|
||||
Record keyRecord = new Record(PROTOCOL_VERSION, KEY, key);
|
||||
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(unknownRecord1));
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(unknownRecord2));
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(keyRecord));
|
||||
}});
|
||||
|
||||
assertArrayEquals(key, kat.receiveKey());
|
||||
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1),
|
||||
new byte[123]);
|
||||
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2),
|
||||
new byte[0]);
|
||||
byte[] payload = TestUtils.getRandomBytes(123);
|
||||
byte[] key = createRecord(PROTOCOL_VERSION, KEY, payload);
|
||||
ByteArrayOutputStream input = new ByteArrayOutputStream();
|
||||
input.write(skip1);
|
||||
input.write(skip2);
|
||||
input.write(key);
|
||||
setup(input.toByteArray());
|
||||
assertArrayEquals(payload, kat.receiveKey());
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(throwException(new EOFException()));
|
||||
}});
|
||||
setup(new byte[0]);
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfHeaderIsTooShort()
|
||||
throws Exception {
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = CONFIRM;
|
||||
setup(input);
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfPayloadIsTooShort()
|
||||
throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = CONFIRM;
|
||||
ByteUtils.writeUint16(payloadLength, input, 2);
|
||||
setup(input);
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
byte unknownVersion = (byte) (PROTOCOL_VERSION + 1);
|
||||
byte[] confirm = getRandomBytes(123);
|
||||
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(new Record(unknownVersion, CONFIRM, confirm)));
|
||||
}});
|
||||
|
||||
setup(createRecord((byte) (PROTOCOL_VERSION + 1), CONFIRM,
|
||||
new byte[123]));
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfAbortIsReceived()
|
||||
throws Exception {
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(new Record(PROTOCOL_VERSION, ABORT, new byte[0])));
|
||||
}});
|
||||
|
||||
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0]));
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfKeyIsReceived()
|
||||
public void testReceiveKeyThrowsExceptionIfKeyIsReceived()
|
||||
throws Exception {
|
||||
byte[] key = getRandomBytes(123);
|
||||
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(new Record(PROTOCOL_VERSION, KEY, key)));
|
||||
}});
|
||||
|
||||
setup(createRecord(PROTOCOL_VERSION, KEY, new byte[123]));
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveConfirmSkipsUnrecognisedRecordTypes()
|
||||
throws Exception {
|
||||
byte type1 = (byte) (ABORT + 1);
|
||||
byte[] payload1 = getRandomBytes(123);
|
||||
Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
|
||||
byte type2 = (byte) (ABORT + 2);
|
||||
byte[] payload2 = new byte[0];
|
||||
Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
|
||||
byte[] confirm = getRandomBytes(123);
|
||||
Record confirmRecord = new Record(PROTOCOL_VERSION, CONFIRM, confirm);
|
||||
|
||||
setup();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(unknownRecord1));
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(unknownRecord2));
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(confirmRecord));
|
||||
}});
|
||||
|
||||
assertArrayEquals(confirm, kat.receiveConfirm());
|
||||
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1),
|
||||
new byte[123]);
|
||||
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2),
|
||||
new byte[0]);
|
||||
byte[] payload = TestUtils.getRandomBytes(123);
|
||||
byte[] confirm = createRecord(PROTOCOL_VERSION, CONFIRM, payload);
|
||||
ByteArrayOutputStream input = new ByteArrayOutputStream();
|
||||
input.write(skip1);
|
||||
input.write(skip2);
|
||||
input.write(confirm);
|
||||
setup(input.toByteArray());
|
||||
assertArrayEquals(payload, kat.receiveConfirm());
|
||||
}
|
||||
|
||||
private void setup() throws Exception {
|
||||
private void setup(byte[] input) throws Exception {
|
||||
inputStream = new ByteArrayInputStream(input);
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
context.checking(new Expectations() {{
|
||||
allowing(duplexTransportConnection).getReader();
|
||||
will(returnValue(transportConnectionReader));
|
||||
allowing(transportConnectionReader).getInputStream();
|
||||
will(returnValue(inputStream));
|
||||
oneOf(recordReaderFactory).createRecordReader(inputStream);
|
||||
will(returnValue(recordReader));
|
||||
allowing(duplexTransportConnection).getWriter();
|
||||
will(returnValue(transportConnectionWriter));
|
||||
allowing(transportConnectionWriter).getOutputStream();
|
||||
will(returnValue(outputStream));
|
||||
oneOf(recordWriterFactory).createRecordWriter(outputStream);
|
||||
will(returnValue(recordWriter));
|
||||
}});
|
||||
kat = new KeyAgreementTransport(recordReaderFactory,
|
||||
recordWriterFactory, keyAgreementConnection);
|
||||
kat = new KeyAgreementTransport(keyAgreementConnection);
|
||||
}
|
||||
|
||||
private AtomicReference<Record> expectWriteRecord() throws Exception {
|
||||
AtomicReference<Record> captured = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordWriter).writeRecord(with(any(Record.class)));
|
||||
will(new CaptureArgumentAction<>(captured, Record.class, 0));
|
||||
oneOf(recordWriter).flush();
|
||||
}});
|
||||
return captured;
|
||||
private void assertRecordSent(byte expectedType, byte[] expectedPayload) {
|
||||
byte[] output = outputStream.toByteArray();
|
||||
assertEquals(RECORD_HEADER_LENGTH + expectedPayload.length,
|
||||
output.length);
|
||||
assertEquals(PROTOCOL_VERSION, output[0]);
|
||||
assertEquals(expectedType, output[1]);
|
||||
assertEquals(expectedPayload.length, ByteUtils.readUint16(output, 2));
|
||||
byte[] payload = new byte[output.length - RECORD_HEADER_LENGTH];
|
||||
System.arraycopy(output, RECORD_HEADER_LENGTH, payload, 0,
|
||||
payload.length);
|
||||
assertArrayEquals(expectedPayload, payload);
|
||||
}
|
||||
|
||||
private void assertRecordEquals(byte expectedVersion, byte expectedType,
|
||||
byte[] expectedPayload, Record actual) {
|
||||
assertEquals(expectedVersion, actual.getProtocolVersion());
|
||||
assertEquals(expectedType, actual.getRecordType());
|
||||
assertArrayEquals(expectedPayload, actual.getPayload());
|
||||
private byte[] createRecord(byte version, byte type, byte[] payload) {
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
|
||||
b[0] = version;
|
||||
b[1] = type;
|
||||
ByteUtils.writeUint16(payload.length, b, 2);
|
||||
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ 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 org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
@@ -30,9 +29,8 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
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.CLIENT_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
@@ -47,15 +45,13 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ClientVersioningManager clientVersioningManager =
|
||||
context.mock(ClientVersioningManager.class);
|
||||
private final MetadataParser metadataParser =
|
||||
context.mock(MetadataParser.class);
|
||||
private final ContactGroupFactory contactGroupFactory =
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
|
||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
private final Group localGroup = getGroup(CLIENT_ID);
|
||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||
private final BdfDictionary fooPropertiesDict = BdfDictionary.of(
|
||||
new BdfEntry("fooKey1", "fooValue1"),
|
||||
@@ -81,41 +77,49 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
private TransportPropertyManagerImpl createInstance() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
CLIENT_VERSION);
|
||||
will(returnValue(localGroup));
|
||||
}});
|
||||
return new TransportPropertyManagerImpl(db, clientHelper,
|
||||
clientVersioningManager, metadataParser, contactGroupFactory,
|
||||
clock);
|
||||
metadataParser, contactGroupFactory, clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesGroupsAtStartup() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Contact contact1 = getContact(true);
|
||||
Contact contact2 = getContact(true);
|
||||
List<Contact> contacts = Arrays.asList(contact1, contact2);
|
||||
Group contactGroup1 = getGroup(CLIENT_ID);
|
||||
Group contactGroup2 = getGroup(CLIENT_ID);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
will(returnValue(contacts));
|
||||
// The first contact's group has already been set up
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
CLIENT_VERSION, contact1);
|
||||
will(returnValue(contactGroup1));
|
||||
oneOf(db).containsGroup(txn, contactGroup1.getId());
|
||||
will(returnValue(true));
|
||||
// The second contact's group hasn't been set up
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
CLIENT_VERSION, contact2);
|
||||
will(returnValue(contactGroup2));
|
||||
oneOf(db).containsGroup(txn, contactGroup2.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, contactGroup2);
|
||||
oneOf(db).setGroupVisibility(txn, contact2.getId(),
|
||||
contactGroup2.getId(), SHARED);
|
||||
}});
|
||||
// Copy the latest local properties into the group
|
||||
expectGetLocalProperties(txn);
|
||||
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
|
||||
expectStoreMessage(txn, contactGroup2.getId(), "foo", fooPropertiesDict,
|
||||
1, true, true);
|
||||
expectStoreMessage(txn, contactGroup.getId(), "bar", barPropertiesDict,
|
||||
expectStoreMessage(txn, contactGroup2.getId(), "bar", barPropertiesDict,
|
||||
1, true, true);
|
||||
|
||||
TransportPropertyManagerImpl t = createInstance();
|
||||
@@ -140,17 +144,16 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Create the group and share it with the contact
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
CLIENT_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).containsGroup(txn, contactGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
}});
|
||||
@@ -169,11 +172,11 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
CLIENT_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).removeGroup(txn, contactGroup);
|
||||
}});
|
||||
@@ -304,7 +307,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testStoresRemotePropertiesWithVersion0() throws Exception {
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Map<TransportId, TransportProperties> properties =
|
||||
new LinkedHashMap<>();
|
||||
@@ -315,7 +318,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
CLIENT_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
}});
|
||||
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
|
||||
@@ -418,8 +421,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
Contact contact3 = getContact(true);
|
||||
List<Contact> contacts =
|
||||
Arrays.asList(contact1, contact2, contact3);
|
||||
Group contactGroup2 = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup3 = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup2 = getGroup(CLIENT_ID);
|
||||
Group contactGroup3 = getGroup(CLIENT_ID);
|
||||
Map<MessageId, BdfDictionary> messageMetadata3 =
|
||||
new LinkedHashMap<>();
|
||||
// A remote update for another transport should be ignored
|
||||
@@ -453,14 +456,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
// First contact: skipped because not active
|
||||
// Second contact: no updates
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact2);
|
||||
CLIENT_VERSION, contact2);
|
||||
will(returnValue(contactGroup2));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup2.getId());
|
||||
will(returnValue(Collections.emptyMap()));
|
||||
// Third contact: returns an update
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact3);
|
||||
CLIENT_VERSION, contact3);
|
||||
will(returnValue(contactGroup3));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup3.getId());
|
||||
@@ -521,7 +524,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testMergingNewPropertiesCreatesUpdate() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -535,9 +538,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
fooPropertiesDict, 1, true, false);
|
||||
// Store the new properties in each contact's group, version 1
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
CLIENT_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
@@ -556,7 +559,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
BdfDictionary oldMetadata = BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 1),
|
||||
@@ -594,9 +597,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).removeMessage(txn, localGroupUpdateId);
|
||||
// Store the merged properties in each contact's group, version 2
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
CLIENT_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
|
||||
@@ -18,8 +18,7 @@ import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
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.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
@@ -43,7 +42,7 @@ public class TransportPropertyValidatorTest extends BrambleMockTestCase {
|
||||
transportProperties = new TransportProperties();
|
||||
transportProperties.put("foo", "bar");
|
||||
|
||||
group = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
group = getGroup(getClientId());
|
||||
message = getMessage(group.getId());
|
||||
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RecordReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testAcceptsEmptyPayload() throws Exception {
|
||||
// Version 1, type 2, payload length 0
|
||||
byte[] header = new byte[] {1, 2, 0, 0};
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(header);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
Record record = reader.readRecord();
|
||||
assertEquals(1, record.getProtocolVersion());
|
||||
assertEquals(2, record.getRecordType());
|
||||
assertArrayEquals(new byte[0], record.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMaxLengthPayload() throws Exception {
|
||||
byte[] record =
|
||||
new byte[RECORD_HEADER_BYTES + MAX_RECORD_PAYLOAD_BYTES];
|
||||
// Version 1, type 2, payload length MAX_RECORD_PAYLOAD_BYTES
|
||||
record[0] = 1;
|
||||
record[1] = 2;
|
||||
ByteUtils.writeUint16(MAX_RECORD_PAYLOAD_BYTES, record, 2);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(record);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfPayloadLengthIsNegative()
|
||||
throws Exception {
|
||||
// Version 1, type 2, payload length -1
|
||||
byte[] header = new byte[] {1, 2, (byte) 0xFF, (byte) 0xFF};
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(header);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfPayloadLengthIsTooLarge()
|
||||
throws Exception {
|
||||
// Version 1, type 2, payload length MAX_RECORD_PAYLOAD_BYTES + 1
|
||||
byte[] header = new byte[] {1, 2, 0, 0};
|
||||
ByteUtils.writeUint16(MAX_RECORD_PAYLOAD_BYTES + 1, header, 2);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(header);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = EOFException.class)
|
||||
public void testEofExceptionIfProtocolVersionIsMissing() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = EOFException.class)
|
||||
public void testEofExceptionIfRecordTypeIsMissing() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[1]);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = EOFException.class)
|
||||
public void testEofExceptionIfPayloadLengthIsMissing() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[2]);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = EOFException.class)
|
||||
public void testEofExceptionIfPayloadLengthIsTruncated() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[3]);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
|
||||
@Test(expected = EOFException.class)
|
||||
public void testEofExceptionIfPayloadIsTruncated() throws Exception {
|
||||
// Version 0, type 0, payload length 1
|
||||
byte[] header = new byte[] {0, 0, 0, 1};
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(header);
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
reader.readRecord();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RecordWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testWritesEmptyRecord() throws Exception {
|
||||
testWritesRecord(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWritesMaxLengthRecord() throws Exception {
|
||||
testWritesRecord(MAX_RECORD_PAYLOAD_BYTES);
|
||||
}
|
||||
|
||||
private void testWritesRecord(int payloadLength) throws Exception {
|
||||
byte protocolVersion = 123;
|
||||
byte recordType = 45;
|
||||
byte[] payload = getRandomBytes(payloadLength);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
RecordWriter writer = new RecordWriterImpl(out);
|
||||
writer.writeRecord(new Record(protocolVersion, recordType, payload));
|
||||
writer.flush();
|
||||
byte[] written = out.toByteArray();
|
||||
|
||||
assertEquals(RECORD_HEADER_BYTES + payloadLength, written.length);
|
||||
assertEquals(protocolVersion, written[0]);
|
||||
assertEquals(recordType, written[1]);
|
||||
assertEquals(payloadLength, ByteUtils.readUint16(written, 2));
|
||||
byte[] writtenPayload = new byte[payloadLength];
|
||||
System.arraycopy(written, RECORD_HEADER_BYTES, writtenPayload, 0,
|
||||
payloadLength);
|
||||
assertArrayEquals(payload, writtenPayload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RecordReaderImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
|
||||
byte[] b = createAck(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfAckIsEmpty() throws Exception {
|
||||
byte[] b = createEmptyAck();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
|
||||
byte[] b = createOffer(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||
byte[] b = createOffer(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
|
||||
byte[] b = createEmptyOffer();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
|
||||
byte[] b = createRequest(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||
byte[] b = createRequest(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
|
||||
byte[] b = createEmptyRequest();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
assertTrue(reader.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
assertFalse(reader.eof());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfHeaderIsTooShort() throws Exception {
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH - 1];
|
||||
b[0] = PROTOCOL_VERSION;
|
||||
b[1] = ACK;
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfPayloadIsTooShort() throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
|
||||
b[0] = PROTOCOL_VERSION;
|
||||
b[1] = ACK;
|
||||
ByteUtils.writeUint16(payloadLength, b, 2);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
byte version = (byte) (PROTOCOL_VERSION + 1);
|
||||
byte[] b = createRecord(version, ACK, new byte[0]);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfPayloadIsTooLong() throws Exception {
|
||||
byte[] payload = new byte[MAX_RECORD_PAYLOAD_LENGTH + 1];
|
||||
byte[] b = createRecord(PROTOCOL_VERSION, ACK, payload);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsUnrecognisedRecordTypes() throws Exception {
|
||||
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 1),
|
||||
new byte[123]);
|
||||
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 2),
|
||||
new byte[0]);
|
||||
byte[] ack = createAck(false);
|
||||
ByteArrayOutputStream input = new ByteArrayOutputStream();
|
||||
input.write(skip1);
|
||||
input.write(skip2);
|
||||
input.write(ack);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(input.toByteArray());
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
assertTrue(reader.hasAck());
|
||||
Ack a = reader.readAck();
|
||||
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
|
||||
}
|
||||
|
||||
private byte[] createAck(boolean tooBig) throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, ACK, createPayload(tooBig));
|
||||
}
|
||||
|
||||
private byte[] createEmptyAck() throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, ACK, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createOffer(boolean tooBig) throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, OFFER, createPayload(tooBig));
|
||||
}
|
||||
|
||||
private byte[] createEmptyOffer() throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, OFFER, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createRequest(boolean tooBig) throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, REQUEST, createPayload(tooBig));
|
||||
}
|
||||
|
||||
private byte[] createEmptyRequest() throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, REQUEST, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createRecord(byte version, byte type, byte[] payload) {
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
|
||||
b[0] = version;
|
||||
b[1] = type;
|
||||
ByteUtils.writeUint16(payload.length, b, 2);
|
||||
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
|
||||
return b;
|
||||
}
|
||||
|
||||
private byte[] createPayload(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
||||
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_LENGTH) {
|
||||
payload.write(TestUtils.getRandomId());
|
||||
}
|
||||
if (tooBig) payload.write(TestUtils.getRandomId());
|
||||
assertEquals(tooBig, payload.size() > MAX_RECORD_PAYLOAD_LENGTH);
|
||||
return payload.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
@@ -29,14 +29,14 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
private final ContactId contactId;
|
||||
private final MessageId messageId;
|
||||
private final int maxLatency;
|
||||
private final SyncRecordWriter recordWriter;
|
||||
private final RecordWriter recordWriter;
|
||||
|
||||
public SimplexOutgoingSessionTest() {
|
||||
context = new Mockery();
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
dbExecutor = new ImmediateExecutor();
|
||||
eventBus = context.mock(EventBus.class);
|
||||
recordWriter = context.mock(SyncRecordWriter.class);
|
||||
recordWriter = context.mock(RecordWriter.class);
|
||||
contactId = new ContactId(234);
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
maxLatency = Integer.MAX_VALUE;
|
||||
|
||||
@@ -12,11 +12,11 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
@@ -54,9 +54,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
@Inject
|
||||
StreamWriterFactory streamWriterFactory;
|
||||
@Inject
|
||||
SyncRecordReaderFactory recordReaderFactory;
|
||||
RecordReaderFactory recordReaderFactory;
|
||||
@Inject
|
||||
SyncRecordWriterFactory recordWriterFactory;
|
||||
RecordWriterFactory recordWriterFactory;
|
||||
@Inject
|
||||
TransportCrypto transportCrypto;
|
||||
|
||||
@@ -81,9 +81,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
streamNumber = 123;
|
||||
// Create a group
|
||||
ClientId clientId = getClientId();
|
||||
int majorVersion = 1234567890;
|
||||
int clientVersion = 1234567890;
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
Group group = groupFactory.createGroup(clientId, majorVersion,
|
||||
Group group = groupFactory.createGroup(clientId, clientVersion,
|
||||
descriptor);
|
||||
// Add two messages to the group
|
||||
long timestamp = System.currentTimeMillis();
|
||||
@@ -104,7 +104,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
headerKey, streamNumber);
|
||||
OutputStream streamWriter = streamWriterFactory.createStreamWriter(out,
|
||||
ctx);
|
||||
SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter(
|
||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(
|
||||
streamWriter);
|
||||
|
||||
recordWriter.writeAck(new Ack(messageIds));
|
||||
@@ -112,8 +112,8 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
recordWriter.writeMessage(message1.getRaw());
|
||||
recordWriter.writeOffer(new Offer(messageIds));
|
||||
recordWriter.writeRequest(new Request(messageIds));
|
||||
recordWriter.flush();
|
||||
|
||||
streamWriter.flush();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
headerKey, streamNumber);
|
||||
InputStream streamReader = streamReaderFactory.createStreamReader(in,
|
||||
ctx);
|
||||
SyncRecordReader recordReader = recordReaderFactory.createRecordReader(
|
||||
RecordReader recordReader = recordReaderFactory.createRecordReader(
|
||||
streamReader);
|
||||
|
||||
// Read the ack
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.record.RecordModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.test.TestSecureRandomModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
@@ -14,7 +13,6 @@ import dagger.Component;
|
||||
@Component(modules = {
|
||||
TestSecureRandomModule.class,
|
||||
CryptoModule.class,
|
||||
RecordModule.class,
|
||||
SyncModule.class,
|
||||
SystemModule.class,
|
||||
TransportModule.class
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
expectReadRecord(createAck());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
Ack ack = reader.readAck();
|
||||
assertEquals(MAX_MESSAGE_IDS, ack.getMessageIds().size());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfAckIsEmpty() throws Exception {
|
||||
expectReadRecord(createEmptyAck());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||
expectReadRecord(createOffer());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
Offer offer = reader.readOffer();
|
||||
assertEquals(MAX_MESSAGE_IDS, offer.getMessageIds().size());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
|
||||
expectReadRecord(createEmptyOffer());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||
expectReadRecord(createRequest());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
Request request = reader.readRequest();
|
||||
assertEquals(MAX_MESSAGE_IDS, request.getMessageIds().size());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
|
||||
expectReadRecord(createEmptyRequest());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(throwException(new EOFException()));
|
||||
}});
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
assertTrue(reader.eof());
|
||||
assertTrue(reader.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
|
||||
expectReadRecord(createAck());
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
assertFalse(reader.eof());
|
||||
assertFalse(reader.eof());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
byte version = (byte) (PROTOCOL_VERSION + 1);
|
||||
byte[] payload = getRandomId();
|
||||
|
||||
expectReadRecord(new Record(version, ACK, payload));
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsUnrecognisedRecordTypes() throws Exception {
|
||||
byte type1 = (byte) (REQUEST + 1);
|
||||
byte[] payload1 = getRandomBytes(123);
|
||||
Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
|
||||
byte type2 = (byte) (REQUEST + 2);
|
||||
byte[] payload2 = new byte[0];
|
||||
Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
|
||||
Record ackRecord = createAck();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(unknownRecord1));
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(unknownRecord2));
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(ackRecord));
|
||||
|
||||
}});
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
assertTrue(reader.hasAck());
|
||||
Ack a = reader.readAck();
|
||||
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
|
||||
}
|
||||
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord();
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
|
||||
private Record createAck() throws Exception {
|
||||
return new Record(PROTOCOL_VERSION, ACK, createPayload());
|
||||
}
|
||||
|
||||
private Record createEmptyAck() throws Exception {
|
||||
return new Record(PROTOCOL_VERSION, ACK, new byte[0]);
|
||||
}
|
||||
|
||||
private Record createOffer() throws Exception {
|
||||
return new Record(PROTOCOL_VERSION, OFFER, createPayload());
|
||||
}
|
||||
|
||||
private Record createEmptyOffer() throws Exception {
|
||||
return new Record(PROTOCOL_VERSION, OFFER, new byte[0]);
|
||||
}
|
||||
|
||||
private Record createRequest() throws Exception {
|
||||
return new Record(PROTOCOL_VERSION, REQUEST, createPayload());
|
||||
}
|
||||
|
||||
private Record createEmptyRequest() throws Exception {
|
||||
return new Record(PROTOCOL_VERSION, REQUEST, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createPayload() throws Exception {
|
||||
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
||||
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_BYTES) {
|
||||
payload.write(getRandomId());
|
||||
}
|
||||
return payload.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -53,11 +53,10 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
private final Executor dbExecutor = new ImmediateExecutor();
|
||||
private final Executor validationExecutor = new ImmediateExecutor();
|
||||
private final ClientId clientId = getClientId();
|
||||
private final int majorVersion = 123;
|
||||
private final MessageId messageId = new MessageId(getRandomId());
|
||||
private final MessageId messageId1 = new MessageId(getRandomId());
|
||||
private final MessageId messageId2 = new MessageId(getRandomId());
|
||||
private final Group group = getGroup(clientId, majorVersion);
|
||||
private final Group group = getGroup(clientId);
|
||||
private final GroupId groupId = group.getId();
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
private final byte[] raw = new byte[123];
|
||||
@@ -86,8 +85,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
public void setUp() {
|
||||
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor,
|
||||
messageFactory);
|
||||
vm.registerMessageValidator(clientId, majorVersion, validator);
|
||||
vm.registerIncomingMessageHook(clientId, majorVersion, hook);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class TestCryptoExecutorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@CryptoExecutor
|
||||
Executor provideCryptoExecutor() {
|
||||
return new ImmediateExecutor();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -12,7 +11,6 @@ import static org.briarproject.bramble.test.UTest.Result.LARGER;
|
||||
import static org.briarproject.bramble.test.UTest.Result.SMALLER;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@Ignore
|
||||
public class UTestTest extends BrambleTestCase {
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
@@ -24,7 +24,7 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
||||
context.mock(MetadataEncoder.class);
|
||||
protected final Clock clock = context.mock(Clock.class);
|
||||
|
||||
protected final Group group = getGroup(getClientId(), 123);
|
||||
protected final Group group = getGroup(getClientId());
|
||||
protected final GroupId groupId = group.getId();
|
||||
protected final byte[] descriptor = group.getDescriptor();
|
||||
protected final Message message = getMessage(groupId);
|
||||
|
||||
@@ -1,669 +0,0 @@
|
||||
package org.briarproject.bramble.versioning;
|
||||
|
||||
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.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
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.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
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.getRandomId;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ContactGroupFactory contactGroupFactory =
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final ClientVersioningHook hook =
|
||||
context.mock(ClientVersioningHook.class);
|
||||
|
||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
private final Contact contact = new Contact(new ContactId(123),
|
||||
getAuthor(), getLocalAuthor().getId(), true, true);
|
||||
private final ClientId clientId = getClientId();
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final Transaction txn = new Transaction(null, false);
|
||||
|
||||
private ClientVersioningManagerImpl createInstance() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
will(returnValue(localGroup));
|
||||
}});
|
||||
return new ClientVersioningManagerImpl(db, clientHelper,
|
||||
contactGroupFactory, clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesGroupsAtStartup() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
}});
|
||||
expectAddingContact();
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.createLocalState(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.createLocalState(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||
expectAddingContact();
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.addingContact(txn, contact);
|
||||
}
|
||||
|
||||
private void expectAddingContact() throws Exception {
|
||||
BdfDictionary groupMeta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
||||
long now = System.currentTimeMillis();
|
||||
BdfList localUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
Message localUpdate = getMessage(contactGroup.getId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
|
||||
groupMeta);
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||
localUpdateBody);
|
||||
will(returnValue(localUpdate));
|
||||
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
|
||||
localUpdateMeta, true);
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).removeGroup(txn, contactGroup);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.removingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoresClientVersionsAtFirstStartup() throws Exception {
|
||||
BdfList localVersionsBody =
|
||||
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
|
||||
Message localVersions = getMessage(localGroup.getId());
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
// No client versions have been stored yet
|
||||
oneOf(db).getMessageIds(txn, localGroup.getId());
|
||||
will(returnValue(emptyList()));
|
||||
// Store the client versions
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(localGroup.getId(), now,
|
||||
localVersionsBody);
|
||||
will(returnValue(localVersions));
|
||||
oneOf(db).addLocalMessage(txn, localVersions, new Metadata(),
|
||||
false);
|
||||
// Inform contacts that client versions have changed
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
// Find the latest local and remote updates (no remote update)
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||
will(returnValue(localUpdateBody));
|
||||
// Latest local update is up-to-date, no visibilities have changed
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
c.startService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparesClientVersionsAtSubsequentStartup()
|
||||
throws Exception {
|
||||
MessageId localVersionsId = new MessageId(getRandomId());
|
||||
BdfList localVersionsBody =
|
||||
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
// Load the old client versions
|
||||
oneOf(db).getMessageIds(txn, localGroup.getId());
|
||||
will(returnValue(singletonList(localVersionsId)));
|
||||
oneOf(clientHelper).getMessageAsList(txn, localVersionsId);
|
||||
will(returnValue(localVersionsBody));
|
||||
// Client versions are up-to-date
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
c.startService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoresClientVersionsAtSubsequentStartupIfChanged()
|
||||
throws Exception {
|
||||
// The client had minor version 234 in the old client versions
|
||||
BdfList oldLocalVersionsBody =
|
||||
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
|
||||
// The client has minor version 345 in the new client versions
|
||||
BdfList newLocalVersionsBody =
|
||||
BdfList.of(BdfList.of(clientId.getString(), 123, 345));
|
||||
// The client had minor version 234 in the old local update
|
||||
BdfList oldLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
// The client has minor version 345 in the new local update
|
||||
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 345, false)), 2L);
|
||||
|
||||
MessageId oldLocalVersionsId = new MessageId(getRandomId());
|
||||
Message newLocalVersions = getMessage(localGroup.getId());
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
Message newLocalUpdate = getMessage(contactGroup.getId());
|
||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
// Load the old client versions
|
||||
oneOf(db).getMessageIds(txn, localGroup.getId());
|
||||
will(returnValue(singletonList(oldLocalVersionsId)));
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId);
|
||||
will(returnValue(oldLocalVersionsBody));
|
||||
// Delete the old client versions
|
||||
oneOf(db).removeMessage(txn, oldLocalVersionsId);
|
||||
// Store the new client versions
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(localGroup.getId(), now,
|
||||
newLocalVersionsBody);
|
||||
will(returnValue(newLocalVersions));
|
||||
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
|
||||
false);
|
||||
// Inform contacts that client versions have changed
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
// Find the latest local and remote updates (no remote update)
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(singletonMap(oldLocalUpdateId,
|
||||
oldLocalUpdateMeta)));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// Delete the latest local update
|
||||
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
|
||||
// Store the new local update
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||
newLocalUpdateBody);
|
||||
will(returnValue(newLocalUpdate));
|
||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||
newLocalUpdateMeta, true);
|
||||
// No visibilities have changed
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 345, hook);
|
||||
c.startService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivatesNewClientAtStartupIfAlreadyAdvertisedByContact()
|
||||
throws Exception {
|
||||
testActivatesNewClientAtStartup(false, VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivatesNewClientAtStartupIfAlreadyActivatedByContact()
|
||||
throws Exception {
|
||||
testActivatesNewClientAtStartup(true, SHARED);
|
||||
}
|
||||
|
||||
private void testActivatesNewClientAtStartup(boolean remoteActive,
|
||||
Visibility visibility) throws Exception {
|
||||
// The client was missing from the old client versions
|
||||
BdfList oldLocalVersionsBody = new BdfList();
|
||||
// The client is included in the new client versions
|
||||
BdfList newLocalVersionsBody =
|
||||
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
|
||||
// The client was missing from the old local update
|
||||
BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
// The client was included in the old remote update
|
||||
BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 345, remoteActive)), 1L);
|
||||
// The client is active in the new local update
|
||||
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 2L);
|
||||
|
||||
MessageId oldLocalVersionsId = new MessageId(getRandomId());
|
||||
Message newLocalVersions = getMessage(localGroup.getId());
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
|
||||
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
|
||||
Message newLocalUpdate = getMessage(localGroup.getId());
|
||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
// Load the old client versions
|
||||
oneOf(db).getMessageIds(txn, localGroup.getId());
|
||||
will(returnValue(singletonList(oldLocalVersionsId)));
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId);
|
||||
will(returnValue(oldLocalVersionsBody));
|
||||
// Delete the old client versions
|
||||
oneOf(db).removeMessage(txn, oldLocalVersionsId);
|
||||
// Store the new client versions
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(localGroup.getId(), now,
|
||||
newLocalVersionsBody);
|
||||
will(returnValue(newLocalVersions));
|
||||
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
|
||||
false);
|
||||
// Inform contacts that client versions have changed
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
// Find the latest local and remote updates
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// Load the latest remote update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
|
||||
will(returnValue(oldRemoteUpdateBody));
|
||||
// Delete the latest local update
|
||||
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
|
||||
// Store the new local update
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||
newLocalUpdateBody);
|
||||
will(returnValue(newLocalUpdate));
|
||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||
newLocalUpdateMeta, true);
|
||||
// The client's visibility has changed
|
||||
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
c.startService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesObsoleteRemoteUpdate() throws Exception {
|
||||
Message newRemoteUpdate = getMessage(contactGroup.getId());
|
||||
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
|
||||
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
will(returnValue(newRemoteUpdateBody));
|
||||
// Find the latest local and remote updates
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
// Delete the new remote update, which is obsolete
|
||||
oneOf(db).deleteMessage(txn, newRemoteUpdate.getId());
|
||||
oneOf(db).deleteMessageMetadata(txn, newRemoteUpdate.getId());
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesPreviousRemoteUpdate() throws Exception {
|
||||
Message newRemoteUpdate = getMessage(contactGroup.getId());
|
||||
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L);
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
|
||||
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
|
||||
BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
will(returnValue(newRemoteUpdateBody));
|
||||
// Find the latest local and remote updates
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// Load the latest remote update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
|
||||
will(returnValue(oldRemoteUpdateBody));
|
||||
// Delete the old remote update
|
||||
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
|
||||
// No states or visibilities have changed
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsFirstRemoteUpdate() throws Exception {
|
||||
Message newRemoteUpdate = getMessage(contactGroup.getId());
|
||||
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
will(returnValue(newRemoteUpdateBody));
|
||||
// Find the latest local and remote updates (no remote update)
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(singletonMap(oldLocalUpdateId,
|
||||
oldLocalUpdateMeta)));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// No states or visibilities have changed
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivatesClientOnIncomingMessageWhenAdvertisedByContact()
|
||||
throws Exception {
|
||||
testActivatesClientOnIncomingMessage(false, VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivatesClientOnIncomingMessageWhenActivatedByContact()
|
||||
throws Exception {
|
||||
testActivatesClientOnIncomingMessage(true, SHARED);
|
||||
}
|
||||
|
||||
private void testActivatesClientOnIncomingMessage(boolean remoteActive,
|
||||
Visibility visibility) throws Exception {
|
||||
// The client was missing from the old remote update
|
||||
BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||
// The client was inactive in the old local update
|
||||
BdfList oldLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||
// The client is included in the new remote update
|
||||
BdfList newRemoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, remoteActive)), 2L);
|
||||
// The client is active in the new local update
|
||||
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 2L);
|
||||
|
||||
Message newRemoteUpdate = getMessage(contactGroup.getId());
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
|
||||
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
|
||||
Message newLocalUpdate = getMessage(contactGroup.getId());
|
||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
BdfDictionary groupMeta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
will(returnValue(newRemoteUpdateBody));
|
||||
// Find the latest local and remote updates
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// Load the latest remote update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
|
||||
will(returnValue(oldRemoteUpdateBody));
|
||||
// Delete the old remote update
|
||||
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
|
||||
// Delete the old local update
|
||||
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
|
||||
// Store the new local update
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||
newLocalUpdateBody);
|
||||
will(returnValue(newLocalUpdate));
|
||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||
newLocalUpdateMeta, true);
|
||||
// The client's visibility has changed
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(groupMeta));
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivatesClientOnIncomingMessage() throws Exception {
|
||||
// The client was active in the old local and remote updates
|
||||
BdfList oldLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||
BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||
// The client is missing from the new remote update
|
||||
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L);
|
||||
// The client is inactive in the new local update
|
||||
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
|
||||
BdfList.of(clientId.getString(), 123, 234, false)), 2L);
|
||||
|
||||
Message newRemoteUpdate = getMessage(contactGroup.getId());
|
||||
MessageId oldLocalUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
|
||||
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
|
||||
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
|
||||
Message newLocalUpdate = getMessage(contactGroup.getId());
|
||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
BdfDictionary groupMeta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
will(returnValue(newRemoteUpdateBody));
|
||||
// Find the latest local and remote updates
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// Load the latest remote update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
|
||||
will(returnValue(oldRemoteUpdateBody));
|
||||
// Delete the old remote update
|
||||
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
|
||||
// Delete the old local update
|
||||
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
|
||||
// Store the new local update
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||
newLocalUpdateBody);
|
||||
will(returnValue(newLocalUpdate));
|
||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||
newLocalUpdateMeta, true);
|
||||
// The client's visibility has changed
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(groupMeta));
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);
|
||||
}});
|
||||
|
||||
ClientVersioningManagerImpl c = createInstance();
|
||||
c.registerClient(clientId, 123, 234, hook);
|
||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
package org.briarproject.bramble.versioning;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
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.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ClientVersioningValidatorTest extends BrambleMockTestCase {
|
||||
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final MetadataEncoder metadataEncoder =
|
||||
context.mock(MetadataEncoder.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final ClientVersioningValidator validator =
|
||||
new ClientVersioningValidator(clientHelper, metadataEncoder, clock);
|
||||
|
||||
private final Group group = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
private final Message message = getMessage(group.getId());
|
||||
private final ClientId clientId = getClientId();
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBody() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBody() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList(), 123, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullStatesList() throws Exception {
|
||||
BdfList body = BdfList.of(null, 123);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonListStatesList() throws Exception {
|
||||
BdfList body = BdfList.of("", 123);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsEmptyStatesList() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList(), 123);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 123L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullUpdateVersion() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonLongUpdateVersion() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList(), "123");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNegativeUpdateVersion() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList(), -1);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsZeroUpdateVersion() throws Exception {
|
||||
BdfList body = BdfList.of(new BdfList(), 0);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 0L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortClientState() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, 234);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongClientState() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, 234, true, null);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullClientId() throws Exception {
|
||||
BdfList state = BdfList.of(null, 123, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonStringClientId() throws Exception {
|
||||
byte[] id = getRandomBytes(MAX_CLIENT_ID_LENGTH);
|
||||
BdfList state = BdfList.of(id, 123, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortClientId() throws Exception {
|
||||
BdfList state = BdfList.of("", 123, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthClientId() throws Exception {
|
||||
BdfList state = BdfList.of(getRandomString(1), 123, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongClientId() throws Exception {
|
||||
String id = getRandomString(MAX_CLIENT_ID_LENGTH + 1);
|
||||
BdfList state = BdfList.of(id, 123, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMaxLengthClientId() throws Exception {
|
||||
String id = getRandomString(MAX_CLIENT_ID_LENGTH);
|
||||
BdfList state = BdfList.of(id, 123, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullMajorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), null, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonLongMajorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), "123", 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNegativeMajorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), -1, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsZeroMajorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 0, 234, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullMinorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, null, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonLongMinorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, "234", true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNegativeMinorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, -1, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsZeroMinorVersion() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, 0, true);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullActiveFlag() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, 234, null);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonBooleanActiveFlag() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, 234, "true");
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsNegativeActiveFlag() throws Exception {
|
||||
BdfList state = BdfList.of(clientId.getString(), 123, 234, false);
|
||||
BdfList body = BdfList.of(BdfList.of(state), 345);
|
||||
BdfMessageContext context =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(emptyList(), context.getDependencies());
|
||||
BdfDictionary expectedMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
assertEquals(expectedMeta, context.getDictionary());
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,9 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
|
||||
Executor ioExecutor, SecureRandom secureRandom,
|
||||
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
|
||||
maxLatency);
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +110,6 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new JavaBluetoothTransportConnection(this, connectionLimiter, s);
|
||||
return new JavaBluetoothTransportConnection(this, s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,10 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
BluetoothConnectionLimiter connectionLimiter =
|
||||
new BluetoothConnectionLimiterImpl();
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
|
||||
ioExecutor, secureRandom, backoff, callback, MAX_LATENCY);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -14,15 +14,11 @@ import javax.microedition.io.StreamConnection;
|
||||
class JavaBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothConnectionLimiter connectionManager;
|
||||
private final StreamConnection stream;
|
||||
|
||||
JavaBluetoothTransportConnection(Plugin plugin,
|
||||
BluetoothConnectionLimiter connectionManager,
|
||||
StreamConnection stream) {
|
||||
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
super(plugin);
|
||||
this.stream = stream;
|
||||
this.connectionManager = connectionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,10 +33,6 @@ class JavaBluetoothTransportConnection
|
||||
|
||||
@Override
|
||||
protected void closeConnection(boolean exception) throws IOException {
|
||||
try {
|
||||
stream.close();
|
||||
} finally {
|
||||
connectionManager.connectionClosed(this);
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +190,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 10001
|
||||
versionName "1.0.1"
|
||||
versionCode 1700
|
||||
versionName "0.17.0"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
resValue "string", "app_package", "org.briarproject.briar.android"
|
||||
resValue "string", "app_name", "Briar"
|
||||
|
||||
@@ -221,6 +221,8 @@ public class BriarService extends Service {
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
LOG.warning("Memory is low");
|
||||
shutdownFromBackground();
|
||||
showLowMemoryShutdownNotification();
|
||||
}
|
||||
|
||||
private void shutdownFromBackground() {
|
||||
@@ -245,7 +247,6 @@ public class BriarService extends Service {
|
||||
}).start();
|
||||
}
|
||||
|
||||
// TODO: Remove if low memory shutdowns are not appropriate
|
||||
private void showLowMemoryShutdownNotification() {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||
|
||||
@@ -124,9 +124,11 @@ abstract class ConversationItem {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_accepted_sent,
|
||||
ir.getName());
|
||||
text += "\n\n" + ctx.getString(
|
||||
R.string.introduction_response_accepted_sent_info,
|
||||
ir.getName());
|
||||
if (ir.isSuccessPossible()) {
|
||||
text += "\n\n" + ctx.getString(
|
||||
R.string.introduction_response_accepted_sent_info,
|
||||
ir.getName());
|
||||
}
|
||||
} else {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_declined_sent,
|
||||
|
||||
@@ -326,5 +326,4 @@
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Открит е овърлей на екрана</string>
|
||||
<!--Permission Requests-->
|
||||
<!--Low Memory Notification-->
|
||||
</resources>
|
||||
|
||||
@@ -149,5 +149,4 @@
|
||||
<!--Sign Out-->
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<!--Permission Requests-->
|
||||
<!--Low Memory Notification-->
|
||||
</resources>
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
<string name="startup_failed_notification_title">Briar no s\'ha pogut iniciar</string>
|
||||
<string name="startup_failed_notification_text">Toqueu per obtenir més informació.</string>
|
||||
<string name="startup_failed_activity_title">Error iniciant Briar</string>
|
||||
<string name="startup_failed_db_error">Per alguna raó, la vostra base de dades de Briar ha estat corrompuda i no es pot reparar. El vostre compte, les dades i els contactes s\'han perdut. Malhauradament, has de reinstal·lar Briar o crear un nou compte triant \"He oblidat la contrasenya\" en la pantalla on poses la contrasenya.</string>
|
||||
<string name="startup_failed_data_too_old_error">El teu compte es va crear amb una versió antiga d\'aquesta app i no es pot obrir amb aquesta versió. O bé reinstal·les la versió antiga o crea un nou compte triant \"Ho oblidat la contrasenya\" en la pantalla on poses la contrasenya.</string>
|
||||
<string name="startup_failed_data_too_new_error">Aquesta versió de l\'aplicació és massa antiga. Actualitzeu a la versió més recent i torneu-ho a provar.</string>
|
||||
<string name="startup_failed_service_error">Briar no ha pogut engegar un connector necessari. La solució sol ser reinstal·lar Briar. De tota manera, tingueu en compte que si ho feu perdreu el vostre compte i totes les dades associades, doncs Briar no usa servidors centrals per desar les vostres dades.</string>
|
||||
<plurals name="expiry_warning">
|
||||
@@ -43,9 +41,6 @@
|
||||
</plurals>
|
||||
<string name="expiry_update">S\'ha ampliat la data de venciment de la prova. El vostre compte caducarà ara en %d dies.</string>
|
||||
<string name="expiry_date_reached">Aquest programa ha caducat.\nGràcies per haver-lo provat!</string>
|
||||
<string name="download_briar">Per continuar utilitzant Briar, baixeu la versió 1.0.</string>
|
||||
<string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string>
|
||||
<string name="download_briar_button">Baixa Briar 1.0</string>
|
||||
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
||||
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
||||
<!--Navigation Drawer-->
|
||||
@@ -104,9 +99,7 @@
|
||||
<string name="help">Ajuda</string>
|
||||
<string name="sorry">Ens sap greu</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">No hi ha contactes\n\nPulsa l\'icona + per afegir un contacte</string>
|
||||
<string name="date_no_private_messages">Sense missatges.</string>
|
||||
<string name="no_private_messages">No hi ha missatges</string>
|
||||
<string name="message_hint">Escriu el missatge.</string>
|
||||
<string name="delete_contact">Suprimeix contacte</string>
|
||||
<string name="dialog_title_delete_contact">Confirma la supressió del contacte</string>
|
||||
@@ -134,7 +127,6 @@
|
||||
<string name="introduction_onboarding_title">Introdueix els teus contactes</string>
|
||||
<string name="introduction_onboarding_text">Pots presentar els teus contactes entre si, de manera que no necessiten trobar-se en persona per a donar-se d\'alta com a contactes a Briar.</string>
|
||||
<string name="introduction_activity_title">Seleccionar contacte</string>
|
||||
<string name="introduction_not_possible">Ja teniu una introducció en progrés amb aquests contactes. Permeteu que això finalitzi primer. Si vostè o els vostres contactes poques vegades són en línia, això pot trigar un temps.</string>
|
||||
<string name="introduction_message_title">Introduir contacte</string>
|
||||
<string name="introduction_message_hint">Afegir un missatge (opcional)</string>
|
||||
<string name="introduction_button">Presentar contactes entre si</string>
|
||||
@@ -146,7 +138,6 @@
|
||||
<string name="introduction_request_exists_received">%1$s ha demanat que us presenteu a %2$s, però %2$s ja està a la vostra llista de contactes. Com que %1$s no sap això, encara podeu respondre:</string>
|
||||
<string name="introduction_request_answered_received">%1$s ha demanat que us presenteu a %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Has acceptat la presentació amb el contacte %1$s.</string>
|
||||
<string name="introduction_response_accepted_sent_info">Abans que %1$s s\'afegeixi als vostres contactes, també heu d\'acceptar la introducció. Això pot trigar un temps.</string>
|
||||
<string name="introduction_response_declined_sent">Heu rebutjat la presentació a %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s va acceptar la presentació de %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s va rebutjar la presentació a %2$s.</string>
|
||||
@@ -156,7 +147,6 @@
|
||||
<item quantity="other">S\'ha afegit %d nous contactes.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">No hi ha grups\n\nApreta l\'icona de + per crear un grup, o demana als teus contactes de compartir els seus grups amb tu</string>
|
||||
<string name="groups_created_by">Creat per 1%s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">1%d missatge</item>
|
||||
@@ -209,7 +199,6 @@
|
||||
<string name="groups_reveal_visible_revealed_by_contact">La relació del contacte és visible per al grup (revelat per %s)</string>
|
||||
<string name="groups_reveal_invisible">La relació del contacte no és visible per al grup</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">No hi ha fòrums\n\nApreta l\'icona + per crear un fòrum, o demana els teus contactes que comparteixin els seus fòrums amb tu</string>
|
||||
<string name="create_forum_title">Crea un fòrum</string>
|
||||
<string name="choose_forum_hint">Trieu un nom per al fòrum</string>
|
||||
<string name="create_forum_button">Crea el fòrum</string>
|
||||
@@ -226,23 +215,17 @@
|
||||
<string name="btn_reply">Respon</string>
|
||||
<string name="forum_leave">Abandona el fòrum</string>
|
||||
<string name="dialog_title_leave_forum">Confirmeu la sortida del Forum</string>
|
||||
<string name="dialog_message_leave_forum">Estàs segur/a que vols marxar d\'aquest fòrum?\n\nTots els contactes que tinguis compartits en aquest fòrum poden deixar de rebre actualitzacions</string>
|
||||
<string name="dialog_button_leave">Abandona</string>
|
||||
<string name="forum_left_toast">Has deixat el fòrum</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Comparteix el fòrum</string>
|
||||
<string name="contacts_selected">Contactes seleccionats</string>
|
||||
<string name="activity_share_toolbar_header">Tria contactes</string>
|
||||
<string name="no_contacts_selector">No hi ha contactes\n\nSi us plau torna quan hagis afegit algun contacte</string>
|
||||
<string name="forum_shared_snackbar">Fòrum compartit amb els contactes seleccionats</string>
|
||||
<string name="forum_share_message">Afegiu un missatge (opcional)</string>
|
||||
<string name="forum_share_error">S\'ha produït un error en compartir aquest fòrum.</string>
|
||||
<string name="forum_invitation_received">%1$s ha compartit el fòrum «%2$s» amb vós.</string>
|
||||
<string name="forum_invitation_sent">Heu compartit el fòrum «%1$s» amb %2$s.</string>
|
||||
<string name="forum_invitations_title">Invitacions al fòrum</string>
|
||||
<string name="forum_invitation_exists">Ja has acceptat una invitació a aquest fòrum.\n\nAcceptant més invitacions farà que la teva connexió amb el fòrum sigui més ràpida i fiable</string>
|
||||
<string name="forum_joined_toast">T\'has unit al fòrum</string>
|
||||
<string name="forum_declined_toast">Has declinat la invitació</string>
|
||||
<string name="shared_by_format">Compartit per 1%s</string>
|
||||
<string name="forum_invitation_already_sharing">Ja esteu compartint</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Heu acceptat la invitació del fòrum de %s.</string>
|
||||
@@ -261,16 +244,12 @@
|
||||
<string name="blogs_other_blog_empty_state">No hi ha publicacions per mostrar</string>
|
||||
<string name="read_more">llegir més</string>
|
||||
<string name="blogs_write_blog_post">Escriviu una publicació de blog</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Escriu el que vulguis publicar</string>
|
||||
<string name="blogs_publish_blog_post">Publica</string>
|
||||
<string name="blogs_blog_post_created">S\'ha creat la publicació al blog</string>
|
||||
<string name="blogs_blog_post_received">S\'ha rebut una nova publicació al blog</string>
|
||||
<string name="blogs_blog_post_scroll_to">Desplaça</string>
|
||||
<string name="blogs_feed_empty_state">No hi ha publicacions\n\nPublicacions dels teus contactes i blogs als quals et subscriguis apareixeran aquí\n\nPulsa la icona del bolígraf per escriure un post</string>
|
||||
<string name="blogs_remove_blog">Elimina el blog</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Estàs segur/a que vols eliminar aquest blog?\n\nLes publicacions seran eliminades del teu dispositiu però no del d\'altres persones.\n\nEls contactes amb els quals hagis compartit aquest blog poden deixar de rebre actualitzacions.</string>
|
||||
<string name="blogs_remove_blog_ok">Eliminar</string>
|
||||
<string name="blogs_blog_removed">Blog eliminat</string>
|
||||
<string name="blogs_reblog_comment_hint">Afegiu un comentari (opcional)</string>
|
||||
<string name="blogs_reblog_button">Rebloga</string>
|
||||
<!--Blog Sharing-->
|
||||
@@ -285,8 +264,6 @@
|
||||
<string name="blogs_sharing_invitation_received">%1$s us ha compartit el blog \"%2$s\".</string>
|
||||
<string name="blogs_sharing_invitation_sent">Heu compartit el blog \"%1$s\" amb %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Invitacions al blog</string>
|
||||
<string name="blogs_sharing_joined_toast">T\'has subscrit al blog</string>
|
||||
<string name="blogs_sharing_declined_toast">Has declinat la invitació</string>
|
||||
<string name="sharing_status_blog">Qualsevol subscrit a un blog el pot compartir amb els seus contactes. Esteu compartint aquest blog amb els següents contactes. També hi pot haver altres subscrits que no veieu.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Importa canal RSS</string>
|
||||
@@ -298,10 +275,8 @@
|
||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Darrera actualització:</string>
|
||||
<string name="blogs_rss_remove_feed">Elimina el canal</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Estàs segur/a que vols eliminar aquest noticiari?\n\nLes publicacions seran eliminades del teu dispositiu però no del d\'altres persones.\n\nEls contactes amb els que hagis compartit aquest noticiari poden deixar de rebre actualitzacions.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Eliminar</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">El canal no s\'ha pogut esborrar.</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">No hi ha notícies a mostrar\n\nPulsa l\'icona + per importar un noticiari</string>
|
||||
<string name="blogs_rss_feeds_manage_error">S\'ha produït un problema en carregar els vostres canals. Torneu-ho a provar més tard.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Xarxes</string>
|
||||
@@ -338,16 +313,12 @@
|
||||
<string name="notification_settings_title">Notificacions</string>
|
||||
<string name="notify_private_messages_setting_title">Missatges privats</string>
|
||||
<string name="notify_private_messages_setting_summary">Mostra els avisos per als missatges privats</string>
|
||||
<string name="notify_private_messages_setting_summary_26">Configura les alertes per missatges privats</string>
|
||||
<string name="notify_group_messages_setting_title">Missatges grupals</string>
|
||||
<string name="notify_group_messages_setting_summary">Mostra alertes per als missatges grupals</string>
|
||||
<string name="notify_group_messages_setting_summary_26">Configura les alertes per missatges de grup</string>
|
||||
<string name="notify_forum_posts_setting_title">Publicacions del fòrum</string>
|
||||
<string name="notify_forum_posts_setting_summary">Mostra alertes per a les publicacions del fòrum</string>
|
||||
<string name="notify_forum_posts_setting_summary_26">Configura les alertes per publicacions als fòrums</string>
|
||||
<string name="notify_blog_posts_setting_title">Publicacions al blog</string>
|
||||
<string name="notify_blog_posts_setting_summary">Mostra alertes per les publicacions al blog</string>
|
||||
<string name="notify_blog_posts_setting_summary_26">Configura les alertes per publicacions als blogs</string>
|
||||
<string name="notify_vibration_setting">Vibra</string>
|
||||
<string name="notify_lock_screen_setting_title">Bloca la pantalla</string>
|
||||
<string name="notify_lock_screen_setting_summary">Mostra notificacions a la pantalla de bloqueig</string>
|
||||
@@ -391,9 +362,4 @@
|
||||
<string name="permission_camera_request_body">Per escanejar el codi QR, Briar necessita accés a la càmera.</string>
|
||||
<string name="permission_camera_denied_body">Heu denegat l\'accés a la càmera, però l\'addició de contactes requereix utilitzar la càmera.\n\nTingueu en compte permetre l\'accés.</string>
|
||||
<string name="permission_camera_denied_toast">No s\'ha concedit el permís de la càmera</string>
|
||||
<string name="qr_code">Codi QR</string>
|
||||
<string name="show_qr_code_fullscreen">Mostra el codi QR a pantalla completa</string>
|
||||
<!--Low Memory Notification-->
|
||||
<string name="low_memory_shutdown_notification_title">Has sortit de Briar</string>
|
||||
<string name="low_memory_shutdown_notification_text">Has sortit de Briar a causa de falta de memòria.</string>
|
||||
</resources>
|
||||
|
||||
@@ -362,5 +362,4 @@
|
||||
<string name="permission_camera_request_body">Pro scan QR kódu, Briar vyžaduje přístup k fotoaparátu.</string>
|
||||
<string name="permission_camera_denied_body">Odmítli jste udělit oprávnění přístupu k fotoaparátu, avšak pro přidání kontaktů je nutné použití fotoaparátu.\n\nZvažte prosím, opětovné udělení přístupu.</string>
|
||||
<string name="permission_camera_denied_toast">Oprávnění pro přístup k fotoaparátu nebylo uděleno</string>
|
||||
<!--Low Memory Notification-->
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user