Use generic record reader/writer for sync.

This commit is contained in:
akwizgran
2018-04-19 13:11:48 +01:00
parent 4100daaa47
commit 215c62ed23
16 changed files with 203 additions and 243 deletions

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
public interface SyncConstants { public interface SyncConstants {
/** /**
@@ -9,16 +11,6 @@ public interface SyncConstants {
*/ */
byte PROTOCOL_VERSION = 0; 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. * 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. * 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;
} }

View File

@@ -39,8 +39,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; 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_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
/** /**
* An outgoing {@link SyncSession} suitable for duplex transports. The session * An outgoing {@link SyncSession} suitable for duplex transports. The session
@@ -273,7 +273,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
b = db.generateRequestedBatch(txn, contactId, b = db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency); MAX_RECORD_PAYLOAD_BYTES, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId)); setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {

View File

@@ -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.BLOCK_LABEL;
import static org.briarproject.bramble.api.sync.MessageId.ID_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_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.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
@@ -53,6 +54,8 @@ class MessageFactoryImpl implements MessageFactory {
public Message createMessage(MessageId m, byte[] raw) { public Message createMessage(MessageId m, byte[] raw) {
if (raw.length < MESSAGE_HEADER_LENGTH) if (raw.length < MESSAGE_HEADER_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (raw.length > MAX_MESSAGE_LENGTH)
throw new IllegalArgumentException();
byte[] groupId = new byte[UniqueId.LENGTH]; byte[] groupId = new byte[UniqueId.LENGTH];
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH); System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH); long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);

View File

@@ -29,8 +29,8 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; 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_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
/** /**
* An outgoing {@link SyncSession} suitable for simplex transports. The session * An outgoing {@link SyncSession} suitable for simplex transports. The session
@@ -171,7 +171,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
b = db.generateBatch(txn, contactId, b = db.generateBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency); MAX_RECORD_PAYLOAD_BYTES, maxLatency);
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);

View File

@@ -58,8 +58,9 @@ public class SyncModule {
} }
@Provides @Provides
SyncRecordWriterFactory provideRecordWriterFactory() { SyncRecordWriterFactory provideRecordWriterFactory(
return new SyncRecordWriterFactoryImpl(); SyncRecordWriterFactoryImpl recordWriterFactory) {
return recordWriterFactory;
} }
@Provides @Provides

View File

@@ -1,6 +1,8 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.MessageFactory;
import org.briarproject.bramble.api.sync.SyncRecordReader; import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory; import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
@@ -15,14 +17,18 @@ import javax.inject.Inject;
class SyncRecordReaderFactoryImpl implements SyncRecordReaderFactory { class SyncRecordReaderFactoryImpl implements SyncRecordReaderFactory {
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final RecordReaderFactory recordReaderFactory;
@Inject @Inject
SyncRecordReaderFactoryImpl(MessageFactory messageFactory) { SyncRecordReaderFactoryImpl(MessageFactory messageFactory,
RecordReaderFactory recordReaderFactory) {
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.recordReaderFactory = recordReaderFactory;
} }
@Override @Override
public SyncRecordReader createRecordReader(InputStream in) { public SyncRecordReader createRecordReader(InputStream in) {
return new SyncRecordReaderImpl(messageFactory, in); RecordReader reader = recordReaderFactory.createRecordReader(in);
return new SyncRecordReaderImpl(messageFactory, reader);
} }
} }

View File

@@ -3,6 +3,8 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.Ack;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -13,72 +15,45 @@ import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader; import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK; 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.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER; 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.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.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class SyncRecordReaderImpl implements SyncRecordReader { class SyncRecordReaderImpl implements SyncRecordReader {
private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF}
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final InputStream in; private final RecordReader reader;
private final byte[] header, payload;
private State state = State.BUFFER_EMPTY; @Nullable
private int payloadLength = 0; private Record nextRecord = null;
private boolean eof = false;
SyncRecordReaderImpl(MessageFactory messageFactory, InputStream in) { SyncRecordReaderImpl(MessageFactory messageFactory, RecordReader reader) {
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.in = in; this.reader = reader;
header = new byte[RECORD_HEADER_LENGTH];
payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
} }
private void readRecord() throws IOException { private void readRecord() throws IOException {
if (state != State.BUFFER_EMPTY) throw new IllegalStateException(); assert nextRecord == null;
while (true) { while (true) {
// Read the header nextRecord = reader.readRecord();
int offset = 0;
while (offset < RECORD_HEADER_LENGTH) {
int read =
in.read(header, offset, RECORD_HEADER_LENGTH - offset);
if (read == -1) {
if (offset > 0) throw new FormatException();
state = State.EOF;
return;
}
offset += read;
}
byte version = header[0], type = header[1];
payloadLength = ByteUtils.readUint16(header, 2);
// Check the protocol version // Check the protocol version
byte version = nextRecord.getProtocolVersion();
if (version != PROTOCOL_VERSION) throw new FormatException(); if (version != PROTOCOL_VERSION) throw new FormatException();
// Check the payload length byte type = nextRecord.getRecordType();
if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH)
throw new FormatException();
// Read the payload
offset = 0;
while (offset < payloadLength) {
int read = in.read(payload, offset, payloadLength - offset);
if (read == -1) throw new FormatException();
offset += read;
}
state = State.BUFFER_FULL;
// Return if this is a known record type, otherwise continue // Return if this is a known record type, otherwise continue
if (type == ACK || type == MESSAGE || type == OFFER || if (type == ACK || type == MESSAGE || type == OFFER ||
type == REQUEST) { type == REQUEST) {
@@ -87,6 +62,11 @@ class SyncRecordReaderImpl implements SyncRecordReader {
} }
} }
private byte getNextRecordType() {
assert nextRecord != null;
return nextRecord.getRecordType();
}
/** /**
* Returns true if there's another record available or false if we've * Returns true if there's another record available or false if we've
* reached the end of the input stream. * reached the end of the input stream.
@@ -97,14 +77,21 @@ class SyncRecordReaderImpl implements SyncRecordReader {
*/ */
@Override @Override
public boolean eof() throws IOException { public boolean eof() throws IOException {
if (state == State.BUFFER_EMPTY) readRecord(); if (nextRecord != null) return false;
if (state == State.BUFFER_EMPTY) throw new IllegalStateException(); if (eof) return true;
return state == State.EOF; try {
readRecord();
return false;
} catch (EOFException e) {
nextRecord = null;
eof = true;
return true;
}
} }
@Override @Override
public boolean hasAck() throws IOException { public boolean hasAck() throws IOException {
return !eof() && header[1] == ACK; return !eof() && getNextRecordType() == ACK;
} }
@Override @Override
@@ -114,27 +101,31 @@ class SyncRecordReaderImpl implements SyncRecordReader {
} }
private List<MessageId> readMessageIds() throws IOException { private List<MessageId> readMessageIds() throws IOException {
if (payloadLength == 0) throw new FormatException(); assert nextRecord != null;
if (payloadLength % UniqueId.LENGTH != 0) throw new FormatException(); byte[] payload = nextRecord.getPayload();
List<MessageId> ids = new ArrayList<>(); if (payload.length == 0) throw new FormatException();
for (int off = 0; off < payloadLength; off += UniqueId.LENGTH) { 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]; byte[] id = new byte[UniqueId.LENGTH];
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH); System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);
ids.add(new MessageId(id)); ids.add(new MessageId(id));
} }
state = State.BUFFER_EMPTY; nextRecord = null;
return ids; return ids;
} }
@Override @Override
public boolean hasMessage() throws IOException { public boolean hasMessage() throws IOException {
return !eof() && header[1] == MESSAGE; return !eof() && getNextRecordType() == MESSAGE;
} }
@Override @Override
public Message readMessage() throws IOException { public Message readMessage() throws IOException {
if (!hasMessage()) throw new FormatException(); if (!hasMessage()) throw new FormatException();
if (payloadLength <= MESSAGE_HEADER_LENGTH) throw new FormatException(); assert nextRecord != null;
byte[] payload = nextRecord.getPayload();
if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException();
// Group ID // Group ID
byte[] id = new byte[UniqueId.LENGTH]; byte[] id = new byte[UniqueId.LENGTH];
System.arraycopy(payload, 0, id, 0, UniqueId.LENGTH); System.arraycopy(payload, 0, id, 0, UniqueId.LENGTH);
@@ -143,16 +134,17 @@ class SyncRecordReaderImpl implements SyncRecordReader {
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH); long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException(); if (timestamp < 0) throw new FormatException();
// Body // Body
byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH]; byte[] body = new byte[payload.length - MESSAGE_HEADER_LENGTH];
System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0, System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
payloadLength - MESSAGE_HEADER_LENGTH); payload.length - MESSAGE_HEADER_LENGTH);
state = State.BUFFER_EMPTY; nextRecord = null;
// TODO: Add a method that reuses the raw message
return messageFactory.createMessage(groupId, timestamp, body); return messageFactory.createMessage(groupId, timestamp, body);
} }
@Override @Override
public boolean hasOffer() throws IOException { public boolean hasOffer() throws IOException {
return !eof() && header[1] == OFFER; return !eof() && getNextRecordType() == OFFER;
} }
@Override @Override
@@ -163,7 +155,7 @@ class SyncRecordReaderImpl implements SyncRecordReader {
@Override @Override
public boolean hasRequest() throws IOException { public boolean hasRequest() throws IOException {
return !eof() && header[1] == REQUEST; return !eof() && getNextRecordType() == REQUEST;
} }
@Override @Override

View File

@@ -1,16 +1,28 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory; import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import java.io.OutputStream; import java.io.OutputStream;
import javax.inject.Inject;
@NotNullByDefault @NotNullByDefault
class SyncRecordWriterFactoryImpl implements SyncRecordWriterFactory { class SyncRecordWriterFactoryImpl implements SyncRecordWriterFactory {
private final RecordWriterFactory recordWriterFactory;
@Inject
SyncRecordWriterFactoryImpl(RecordWriterFactory recordWriterFactory) {
this.recordWriterFactory = recordWriterFactory;
}
@Override @Override
public SyncRecordWriter createRecordWriter(OutputStream out) { public SyncRecordWriter createRecordWriter(OutputStream out) {
return new SyncRecordWriterImpl(out); RecordWriter writer = recordWriterFactory.createRecordWriter(out);
return new SyncRecordWriterImpl(writer);
} }
} }

View File

@@ -1,81 +1,67 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.Ack;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordTypes;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.util.ByteUtils;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK; 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.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST; 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.PROTOCOL_VERSION; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class SyncRecordWriterImpl implements SyncRecordWriter { class SyncRecordWriterImpl implements SyncRecordWriter {
private final OutputStream out; private final RecordWriter writer;
private final byte[] header; private final ByteArrayOutputStream payload = new ByteArrayOutputStream();
private final ByteArrayOutputStream payload;
SyncRecordWriterImpl(OutputStream out) { SyncRecordWriterImpl(RecordWriter writer) {
this.out = out; this.writer = writer;
header = new byte[RECORD_HEADER_LENGTH];
header[0] = PROTOCOL_VERSION;
payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
} }
private void writeRecord(byte recordType) throws IOException { private void writeRecord(byte recordType) throws IOException {
header[1] = recordType; writer.writeRecord(new Record(PROTOCOL_VERSION, recordType,
ByteUtils.writeUint16(payload.size(), header, 2); payload.toByteArray()));
out.write(header);
payload.writeTo(out);
payload.reset(); payload.reset();
} }
@Override @Override
public void writeAck(Ack a) throws IOException { public void writeAck(Ack a) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes()); for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
writeRecord(ACK); writeRecord(ACK);
} }
@Override @Override
public void writeMessage(byte[] raw) throws IOException { public void writeMessage(byte[] raw) throws IOException {
header[1] = RecordTypes.MESSAGE; writer.writeRecord(new Record(PROTOCOL_VERSION, MESSAGE, raw));
ByteUtils.writeUint16(raw.length, header, 2);
out.write(header);
out.write(raw);
} }
@Override @Override
public void writeOffer(Offer o) throws IOException { public void writeOffer(Offer o) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes()); for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
writeRecord(OFFER); writeRecord(OFFER);
} }
@Override @Override
public void writeRequest(Request r) throws IOException { public void writeRequest(Request r) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes()); for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
writeRecord(REQUEST); writeRecord(REQUEST);
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
out.flush(); writer.flush();
} }
} }

View File

@@ -112,8 +112,8 @@ public class SyncIntegrationTest extends BrambleTestCase {
recordWriter.writeMessage(message1.getRaw()); recordWriter.writeMessage(message1.getRaw());
recordWriter.writeOffer(new Offer(messageIds)); recordWriter.writeOffer(new Offer(messageIds));
recordWriter.writeRequest(new Request(messageIds)); recordWriter.writeRequest(new Request(messageIds));
recordWriter.flush();
streamWriter.flush();
return out.toByteArray(); return out.toByteArray();
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.crypto.CryptoModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.test.TestSecureRandomModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
@@ -13,6 +14,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
TestSecureRandomModule.class, TestSecureRandomModule.class,
CryptoModule.class, CryptoModule.class,
RecordModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,
TransportModule.class TransportModule.class

View File

@@ -2,23 +2,28 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId; 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.Ack;
import org.briarproject.bramble.api.sync.MessageFactory; 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.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestUtils; import org.jmock.Expectations;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; 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.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER; 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.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.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.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -27,209 +32,164 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
private final MessageFactory messageFactory = private final MessageFactory messageFactory =
context.mock(MessageFactory.class); context.mock(MessageFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
byte[] b = createAck(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test @Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception { public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
byte[] b = createAck(false); expectReadRecord(createAck());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl SyncRecordReader reader =
reader = new SyncRecordReaderImpl(messageFactory, in); new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readAck(); Ack ack = reader.readAck();
assertEquals(MAX_MESSAGE_IDS, ack.getMessageIds().size());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsEmpty() throws Exception { public void testFormatExceptionIfAckIsEmpty() throws Exception {
byte[] b = createEmptyAck(); expectReadRecord(createEmptyAck());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test(expected = FormatException.class) SyncRecordReader reader =
public void testFormatExceptionIfOfferIsTooLarge() throws Exception { new SyncRecordReaderImpl(messageFactory, recordReader);
byte[] b = createOffer(true); reader.readAck();
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.readOffer();
} }
@Test @Test
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception { public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
byte[] b = createOffer(false); expectReadRecord(createOffer());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl SyncRecordReader reader =
reader = new SyncRecordReaderImpl(messageFactory, in); new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readOffer(); Offer offer = reader.readOffer();
assertEquals(MAX_MESSAGE_IDS, offer.getMessageIds().size());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsEmpty() throws Exception { public void testFormatExceptionIfOfferIsEmpty() throws Exception {
byte[] b = createEmptyOffer(); expectReadRecord(createEmptyOffer());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test(expected = FormatException.class) SyncRecordReader reader =
public void testFormatExceptionIfRequestIsTooLarge() throws Exception { new SyncRecordReaderImpl(messageFactory, recordReader);
byte[] b = createRequest(true); reader.readOffer();
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.readRequest();
} }
@Test @Test
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception { public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
byte[] b = createRequest(false); expectReadRecord(createRequest());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl SyncRecordReader reader =
reader = new SyncRecordReaderImpl(messageFactory, in); new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readRequest(); Request request = reader.readRequest();
assertEquals(MAX_MESSAGE_IDS, request.getMessageIds().size());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsEmpty() throws Exception { public void testFormatExceptionIfRequestIsEmpty() throws Exception {
byte[] b = createEmptyRequest(); expectReadRecord(createEmptyRequest());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl SyncRecordReader reader =
reader = new SyncRecordReaderImpl(messageFactory, in); new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readRequest(); reader.readRequest();
} }
@Test @Test
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception { public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]); context.checking(new Expectations() {{
SyncRecordReaderImpl oneOf(recordReader).readRecord();
reader = new SyncRecordReaderImpl(messageFactory, in); will(throwException(new EOFException()));
}});
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
assertTrue(reader.eof());
assertTrue(reader.eof()); assertTrue(reader.eof());
} }
@Test @Test
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception { public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
byte[] b = createAck(false); expectReadRecord(createAck());
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl SyncRecordReader reader =
reader = new SyncRecordReaderImpl(messageFactory, in); new SyncRecordReaderImpl(messageFactory, recordReader);
assertFalse(reader.eof());
assertFalse(reader.eof()); 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);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(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);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.eof();
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testThrowsExceptionIfProtocolVersionIsUnrecognised() public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception { throws Exception {
byte version = (byte) (PROTOCOL_VERSION + 1); byte version = (byte) (PROTOCOL_VERSION + 1);
byte[] b = createRecord(version, ACK, new byte[0]); byte[] payload = getRandomId();
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class) expectReadRecord(new Record(version, ACK, payload));
public void testThrowsExceptionIfPayloadIsTooLong() throws Exception {
byte[] payload = new byte[MAX_RECORD_PAYLOAD_LENGTH + 1]; SyncRecordReader reader =
byte[] b = createRecord(PROTOCOL_VERSION, ACK, payload); new SyncRecordReaderImpl(messageFactory, recordReader);
ByteArrayInputStream in = new ByteArrayInputStream(b);
SyncRecordReaderImpl
reader = new SyncRecordReaderImpl(messageFactory, in);
reader.eof(); reader.eof();
} }
@Test @Test
public void testSkipsUnrecognisedRecordTypes() throws Exception { public void testSkipsUnrecognisedRecordTypes() throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 1), byte type1 = (byte) (REQUEST + 1);
new byte[123]); byte[] payload1 = getRandomBytes(123);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 2), Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
new byte[0]); byte type2 = (byte) (REQUEST + 2);
byte[] ack = createAck(false); byte[] payload2 = new byte[0];
ByteArrayOutputStream input = new ByteArrayOutputStream(); Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
input.write(skip1); Record ackRecord = createAck();
input.write(skip2);
input.write(ack); context.checking(new Expectations() {{
ByteArrayInputStream in = new ByteArrayInputStream(input.toByteArray()); oneOf(recordReader).readRecord();
SyncRecordReaderImpl will(returnValue(unknownRecord1));
reader = new SyncRecordReaderImpl(messageFactory, in); oneOf(recordReader).readRecord();
will(returnValue(unknownRecord2));
oneOf(recordReader).readRecord();
will(returnValue(ackRecord));
}});
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
assertTrue(reader.hasAck()); assertTrue(reader.hasAck());
Ack a = reader.readAck(); Ack a = reader.readAck();
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size()); assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
} }
private byte[] createAck(boolean tooBig) throws Exception { private void expectReadRecord(Record record) throws Exception {
return createRecord(PROTOCOL_VERSION, ACK, createPayload(tooBig)); context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(record));
}});
} }
private byte[] createEmptyAck() throws Exception { private Record createAck() throws Exception {
return createRecord(PROTOCOL_VERSION, ACK, new byte[0]); return new Record(PROTOCOL_VERSION, ACK, createPayload());
} }
private byte[] createOffer(boolean tooBig) throws Exception { private Record createEmptyAck() throws Exception {
return createRecord(PROTOCOL_VERSION, OFFER, createPayload(tooBig)); return new Record(PROTOCOL_VERSION, ACK, new byte[0]);
} }
private byte[] createEmptyOffer() throws Exception { private Record createOffer() throws Exception {
return createRecord(PROTOCOL_VERSION, OFFER, new byte[0]); return new Record(PROTOCOL_VERSION, OFFER, createPayload());
} }
private byte[] createRequest(boolean tooBig) throws Exception { private Record createEmptyOffer() throws Exception {
return createRecord(PROTOCOL_VERSION, REQUEST, createPayload(tooBig)); return new Record(PROTOCOL_VERSION, OFFER, new byte[0]);
} }
private byte[] createEmptyRequest() throws Exception { private Record createRequest() throws Exception {
return createRecord(PROTOCOL_VERSION, REQUEST, new byte[0]); return new Record(PROTOCOL_VERSION, REQUEST, createPayload());
} }
private byte[] createRecord(byte version, byte type, byte[] payload) { private Record createEmptyRequest() throws Exception {
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length]; return new Record(PROTOCOL_VERSION, REQUEST, new byte[0]);
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 { private byte[] createPayload() throws Exception {
ByteArrayOutputStream payload = new ByteArrayOutputStream(); ByteArrayOutputStream payload = new ByteArrayOutputStream();
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_LENGTH) { while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_BYTES) {
payload.write(TestUtils.getRandomId()); payload.write(getRandomId());
} }
if (tooBig) payload.write(TestUtils.getRandomId());
assertEquals(tooBig, payload.size() > MAX_RECORD_PAYLOAD_LENGTH);
return payload.toByteArray(); return payload.toByteArray();
} }
} }

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestDatabaseModule;
@@ -52,6 +53,7 @@ import dagger.Component;
MessagingModule.class, MessagingModule.class,
PrivateGroupModule.class, PrivateGroupModule.class,
PropertiesModule.class, PropertiesModule.class,
RecordModule.class,
SharingModule.class, SharingModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,

View File

@@ -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_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH; 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; int length = message.getMessage().getRaw().length;
assertTrue( assertTrue(
length > UniqueId.LENGTH + 8 + MAX_PRIVATE_MESSAGE_BODY_LENGTH); length > UniqueId.LENGTH + 8 + MAX_PRIVATE_MESSAGE_BODY_LENGTH);
assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH); assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
} }
@Test @Test
@@ -87,7 +87,7 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH + 4 assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH + 4
+ MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH + MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH
+ MAX_FORUM_POST_BODY_LENGTH); + MAX_FORUM_POST_BODY_LENGTH);
assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH); assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
} }
private static void injectEagerSingletons( private static void injectEagerSingletons(

View File

@@ -16,6 +16,7 @@ import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.test.TestCryptoExecutorModule; import org.briarproject.bramble.test.TestCryptoExecutorModule;
@@ -48,6 +49,7 @@ import dagger.Component;
IdentityModule.class, IdentityModule.class,
LifecycleModule.class, LifecycleModule.class,
MessagingModule.class, MessagingModule.class,
RecordModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,
TransportModule.class, TransportModule.class,

View File

@@ -19,6 +19,7 @@ import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestDatabaseModule;
@@ -73,6 +74,7 @@ import dagger.Component;
MessagingModule.class, MessagingModule.class,
PrivateGroupModule.class, PrivateGroupModule.class,
PropertiesModule.class, PropertiesModule.class,
RecordModule.class,
SharingModule.class, SharingModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,