Compare commits

..

1 Commits

Author SHA1 Message Date
Torsten Grote
1aec948f18 Only show Introduction Accept Information if success is still possible 2018-04-28 11:45:10 -03:00
177 changed files with 1486 additions and 4676 deletions

View File

@@ -14,8 +14,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 10000
versionName "1.0.0"
versionCode 1700
versionName "0.17.0"
consumerProguardFiles 'proguard-rules.txt'
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api.contact;
/**
* Record types for the contact exchange protocol.
*/
public interface RecordTypes {
byte CONTACT_INFO = 0;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.api.record;
import java.io.InputStream;
public interface RecordReaderFactory {
RecordReader createRecordReader(InputStream in);
}

View File

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

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.api.record;
import java.io.OutputStream;
public interface RecordWriterFactory {
RecordWriter createRecordWriter(OutputStream out);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -89,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;
@@ -107,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();
@@ -177,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);
@@ -217,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());
@@ -1317,8 +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();
@@ -1326,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));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -190,8 +190,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 10000
versionName "1.0.0"
versionCode 1700
versionName "0.17.0"
applicationId "org.briarproject.briar.android"
resValue "string", "app_package", "org.briarproject.briar.android"
resValue "string", "app_name", "Briar"

View File

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

View File

@@ -22,14 +22,9 @@ public interface BlogManager {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.blog");
/**
* The current major version of the blog client.
* The current version of the blog client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the blog client.
*/
int MINOR_VERSION = 0;
int CLIENT_VERSION = 0;
/**
* Adds the given {@link Blog).}

View File

@@ -11,12 +11,7 @@ public interface BlogSharingManager extends SharingManager<Blog> {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.blog.sharing");
/**
* The current major version of the blog sharing client.
* The current version of the blog sharing client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the blog sharing client.
*/
int MINOR_VERSION = 0;
int CLIENT_VERSION = 0;
}

View File

@@ -16,9 +16,9 @@ public interface FeedManager {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.feed");
/**
* The current major version of the RSS feed client.
* The current version of the RSS feed client.
*/
int MAJOR_VERSION = 0;
int CLIENT_VERSION = 0;
/**
* Adds an RSS feed as a new dedicated blog.

View File

@@ -23,14 +23,9 @@ public interface ForumManager {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.forum");
/**
* The current major version of the forum client.
* The current version of the forum client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the forum client.
*/
int MINOR_VERSION = 0;
int CLIENT_VERSION = 0;
/**
* Subscribes to a forum.

View File

@@ -11,12 +11,7 @@ public interface ForumSharingManager extends SharingManager<Forum> {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.forum.sharing");
/**
* The current major version of the forum sharing client.
* The current version of the forum sharing client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the forum sharing client.
*/
int MINOR_VERSION = 0;
int CLIENT_VERSION = 0;
}

Some files were not shown because too many files have changed in this diff Show More