diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java index f07fe3ea5a18d15fc4a64a48c5907197f36160ae..28857276e85a8048a0cc38d2b12ef9519fa95c1e 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java @@ -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. */ - int PROTOCOL_VERSION = 0; + byte PROTOCOL_VERSION = 1; /** * Label for deriving Alice's header key from the master secret. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..bd24dcf67d892b5a37696f7777b500e57e8a5fbb --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java @@ -0,0 +1,9 @@ +package org.briarproject.bramble.api.contact; + +/** + * Record types for the contact exchange protocol. + */ +public interface RecordTypes { + + byte CONTACT_INFO = 0; +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/data/BdfDictionary.java b/bramble-api/src/main/java/org/briarproject/bramble/api/data/BdfDictionary.java index 927dafb64bb6e0bb64e19e5907250a8b43be0590..c17c19bca866da3abcf67aaefd0618194fdbae7b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/data/BdfDictionary.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/data/BdfDictionary.java @@ -24,9 +24,9 @@ public class BdfDictionary extends TreeMap<String, Object> { * ); * </pre> */ - public static BdfDictionary of(Entry<String, Object>... entries) { + public static BdfDictionary of(Entry<String, ?>... entries) { BdfDictionary d = new BdfDictionary(); - for (Entry<String, Object> e : entries) d.put(e.getKey(), e.getValue()); + for (Entry<String, ?> e : entries) d.put(e.getKey(), e.getValue()); return d; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/record/Record.java b/bramble-api/src/main/java/org/briarproject/bramble/api/record/Record.java new file mode 100644 index 0000000000000000000000000000000000000000..fedb7212c2ad75dd35fea5b7e1799a2b81022f67 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/record/Record.java @@ -0,0 +1,36 @@ +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; + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordReader.java b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordReader.java new file mode 100644 index 0000000000000000000000000000000000000000..371dead2025f40a1dc5e4fc8f7f6b15d5f76cc45 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordReader.java @@ -0,0 +1,20 @@ +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; +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordReaderFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordReaderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..17588eaed2d17871b86b8f15de8b7847a97b01d7 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordReaderFactory.java @@ -0,0 +1,8 @@ +package org.briarproject.bramble.api.record; + +import java.io.InputStream; + +public interface RecordReaderFactory { + + RecordReader createRecordReader(InputStream in); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..eb83d4d410ae358e2978f8557980cc82600b0a57 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordWriter.java @@ -0,0 +1,15 @@ +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; +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordWriterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordWriterFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2713bb4febad77a8363ca2286782b2b44d457547 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/record/RecordWriterFactory.java @@ -0,0 +1,8 @@ +package org.briarproject.bramble.api.record; + +import java.io.OutputStream; + +public interface RecordWriterFactory { + + RecordWriter createRecordWriter(OutputStream out); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java index e85b15d7df3426298fd5139bf05d12041a4da2b7..a31e7737000a80d1511c5e9e57fa3bb1c5880bcd 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java @@ -7,5 +7,7 @@ public interface MessageFactory { Message createMessage(GroupId g, long timestamp, byte[] body); + Message createMessage(byte[] raw); + Message createMessage(MessageId m, byte[] raw); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java index 5f196f147060a08fee698f2e4747c6f0609dc074..a6e3474f30453b24d06b0b4aaeeb72a96d0a89bd 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java @@ -2,6 +2,8 @@ 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 { /** @@ -9,16 +11,6 @@ public interface SyncConstants { */ byte PROTOCOL_VERSION = 0; - /** - * 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. */ @@ -42,5 +34,5 @@ public interface SyncConstants { /** * The maximum number of message IDs in an ack, offer or request record. */ - int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH; + int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReader.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java similarity index 93% rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReader.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java index f29169085979168d15e99968b5f756f1f9eea378..374a712087d3f1fcbd4a4dd84669b3e567fb02aa 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReader.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java @@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.IOException; @NotNullByDefault -public interface RecordReader { +public interface SyncRecordReader { boolean eof() throws IOException; diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReaderFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReaderFactory.java similarity index 62% rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReaderFactory.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReaderFactory.java index a1488491949bd0af8753c087bc121549d6c9da07..1f66bdbadcfb2ebfe2d7376461740096485bfa64 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReaderFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReaderFactory.java @@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.InputStream; @NotNullByDefault -public interface RecordReaderFactory { +public interface SyncRecordReaderFactory { - RecordReader createRecordReader(InputStream in); + SyncRecordReader createRecordReader(InputStream in); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java similarity index 91% rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriter.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java index 2c6abfa789710539f30fd3b611a46c2382dd3aeb..7a091544a9e38ccec5dd13e846d35497a55e43f9 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriter.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java @@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.IOException; @NotNullByDefault -public interface RecordWriter { +public interface SyncRecordWriter { void writeAck(Ack a) throws IOException; diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriterFactory.java similarity index 61% rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriterFactory.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriterFactory.java index de8606d42b8e4e5b07353cc45c7a9e92f4593f19..32c7c0c36c7a8f9b7b83631d7a9f98d5d16f509d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriterFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriterFactory.java @@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.OutputStream; @NotNullByDefault -public interface RecordWriterFactory { +public interface SyncRecordWriterFactory { - RecordWriter createRecordWriter(OutputStream out); + SyncRecordWriter createRecordWriter(OutputStream out); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index b92329b187ea87e63ae703849c4de08f8d255a35..f4ece8bd4903288608ac5b8cf3594de90bfa5340 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -13,6 +13,7 @@ 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; @@ -38,6 +39,7 @@ import dagger.Module; LifecycleModule.class, PluginModule.class, PropertiesModule.class, + RecordModule.class, ReliabilityModule.class, ReportingModule.class, SettingsModule.class, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java index 6fdcc7aaddfadef8883fb0c6450d98894ac67d46..b2f552d088da6531dd397e407254c3a09bff8628 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java @@ -1,23 +1,20 @@ 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; @@ -26,30 +23,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.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.contact.RecordTypes.CONTACT_INFO; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; -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; +import static org.briarproject.bramble.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -62,9 +59,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { "org.briarproject.briar.contact/EXCHANGE"; private final DatabaseComponent db; - private final AuthorFactory authorFactory; - private final BdfReaderFactory bdfReaderFactory; - private final BdfWriterFactory bdfWriterFactory; + private final ClientHelper clientHelper; + private final RecordReaderFactory recordReaderFactory; + private final RecordWriterFactory recordWriterFactory; private final Clock clock; private final ConnectionManager connectionManager; private final ContactManager contactManager; @@ -81,17 +78,17 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { private volatile boolean alice; @Inject - ContactExchangeTaskImpl(DatabaseComponent db, - AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory, - BdfWriterFactory bdfWriterFactory, Clock clock, + ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper, + RecordReaderFactory recordReaderFactory, + RecordWriterFactory recordWriterFactory, Clock clock, ConnectionManager connectionManager, ContactManager contactManager, TransportPropertyManager transportPropertyManager, CryptoComponent crypto, StreamReaderFactory streamReaderFactory, StreamWriterFactory streamWriterFactory) { this.db = db; - this.authorFactory = authorFactory; - this.bdfReaderFactory = bdfReaderFactory; - this.bdfWriterFactory = bdfWriterFactory; + this.clientHelper = clientHelper; + this.recordReaderFactory = recordReaderFactory; + this.recordWriterFactory = recordWriterFactory; this.clock = clock; this.connectionManager = connectionManager; this.contactManager = contactManager; @@ -126,18 +123,18 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); listener.contactExchangeFailed(); - tryToClose(conn, true); + tryToClose(conn); return; } // Get the local transport properties - Map<TransportId, TransportProperties> localProperties, remoteProperties; + Map<TransportId, TransportProperties> localProperties; try { localProperties = transportPropertyManager.getLocalProperties(); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); listener.contactExchangeFailed(); - tryToClose(conn, true); + tryToClose(conn); return; } @@ -151,159 +148,138 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { InputStream streamReader = streamReaderFactory.createContactExchangeStreamReader(in, alice ? bobHeaderKey : aliceHeaderKey); - BdfReader r = bdfReaderFactory.createReader(streamReader); + RecordReader recordReader = + recordReaderFactory.createRecordReader(streamReader); + // Create the writers OutputStream streamWriter = streamWriterFactory.createContactExchangeStreamWriter(out, alice ? aliceHeaderKey : bobHeaderKey); - BdfWriter w = bdfWriterFactory.createWriter(streamWriter); + RecordWriter recordWriter = + recordWriterFactory.createRecordWriter(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 pseudonyms, signed nonces, and timestamps + // Exchange contact info long localTimestamp = clock.currentTimeMillis(); - Author remoteAuthor; - long remoteTimestamp; + ContactInfo remoteInfo; try { if (alice) { - sendPseudonym(w, aliceNonce); - sendTimestamp(w, localTimestamp); - sendTransportProperties(w, localProperties); - w.flush(); - remoteAuthor = receivePseudonym(r, bobNonce); - remoteTimestamp = receiveTimestamp(r); - remoteProperties = receiveTransportProperties(r); + sendContactInfo(recordWriter, localAuthor, localProperties, + localSignature, localTimestamp); + recordWriter.flush(); + remoteInfo = receiveContactInfo(recordReader); } else { - remoteAuthor = receivePseudonym(r, aliceNonce); - remoteTimestamp = receiveTimestamp(r); - remoteProperties = receiveTransportProperties(r); - sendPseudonym(w, bobNonce); - sendTimestamp(w, localTimestamp); - sendTransportProperties(w, localProperties); - w.flush(); + remoteInfo = receiveContactInfo(recordReader); + sendContactInfo(recordWriter, localAuthor, localProperties, + localSignature, localTimestamp); + recordWriter.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"); } - // 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) { + } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); listener.contactExchangeFailed(); - tryToClose(conn, true); + tryToClose(conn); + return; + } + + // Verify the contact's signature + if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) { + LOG.warning("Invalid signature"); + listener.contactExchangeFailed(); + tryToClose(conn); return; } // The agreed timestamp is the minimum of the peers' timestamps - long timestamp = Math.min(localTimestamp, remoteTimestamp); + long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); try { // Add the contact - ContactId contactId = addContact(remoteAuthor, timestamp, - remoteProperties); + ContactId contactId = addContact(remoteInfo.author, timestamp, + remoteInfo.properties); // Reuse the connection as a transport connection connectionManager.manageOutgoingConnection(contactId, transportId, conn); // Pseudonym exchange succeeded LOG.info("Pseudonym exchange succeeded"); - listener.contactExchangeSucceeded(remoteAuthor); + listener.contactExchangeSucceeded(remoteInfo.author); } catch (ContactExistsException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - tryToClose(conn, true); - listener.duplicateContact(remoteAuthor); + tryToClose(conn); + listener.duplicateContact(remoteInfo.author); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - tryToClose(conn, true); + tryToClose(conn); listener.contactExchangeFailed(); } } - 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 byte[] sign(LocalAuthor author, byte[] nonce) { + try { + return crypto.sign(SIGNING_LABEL_EXCHANGE, nonce, + author.getPrivateKey()); + } catch (GeneralSecurityException e) { + throw new AssertionError(); + } } - 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(); + 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; } - return authorFactory.createAuthor(formatVersion, name, publicKey); } - private void sendTimestamp(BdfWriter w, long timestamp) - throws IOException { - w.writeLong(timestamp); - LOG.info("Sent timestamp"); + 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 long receiveTimestamp(BdfReader r) throws IOException { - long timestamp = r.readLong(); + private ContactInfo receiveContactInfo(RecordReader recordReader) + 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); if (timestamp < 0) throw new FormatException(); - 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; + return new ContactInfo(author, properties, signature, timestamp); } private ContactId addContact(Author remoteAuthor, long timestamp, @@ -324,13 +300,30 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { return contactId; } - private void tryToClose(DuplexTransportConnection conn, boolean exception) { + private void tryToClose(DuplexTransportConnection conn) { try { LOG.info("Closing connection"); - conn.getReader().dispose(exception, true); - conn.getWriter().dispose(exception); + conn.getReader().dispose(true, true); + conn.getWriter().dispose(true); } 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; + } + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java index 079b9225317c4a8107d735685f0b02cbabdfe891..0c24f1c9f69d99105ba0255feefb7fc0a1ddadfe 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java @@ -13,6 +13,8 @@ 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; @@ -44,6 +46,8 @@ 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<>(); @@ -54,11 +58,15 @@ class KeyAgreementConnector { KeyAgreementConnector(Callbacks callbacks, KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager, - ConnectionChooser connectionChooser) { + ConnectionChooser connectionChooser, + RecordReaderFactory recordReaderFactory, + RecordWriterFactory recordWriterFactory) { this.callbacks = callbacks; this.keyAgreementCrypto = keyAgreementCrypto; this.pluginManager = pluginManager; this.connectionChooser = connectionChooser; + this.recordReaderFactory = recordReaderFactory; + this.recordWriterFactory = recordWriterFactory; } Payload listen(KeyPair localKeyPair) { @@ -119,7 +127,8 @@ class KeyAgreementConnector { KeyAgreementConnection chosen = connectionChooser.poll(CONNECTION_TIMEOUT); if (chosen == null) return null; - return new KeyAgreementTransport(chosen); + return new KeyAgreementTransport(recordReaderFactory, + recordWriterFactory, chosen); } catch (InterruptedException e) { LOG.info("Interrupted while waiting for connection"); Thread.currentThread().interrupt(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java index 1b3d9abea84d7237e58bb09a51ccf04e07182537..b5fc7ef7c0fcd091b0e44f3b818481c93c2d4400 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java @@ -19,6 +19,8 @@ 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; @@ -49,14 +51,17 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask, KeyAgreementTaskImpl(CryptoComponent crypto, KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus, PayloadEncoder payloadEncoder, PluginManager pluginManager, - ConnectionChooser connectionChooser) { + ConnectionChooser connectionChooser, + RecordReaderFactory recordReaderFactory, + RecordWriterFactory recordWriterFactory) { this.crypto = crypto; this.keyAgreementCrypto = keyAgreementCrypto; this.eventBus = eventBus; this.payloadEncoder = payloadEncoder; localKeyPair = crypto.generateAgreementKeyPair(); connector = new KeyAgreementConnector(this, keyAgreementCrypto, - pluginManager, connectionChooser); + pluginManager, connectionChooser, recordReaderFactory, + recordWriterFactory); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java index 5a58743ff57a029c8d440e0b951323da01ba1796..c545fbd69e8c6662e2edc16c73edefa5e12fc16f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java @@ -4,9 +4,12 @@ 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.util.ByteUtils; +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 java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,8 +17,6 @@ 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; @@ -30,14 +31,17 @@ class KeyAgreementTransport { Logger.getLogger(KeyAgreementTransport.class.getName()); private final KeyAgreementConnection kac; - private final InputStream in; - private final OutputStream out; + private final RecordReader reader; + private final RecordWriter writer; - KeyAgreementTransport(KeyAgreementConnection kac) + KeyAgreementTransport(RecordReaderFactory recordReaderFactory, + RecordWriterFactory recordWriterFactory, KeyAgreementConnection kac) throws IOException { this.kac = kac; - in = kac.getConnection().getReader().getInputStream(); - out = kac.getConnection().getWriter().getOutputStream(); + InputStream in = kac.getConnection().getReader().getInputStream(); + reader = recordReaderFactory.createRecordReader(in); + OutputStream out = kac.getConnection().getWriter().getOutputStream(); + writer = recordWriterFactory.createRecordWriter(out); } public DuplexTransportConnection getConnection() { @@ -74,9 +78,8 @@ class KeyAgreementTransport { tryToClose(exception); } - public void tryToClose(boolean exception) { + private void tryToClose(boolean exception) { try { - LOG.info("Closing connection"); kac.getConnection().getReader().dispose(exception, true); kac.getConnection().getWriter().dispose(exception); } catch (IOException e) { @@ -85,59 +88,27 @@ class KeyAgreementTransport { } private void writeRecord(byte type, byte[] payload) throws IOException { - 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(); + writer.writeRecord(new Record(PROTOCOL_VERSION, type, payload)); + writer.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 { - readData(len); + 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 } 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; - } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/record/RecordModule.java b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordModule.java new file mode 100644 index 0000000000000000000000000000000000000000..54dd4587c3285be5f575051faf89fc6af5aa1212 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordModule.java @@ -0,0 +1,21 @@ +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(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/record/RecordReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordReaderFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f8b89d8883a5cb601627dc4df4c790791a9803ce --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordReaderFactoryImpl.java @@ -0,0 +1,14 @@ +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); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/record/RecordReaderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordReaderImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..12aac514ca3b67d68622eef507e2b9985a6bd115 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordReaderImpl.java @@ -0,0 +1,46 @@ +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(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/record/RecordWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordWriterFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a20742423478c335e1708acdccdf54ad4bf16cc2 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordWriterFactoryImpl.java @@ -0,0 +1,14 @@ +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); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/record/RecordWriterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordWriterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ec9f5c0c026746652c537838f47db7fa4451b614 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/record/RecordWriterImpl.java @@ -0,0 +1,45 @@ +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(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java index 80f699a1f9e6ebdb134ae0835541018992c6c406..c2b54f5a9722fe2e8378f0421df6f45e16a7a805 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java @@ -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 RecordWriter recordWriter; + private final SyncRecordWriter 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, RecordWriter recordWriter) { + int maxIdleTime, SyncRecordWriter 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_LENGTH, maxLatency); + MAX_RECORD_PAYLOAD_BYTES, maxLatency); setNextSendTime(db.getNextSendTime(txn, contactId)); db.commitTransaction(txn); } finally { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java index 5a46a4aad5287d4e896778834681b703ae0336aa..0e34cf6b4db4005ce63088346d7e0b036be42346 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java @@ -20,6 +20,9 @@ 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,7 +34,7 @@ class GroupFactoryImpl implements GroupFactory { 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, new byte[] {FORMAT_VERSION}, + byte[] hash = crypto.hash(LABEL, FORMAT_VERSION_BYTES, StringUtils.toUtf8(c.getString()), majorVersionBytes, descriptor); return new Group(new GroupId(hash), c, majorVersion, descriptor); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java index 32eb58df4854d87c080671e913926e7dd3a2e925..2605764b258524f870e09333292538e284bba0c2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java @@ -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 RecordReader recordReader; + private final SyncRecordReader recordReader; private volatile boolean interrupted = false; IncomingSession(DatabaseComponent db, Executor dbExecutor, EventBus eventBus, ContactId contactId, - RecordReader recordReader) { + SyncRecordReader recordReader) { this.db = db; this.dbExecutor = dbExecutor; this.eventBus = eventBus; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java index 7f92045fcbbf6984308bc0246ddd529e79d26827..754f0edac58ed70f648ac2983ae70e21e35bf051 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java @@ -16,6 +16,7 @@ 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; @@ -23,6 +24,9 @@ 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 @@ -34,14 +38,7 @@ class MessageFactoryImpl implements MessageFactory { public Message createMessage(GroupId g, long timestamp, byte[] body) { if (body.length > MAX_MESSAGE_BODY_LENGTH) throw new IllegalArgumentException(); - 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); + MessageId id = getMessageId(g, timestamp, body); byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length]; System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH); ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH); @@ -49,10 +46,38 @@ 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); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderFactoryImpl.java deleted file mode 100644 index fb43f9459c5625b36f367989cdfe1f4a6d9df11b..0000000000000000000000000000000000000000 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderFactoryImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -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); - } -} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterFactoryImpl.java deleted file mode 100644 index 1bcbde17f7688b3569c5734d926a9e671254d5ee..0000000000000000000000000000000000000000 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterFactoryImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -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); - } -} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java index b1f8198f86dcb6b0d771527876de0ce9261ab057..b0645dc5125880882a7777cbfb188c23deb364a5 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java @@ -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.RecordWriter; +import org.briarproject.bramble.api.sync.SyncRecordWriter; 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 RecordWriter recordWriter; + private final SyncRecordWriter 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, RecordWriter recordWriter) { + int maxLatency, SyncRecordWriter 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_LENGTH, maxLatency); + MAX_RECORD_PAYLOAD_BYTES, maxLatency); db.commitTransaction(txn); } finally { db.endTransaction(txn); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java index ca6ec897af315954beb53279a1a7c8df794808a3..0034289e7ddfcf73bcf6291264c8557079cceec2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java @@ -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.RecordReaderFactory; -import org.briarproject.bramble.api.sync.RecordWriterFactory; +import org.briarproject.bramble.api.sync.SyncRecordReaderFactory; +import org.briarproject.bramble.api.sync.SyncRecordWriterFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; @@ -52,22 +52,23 @@ public class SyncModule { } @Provides - RecordReaderFactory provideRecordReaderFactory( - RecordReaderFactoryImpl recordReaderFactory) { + SyncRecordReaderFactory provideRecordReaderFactory( + SyncRecordReaderFactoryImpl recordReaderFactory) { return recordReaderFactory; } @Provides - RecordWriterFactory provideRecordWriterFactory() { - return new RecordWriterFactoryImpl(); + SyncRecordWriterFactory provideRecordWriterFactory( + SyncRecordWriterFactoryImpl recordWriterFactory) { + return recordWriterFactory; } @Provides @Singleton SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db, @DatabaseExecutor Executor dbExecutor, EventBus eventBus, - Clock clock, RecordReaderFactory recordReaderFactory, - RecordWriterFactory recordWriterFactory) { + Clock clock, SyncRecordReaderFactory recordReaderFactory, + SyncRecordWriterFactory recordWriterFactory) { return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock, recordReaderFactory, recordWriterFactory); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7124f8810f20498c6af3273effe1707a7b1bde7e --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderFactoryImpl.java @@ -0,0 +1,34 @@ +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); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java similarity index 54% rename from bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderImpl.java rename to bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java index a9663dc0c232b7f9459f0ceefa0ed8f7b8795f0a..3a7c6d057bcf21b85fdde144626576ab3d4345cd 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java @@ -3,82 +3,56 @@ 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 RecordReaderImpl implements RecordReader { - - private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF} +class SyncRecordReaderImpl implements SyncRecordReader { private final MessageFactory messageFactory; - private final InputStream in; - private final byte[] header, payload; + private final RecordReader reader; - private State state = State.BUFFER_EMPTY; - private int payloadLength = 0; + @Nullable + private Record nextRecord = null; + private boolean eof = false; - RecordReaderImpl(MessageFactory messageFactory, InputStream in) { + SyncRecordReaderImpl(MessageFactory messageFactory, RecordReader reader) { this.messageFactory = messageFactory; - this.in = in; - header = new byte[RECORD_HEADER_LENGTH]; - payload = new byte[MAX_RECORD_PAYLOAD_LENGTH]; + this.reader = reader; } private void readRecord() throws IOException { - if (state != State.BUFFER_EMPTY) throw new IllegalStateException(); + assert nextRecord == null; while (true) { - // 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); + nextRecord = reader.readRecord(); // Check the protocol version + byte version = nextRecord.getProtocolVersion(); if (version != PROTOCOL_VERSION) throw new FormatException(); - // 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; + byte type = nextRecord.getRecordType(); // Return if this is a known record type, otherwise continue if (type == ACK || type == MESSAGE || type == OFFER || type == REQUEST) { @@ -87,6 +61,11 @@ class RecordReaderImpl implements RecordReader { } } + 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. @@ -97,14 +76,21 @@ class RecordReaderImpl implements RecordReader { */ @Override public boolean eof() throws IOException { - if (state == State.BUFFER_EMPTY) readRecord(); - if (state == State.BUFFER_EMPTY) throw new IllegalStateException(); - return state == State.EOF; + if (nextRecord != null) return false; + if (eof) return true; + try { + readRecord(); + return false; + } catch (EOFException e) { + nextRecord = null; + eof = true; + return true; + } } @Override public boolean hasAck() throws IOException { - return !eof() && header[1] == ACK; + return !eof() && getNextRecordType() == ACK; } @Override @@ -114,45 +100,41 @@ class RecordReaderImpl implements RecordReader { } private List<MessageId> readMessageIds() throws IOException { - 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) { + 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) { byte[] id = new byte[UniqueId.LENGTH]; System.arraycopy(payload, off, id, 0, UniqueId.LENGTH); ids.add(new MessageId(id)); } - state = State.BUFFER_EMPTY; + nextRecord = null; return ids; } @Override public boolean hasMessage() throws IOException { - return !eof() && header[1] == MESSAGE; + return !eof() && getNextRecordType() == MESSAGE; } @Override public Message readMessage() throws IOException { if (!hasMessage()) throw new FormatException(); - 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 + assert nextRecord != null; + byte[] payload = nextRecord.getPayload(); + if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException(); + // Validate timestamp long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH); if (timestamp < 0) throw new FormatException(); - // 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); + nextRecord = null; + return messageFactory.createMessage(payload); } @Override public boolean hasOffer() throws IOException { - return !eof() && header[1] == OFFER; + return !eof() && getNextRecordType() == OFFER; } @Override @@ -163,7 +145,7 @@ class RecordReaderImpl implements RecordReader { @Override public boolean hasRequest() throws IOException { - return !eof() && header[1] == REQUEST; + return !eof() && getNextRecordType() == REQUEST; } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0d6a762863e3b75e0a2b28525765e75fe2c4ae18 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterFactoryImpl.java @@ -0,0 +1,28 @@ +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); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java similarity index 55% rename from bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterImpl.java rename to bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java index f7bf9f5fd8e7e557f8d844eb37ea1b4a638816c6..d5e59f78dfbd03d439080e7499ad611212cbf394 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java @@ -1,81 +1,67 @@ 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.util.ByteUtils; +import org.briarproject.bramble.api.sync.SyncRecordWriter; 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 RecordWriterImpl implements RecordWriter { +class SyncRecordWriterImpl implements SyncRecordWriter { - private final OutputStream out; - private final byte[] header; - private final ByteArrayOutputStream payload; + private final RecordWriter writer; + private final ByteArrayOutputStream payload = new ByteArrayOutputStream(); - RecordWriterImpl(OutputStream out) { - this.out = out; - header = new byte[RECORD_HEADER_LENGTH]; - header[0] = PROTOCOL_VERSION; - payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH); + SyncRecordWriterImpl(RecordWriter writer) { + this.writer = writer; } private void writeRecord(byte recordType) throws IOException { - header[1] = recordType; - ByteUtils.writeUint16(payload.size(), header, 2); - out.write(header); - payload.writeTo(out); + writer.writeRecord(new Record(PROTOCOL_VERSION, recordType, + payload.toByteArray())); 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 { - header[1] = RecordTypes.MESSAGE; - ByteUtils.writeUint16(raw.length, header, 2); - out.write(header); - out.write(raw); + writer.writeRecord(new Record(PROTOCOL_VERSION, MESSAGE, 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 { - out.flush(); + writer.flush(); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java index 277a21dcde1ec78aa0db2cbc8a71a950f31bdb96..8459f6eaf975143dde4d7a3e1d34504e75b2a093 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java @@ -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.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.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.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 RecordReaderFactory recordReaderFactory; - private final RecordWriterFactory recordWriterFactory; + private final SyncRecordReaderFactory recordReaderFactory; + private final SyncRecordWriterFactory recordWriterFactory; @Inject SyncSessionFactoryImpl(DatabaseComponent db, @DatabaseExecutor Executor dbExecutor, EventBus eventBus, - Clock clock, RecordReaderFactory recordReaderFactory, - RecordWriterFactory recordWriterFactory) { + Clock clock, SyncRecordReaderFactory recordReaderFactory, + SyncRecordWriterFactory recordWriterFactory) { this.db = db; this.dbExecutor = dbExecutor; this.eventBus = eventBus; @@ -46,14 +46,16 @@ class SyncSessionFactoryImpl implements SyncSessionFactory { @Override public SyncSession createIncomingSession(ContactId c, InputStream in) { - RecordReader recordReader = recordReaderFactory.createRecordReader(in); + SyncRecordReader recordReader = + recordReaderFactory.createRecordReader(in); return new IncomingSession(db, dbExecutor, eventBus, c, recordReader); } @Override public SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency, OutputStream out) { - RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out); + SyncRecordWriter recordWriter = + recordWriterFactory.createRecordWriter(out); return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, maxLatency, recordWriter); } @@ -61,7 +63,8 @@ class SyncSessionFactoryImpl implements SyncSessionFactory { @Override public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency, int maxIdleTime, OutputStream out) { - RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out); + SyncRecordWriter recordWriter = + recordWriterFactory.createRecordWriter(out); return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, maxLatency, maxIdleTime, recordWriter); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementTransportTest.java b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementTransportTest.java index b42fbbd25442572cecb1bbcf3cacff9ba16dd64f..cddb5b40ec99f57b350c4ec89ef888554ac748a3 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementTransportTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementTransportTest.java @@ -5,23 +5,31 @@ 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.TestUtils; -import org.briarproject.bramble.util.ByteUtils; +import org.briarproject.bramble.test.CaptureArgumentAction; import org.jmock.Expectations; +import org.jmock.lib.legacy.ClassImposteriser; import org.junit.Test; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicReference; 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 { @@ -31,222 +39,268 @@ 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 ByteArrayInputStream inputStream; - private ByteArrayOutputStream outputStream; + private final InputStream inputStream; + private final OutputStream 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 { - setup(new byte[0]); - byte[] key = TestUtils.getRandomBytes(123); + byte[] key = getRandomBytes(123); + + setup(); + AtomicReference<Record> written = expectWriteRecord(); + kat.sendKey(key); - assertRecordSent(KEY, key); + assertNotNull(written.get()); + assertRecordEquals(PROTOCOL_VERSION, KEY, key, written.get()); } @Test public void testSendConfirm() throws Exception { - setup(new byte[0]); - byte[] confirm = TestUtils.getRandomBytes(123); + byte[] confirm = getRandomBytes(123); + + setup(); + AtomicReference<Record> written = expectWriteRecord(); + kat.sendConfirm(confirm); - assertRecordSent(CONFIRM, confirm); + assertNotNull(written.get()); + assertRecordEquals(PROTOCOL_VERSION, CONFIRM, confirm, written.get()); } @Test public void testSendAbortWithException() throws Exception { - setup(new byte[0]); + setup(); + AtomicReference<Record> written = expectWriteRecord(); context.checking(new Expectations() {{ oneOf(transportConnectionReader).dispose(true, true); oneOf(transportConnectionWriter).dispose(true); }}); + kat.sendAbort(true); - assertRecordSent(ABORT, new byte[0]); + assertNotNull(written.get()); + assertRecordEquals(PROTOCOL_VERSION, ABORT, new byte[0], written.get()); } @Test public void testSendAbortWithoutException() throws Exception { - setup(new byte[0]); + setup(); + AtomicReference<Record> written = expectWriteRecord(); context.checking(new Expectations() {{ oneOf(transportConnectionReader).dispose(false, true); oneOf(transportConnectionWriter).dispose(false); }}); + kat.sendAbort(false); - assertRecordSent(ABORT, new byte[0]); + assertNotNull(written.get()); + assertRecordEquals(PROTOCOL_VERSION, ABORT, new byte[0], written.get()); } @Test(expected = AbortException.class) public void testReceiveKeyThrowsExceptionIfAtEndOfStream() throws Exception { - setup(new byte[0]); - kat.receiveKey(); - } + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(throwException(new EOFException())); + }}); - @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 { - setup(createRecord((byte) (PROTOCOL_VERSION + 1), KEY, new byte[123])); + 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))); + }}); + kat.receiveKey(); } @Test(expected = AbortException.class) public void testReceiveKeyThrowsExceptionIfAbortIsReceived() throws Exception { - setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0])); + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(returnValue(new Record(PROTOCOL_VERSION, ABORT, new byte[0]))); + }}); + kat.receiveKey(); } @Test(expected = AbortException.class) public void testReceiveKeyThrowsExceptionIfConfirmIsReceived() throws Exception { - setup(createRecord(PROTOCOL_VERSION, CONFIRM, new byte[123])); + byte[] confirm = getRandomBytes(123); + + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(returnValue(new Record(PROTOCOL_VERSION, CONFIRM, confirm))); + }}); + kat.receiveKey(); } @Test public void testReceiveKeySkipsUnrecognisedRecordTypes() throws Exception { - 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()); - } + 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); - @Test(expected = AbortException.class) - public void testReceiveConfirmThrowsExceptionIfAtEndOfStream() - throws Exception { - setup(new byte[0]); - kat.receiveConfirm(); - } + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(returnValue(unknownRecord1)); + oneOf(recordReader).readRecord(); + will(returnValue(unknownRecord2)); + oneOf(recordReader).readRecord(); + will(returnValue(keyRecord)); + }}); - @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(); + assertArrayEquals(key, kat.receiveKey()); } @Test(expected = AbortException.class) - public void testReceiveConfirmThrowsExceptionIfPayloadIsTooShort() + public void testReceiveConfirmThrowsExceptionIfAtEndOfStream() 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); + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(throwException(new EOFException())); + }}); + kat.receiveConfirm(); } @Test(expected = AbortException.class) public void testReceiveConfirmThrowsExceptionIfProtocolVersionIsUnrecognised() throws Exception { - setup(createRecord((byte) (PROTOCOL_VERSION + 1), CONFIRM, - new byte[123])); + 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))); + }}); + kat.receiveConfirm(); } @Test(expected = AbortException.class) public void testReceiveConfirmThrowsExceptionIfAbortIsReceived() throws Exception { - setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0])); + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(returnValue(new Record(PROTOCOL_VERSION, ABORT, new byte[0]))); + }}); + kat.receiveConfirm(); } @Test(expected = AbortException.class) - public void testReceiveKeyThrowsExceptionIfKeyIsReceived() + public void testReceiveConfirmThrowsExceptionIfKeyIsReceived() throws Exception { - setup(createRecord(PROTOCOL_VERSION, KEY, new byte[123])); + byte[] key = getRandomBytes(123); + + setup(); + context.checking(new Expectations() {{ + oneOf(recordReader).readRecord(); + will(returnValue(new Record(PROTOCOL_VERSION, KEY, key))); + }}); + kat.receiveConfirm(); } @Test public void testReceiveConfirmSkipsUnrecognisedRecordTypes() throws Exception { - 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(byte[] input) throws Exception { - inputStream = new ByteArrayInputStream(input); - outputStream = new ByteArrayOutputStream(); + 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()); + } + + private void setup() throws Exception { 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); + } + + 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(); }}); - kat = new KeyAgreementTransport(keyAgreementConnection); - } - - 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 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; + return captured; + } + + private void assertRecordEquals(byte expectedVersion, byte expectedType, + byte[] expectedPayload, Record actual) { + assertEquals(expectedVersion, actual.getProtocolVersion()); + assertEquals(expectedType, actual.getRecordType()); + assertArrayEquals(expectedPayload, actual.getPayload()); } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/record/RecordReaderImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/record/RecordReaderImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..26ef89c8ee8e3a190606713c0b8d38cb26359048 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/record/RecordReaderImplTest.java @@ -0,0 +1,102 @@ +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(); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/record/RecordWriterImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/record/RecordWriterImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2e5f236befaaeaadda8b810a6fc00335ee1df480 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/record/RecordWriterImplTest.java @@ -0,0 +1,49 @@ +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); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/RecordReaderImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/RecordReaderImplTest.java deleted file mode 100644 index 7b927cf59b3aad4211f1c8e00cdcfd746f253ba6..0000000000000000000000000000000000000000 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/RecordReaderImplTest.java +++ /dev/null @@ -1,219 +0,0 @@ -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(); - } -} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java index 92f030052fd6e37578e32318ff1e02dea6669bfe..3e001dae86a05af999b35eea36325b32b2286148 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java @@ -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.RecordWriter; +import org.briarproject.bramble.api.sync.SyncRecordWriter; 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 RecordWriter recordWriter; + private final SyncRecordWriter recordWriter; public SimplexOutgoingSessionTest() { context = new Mockery(); db = context.mock(DatabaseComponent.class); dbExecutor = new ImmediateExecutor(); eventBus = context.mock(EventBus.class); - recordWriter = context.mock(RecordWriter.class); + recordWriter = context.mock(SyncRecordWriter.class); contactId = new ContactId(234); messageId = new MessageId(TestUtils.getRandomId()); maxLatency = Integer.MAX_VALUE; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java index 1ceedbb20e3102ca52db0812ca866546acd44bce..1504da9eb48cfcd038d724c04c4fa5398e8d954e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java @@ -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 - RecordReaderFactory recordReaderFactory; + SyncRecordReaderFactory recordReaderFactory; @Inject - RecordWriterFactory recordWriterFactory; + SyncRecordWriterFactory recordWriterFactory; @Inject TransportCrypto transportCrypto; @@ -104,7 +104,7 @@ public class SyncIntegrationTest extends BrambleTestCase { headerKey, streamNumber); OutputStream streamWriter = streamWriterFactory.createStreamWriter(out, ctx); - RecordWriter recordWriter = recordWriterFactory.createRecordWriter( + SyncRecordWriter 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); - RecordReader recordReader = recordReaderFactory.createRecordReader( + SyncRecordReader recordReader = recordReaderFactory.createRecordReader( streamReader); // Read the ack diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java index a714520dd5df088f882a160d33b3d7c6a48be117..0397f549fd72b8291396c3d4a0d8f7d03212b2d5 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java @@ -1,6 +1,7 @@ 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; @@ -13,6 +14,7 @@ import dagger.Component; @Component(modules = { TestSecureRandomModule.class, CryptoModule.class, + RecordModule.class, SyncModule.class, SystemModule.class, TransportModule.class diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncRecordReaderImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncRecordReaderImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ccd1207ffa0e2c60457fea5fccbb4da97c37e13e --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncRecordReaderImplTest.java @@ -0,0 +1,195 @@ +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(); + } +} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java index 160d80e534c9d3b234835ad0f08b8293c4724056..20a59f21a1f4cb0a0aea7a4f82c00025da6e869d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.properties.PropertiesModule; +import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestDatabaseModule; @@ -52,6 +53,7 @@ import dagger.Component; MessagingModule.class, PrivateGroupModule.class, PropertiesModule.class, + RecordModule.class, SharingModule.class, SyncModule.class, SystemModule.class, diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java index c5a1f1851cb99e3acf668e9d400cd6d031e465c1..9e828d722ddae2caa5dd137c800057f4e81dc90c 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java @@ -26,7 +26,7 @@ import javax.inject.Inject; 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.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH; +import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH; @@ -63,7 +63,7 @@ public class MessageSizeIntegrationTest extends BriarTestCase { int length = message.getMessage().getRaw().length; assertTrue( length > UniqueId.LENGTH + 8 + MAX_PRIVATE_MESSAGE_BODY_LENGTH); - assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH); + assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES); } @Test @@ -87,7 +87,7 @@ public class MessageSizeIntegrationTest extends BriarTestCase { assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH + 4 + MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH + MAX_FORUM_POST_BODY_LENGTH); - assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH); + assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES); } private static void injectEagerSingletons( diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java index f3d6f039977361459b26333f5ade3d6538db3657..7dade1ed198f8223971bf55255ed03ecb2a79ff7 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java @@ -16,6 +16,7 @@ import org.briarproject.bramble.db.DatabaseModule; import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; +import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestCryptoExecutorModule; @@ -48,6 +49,7 @@ import dagger.Component; IdentityModule.class, LifecycleModule.class, MessagingModule.class, + RecordModule.class, SyncModule.class, SystemModule.class, TransportModule.class, diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index 131f92ea8b8b910ab7f49ca9f00466900fd9c15c..9d86e58d97f841cf01cd1339f8749bc71eeb448f 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -19,6 +19,7 @@ import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.properties.PropertiesModule; +import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestDatabaseModule; @@ -73,6 +74,7 @@ import dagger.Component; MessagingModule.class, PrivateGroupModule.class, PropertiesModule.class, + RecordModule.class, SharingModule.class, SyncModule.class, SystemModule.class,