diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java index c8d08bdc4..168b4c9ef 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java @@ -9,5 +9,5 @@ public interface RecordTypes { byte MESSAGE = 1; byte OFFER = 2; byte REQUEST = 3; - + byte VERSIONS = 4; } 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 a6e3474f3..422a15117 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 @@ -35,4 +35,10 @@ public interface SyncConstants { * The maximum number of message IDs in an ack, offer or request record. */ int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH; + + /** + * The maximum number of versions of the sync protocol a peer may support + * simultaneously. + */ + int MAX_SUPPORTED_VERSIONS = 10; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java index 374a71208..55650e407 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordReader.java @@ -25,4 +25,7 @@ public interface SyncRecordReader { Request readRequest() throws IOException; + boolean hasVersions() throws IOException; + + Versions readVersions() throws IOException; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java index 1c2600ad7..bdeca0cf8 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncRecordWriter.java @@ -15,5 +15,7 @@ public interface SyncRecordWriter { void writeRequest(Request r) throws IOException; + void writeVersions(Versions v) throws IOException; + void flush() throws IOException; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Versions.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Versions.java new file mode 100644 index 000000000..9517d02e6 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Versions.java @@ -0,0 +1,26 @@ +package org.briarproject.bramble.api.sync; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.List; + +import javax.annotation.concurrent.Immutable; + +/** + * A record telling the recipient which versions of the sync protocol the + * sender supports. + */ +@Immutable +@NotNullByDefault +public class Versions { + + private final List supported; + + public Versions(List supported) { + this.supported = supported; + } + + public List getSupportedVersions() { + return supported; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java index 02b3a4154..7ea0f6a41 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java @@ -13,6 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId; 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.api.sync.Versions; import org.briarproject.bramble.util.ByteUtils; import java.io.IOException; @@ -26,6 +27,8 @@ 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.RecordTypes.VERSIONS; +import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION; @@ -45,7 +48,7 @@ class SyncRecordReaderImpl implements SyncRecordReader { private static boolean isKnownRecordType(byte type) { return type == ACK || type == MESSAGE || type == OFFER || - type == REQUEST; + type == REQUEST || type == VERSIONS; } private final MessageFactory messageFactory; @@ -148,4 +151,27 @@ class SyncRecordReaderImpl implements SyncRecordReader { if (!hasRequest()) throw new FormatException(); return new Request(readMessageIds()); } + + @Override + public boolean hasVersions() throws IOException { + return !eof() && getNextRecordType() == VERSIONS; + } + + @Override + public Versions readVersions() throws IOException { + if (!hasVersions()) throw new FormatException(); + return new Versions(readSupportedVersions()); + } + + private List readSupportedVersions() throws IOException { + if (nextRecord == null) throw new AssertionError(); + byte[] payload = nextRecord.getPayload(); + if (payload.length == 0) throw new FormatException(); + if (payload.length > MAX_SUPPORTED_VERSIONS) + throw new FormatException(); + List supported = new ArrayList<>(payload.length); + for (byte b : payload) supported.add(b); + nextRecord = null; + return supported; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java index 583f4eb62..118cc51cd 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordWriterImpl.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.SyncRecordWriter; +import org.briarproject.bramble.api.sync.Versions; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -20,6 +21,7 @@ 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.RecordTypes.VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION; @NotThreadSafe @@ -65,6 +67,12 @@ class SyncRecordWriterImpl implements SyncRecordWriter { writeRecord(REQUEST); } + @Override + public void writeVersions(Versions v) throws IOException { + for (byte b : v.getSupportedVersions()) payload.write(b); + writeRecord(VERSIONS); + } + @Override public void flush() throws IOException { writer.flush(); 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 index ae52af979..667c2b557 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncRecordReaderImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncRecordReaderImplTest.java @@ -10,11 +10,14 @@ 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.api.sync.Versions; import org.briarproject.bramble.test.BrambleMockTestCase; import org.jmock.Expectations; +import org.junit.Before; import org.junit.Test; import java.io.ByteArrayOutputStream; +import java.util.List; import javax.annotation.Nullable; @@ -22,7 +25,9 @@ import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTE 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.RecordTypes.VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; +import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.junit.Assert.assertEquals; @@ -35,12 +40,17 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { context.mock(MessageFactory.class); private final RecordReader recordReader = context.mock(RecordReader.class); + private SyncRecordReader reader; + + @Before + public void setUp() { + reader = new SyncRecordReaderImpl(messageFactory, recordReader); + } + @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()); } @@ -49,8 +59,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { public void testFormatExceptionIfAckIsEmpty() throws Exception { expectReadRecord(createEmptyAck()); - SyncRecordReader reader = - new SyncRecordReaderImpl(messageFactory, recordReader); reader.readAck(); } @@ -58,8 +66,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception { expectReadRecord(createOffer()); - SyncRecordReader reader = - new SyncRecordReaderImpl(messageFactory, recordReader); Offer offer = reader.readOffer(); assertEquals(MAX_MESSAGE_IDS, offer.getMessageIds().size()); } @@ -68,8 +74,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { public void testFormatExceptionIfOfferIsEmpty() throws Exception { expectReadRecord(createEmptyOffer()); - SyncRecordReader reader = - new SyncRecordReaderImpl(messageFactory, recordReader); reader.readOffer(); } @@ -77,8 +81,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception { expectReadRecord(createRequest()); - SyncRecordReader reader = - new SyncRecordReaderImpl(messageFactory, recordReader); Request request = reader.readRequest(); assertEquals(MAX_MESSAGE_IDS, request.getMessageIds().size()); } @@ -87,11 +89,36 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { public void testFormatExceptionIfRequestIsEmpty() throws Exception { expectReadRecord(createEmptyRequest()); - SyncRecordReader reader = - new SyncRecordReaderImpl(messageFactory, recordReader); reader.readRequest(); } + @Test + public void testNoFormatExceptionIfVersionsIsMaximumSize() + throws Exception { + expectReadRecord(createVersions(MAX_SUPPORTED_VERSIONS)); + + Versions versions = reader.readVersions(); + List supported = versions.getSupportedVersions(); + assertEquals(MAX_SUPPORTED_VERSIONS, supported.size()); + for (int i = 0; i < supported.size(); i++) { + assertEquals(i, (int) supported.get(i)); + } + } + + @Test(expected = FormatException.class) + public void testFormatExceptionIfVersionsIsEmpty() throws Exception { + expectReadRecord(createVersions(0)); + + reader.readVersions(); + } + + @Test(expected = FormatException.class) + public void testFormatExceptionIfVersionsIsTooLarge() throws Exception { + expectReadRecord(createVersions(MAX_SUPPORTED_VERSIONS + 1)); + + reader.readVersions(); + } + @Test public void testEofReturnsTrueWhenAtEndOfStream() throws Exception { expectReadRecord(createAck()); @@ -140,6 +167,12 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase { return new Record(PROTOCOL_VERSION, REQUEST, new byte[0]); } + private Record createVersions(int numVersions) { + byte[] payload = new byte[numVersions]; + for (int i = 0; i < payload.length; i++) payload[i] = (byte) i; + return new Record(PROTOCOL_VERSION, VERSIONS, payload); + } + private byte[] createPayload() throws Exception { ByteArrayOutputStream payload = new ByteArrayOutputStream(); while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_BYTES) {