Delimited structs - this will allow us to skip unrecognised structs.

This commit is contained in:
akwizgran
2013-11-19 18:05:44 +00:00
parent ab5389ce1f
commit 6764ade475
20 changed files with 310 additions and 224 deletions

View File

@@ -40,30 +40,28 @@ public interface Reader {
double readFloat64() throws IOException;
boolean hasString() throws IOException;
String readString() throws IOException;
String readString(int maxLength) throws IOException;
boolean hasBytes() throws IOException;
byte[] readBytes() throws IOException;
byte[] readBytes(int maxLength) throws IOException;
boolean hasList() throws IOException;
<E> List<E> readList(Class<E> e) throws IOException;
boolean hasListStart() throws IOException;
void readListStart() throws IOException;
boolean hasListEnd() throws IOException;
void readListEnd() throws IOException;
boolean hasMap() throws IOException;
<K, V> Map<K, V> readMap(Class<K> k, Class<V> v) throws IOException;
boolean hasMapStart() throws IOException;
void readMapStart() throws IOException;
boolean hasMapEnd() throws IOException;
void readMapEnd() throws IOException;
boolean hasStruct(int id) throws IOException;
void readStructStart(int id) throws IOException;
boolean hasStructEnd() throws IOException;
void readStructEnd() throws IOException;
boolean hasNull() throws IOException;
void readNull() throws IOException;
boolean hasStruct(int id) throws IOException;
void readStructId(int id) throws IOException;
}

View File

@@ -2,11 +2,13 @@ package net.sf.briar.api.serial;
public interface SerialComponent {
int getSerialisedListEndLength();
int getSerialisedListStartLength();
int getSerialisedStructIdLength(int id);
int getSerialisedListEndLength();
int getSerialisedStructStartLength(int id);
int getSerialisedStructEndLength();
int getSerialisedUniqueIdLength();
}

View File

@@ -35,7 +35,8 @@ public interface Writer {
void writeMapStart() throws IOException;
void writeMapEnd() throws IOException;
void writeNull() throws IOException;
void writeStructStart(int id) throws IOException;
void writeStructEnd() throws IOException;
void writeStructId(int id) throws IOException;
void writeNull() throws IOException;
}

View File

@@ -41,9 +41,10 @@ class AuthorFactoryImpl implements AuthorFactory {
private AuthorId getId(String name, byte[] publicKey) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(AUTHOR);
w.writeStructStart(AUTHOR);
w.writeString(name);
w.writeBytes(publicKey);
w.writeStructEnd();
MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(out.toByteArray());
return new AuthorId(messageDigest.digest());

View File

@@ -23,12 +23,15 @@ class AuthorReader implements StructReader<Author> {
}
public Author readStruct(Reader r) throws IOException {
// Set up the reader
DigestingConsumer digesting = new DigestingConsumer(messageDigest);
// Read and digest the data
r.addConsumer(digesting);
r.readStructId(AUTHOR);
// Read and digest the data
r.readStructStart(AUTHOR);
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
r.readStructEnd();
// Reset the reader
r.removeConsumer(digesting);
// Build and return the author
AuthorId id = new AuthorId(messageDigest.digest());

View File

@@ -36,9 +36,10 @@ class GroupFactoryImpl implements GroupFactory {
public Group createGroup(String name, byte[] salt) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(GROUP);
w.writeStructStart(GROUP);
w.writeString(name);
w.writeBytes(salt);
w.writeStructEnd();
MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(out.toByteArray());
GroupId id = new GroupId(messageDigest.digest());

View File

@@ -26,11 +26,12 @@ class GroupReader implements StructReader<Group> {
DigestingConsumer digesting = new DigestingConsumer(messageDigest);
// Read and digest the data
r.addConsumer(digesting);
r.readStructId(GROUP);
r.readStructStart(GROUP);
String name = r.readString(MAX_GROUP_NAME_LENGTH);
byte[] publicKey = null;
if(r.hasNull()) r.readNull();
else publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
r.readStructEnd();
r.removeConsumer(digesting);
// Build and return the group
GroupId id = new GroupId(messageDigest.digest());

View File

@@ -100,7 +100,7 @@ class MessageFactoryImpl implements MessageFactory {
w.addConsumer(signingConsumer);
}
// Write the message
w.writeStructId(MESSAGE);
w.writeStructStart(MESSAGE);
if(parent == null) w.writeNull();
else w.writeBytes(parent.getBytes());
if(group == null) w.writeNull();
@@ -109,7 +109,7 @@ class MessageFactoryImpl implements MessageFactory {
else writeAuthor(w, author);
w.writeString(contentType);
long timestamp = clock.currentTimeMillis();
w.writeInt64(timestamp);
w.writeIntAny(timestamp);
byte[] salt = new byte[MESSAGE_SALT_LENGTH];
random.nextBytes(salt);
w.writeBytes(salt);
@@ -125,6 +125,7 @@ class MessageFactoryImpl implements MessageFactory {
throw new IllegalArgumentException();
w.writeBytes(sig);
}
w.writeStructEnd();
// Hash the message, including the signature, to get the message ID
w.removeConsumer(digestingConsumer);
MessageId id = new MessageId(messageDigest.digest());
@@ -143,14 +144,16 @@ class MessageFactoryImpl implements MessageFactory {
}
private void writeGroup(Writer w, Group g) throws IOException {
w.writeStructId(GROUP);
w.writeStructStart(GROUP);
w.writeString(g.getName());
w.writeBytes(g.getSalt());
w.writeStructEnd();
}
private void writeAuthor(Writer w, Author a) throws IOException {
w.writeStructId(AUTHOR);
w.writeStructStart(AUTHOR);
w.writeString(a.getName());
w.writeBytes(a.getPublicKey());
w.writeStructEnd();
}
}

View File

@@ -42,8 +42,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
CountingConsumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
r.addConsumer(copying);
r.addConsumer(counting);
// Read the initial tag
r.readStructId(MESSAGE);
// Read the start of the struct
r.readStructStart(MESSAGE);
// Read the parent's message ID, if there is one
MessageId parent = null;
if(r.hasNull()) {
@@ -64,7 +64,7 @@ class MessageReader implements StructReader<UnverifiedMessage> {
// Read the content type
String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
// Read the timestamp
long timestamp = r.readInt64();
long timestamp = r.readIntAny();
if(timestamp < 0) throw new FormatException();
// Read the salt
byte[] salt = r.readBytes(MESSAGE_SALT_LENGTH);
@@ -89,6 +89,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
byte[] signature = null;
if(author == null) r.readNull();
else signature = r.readBytes(MAX_SIGNATURE_LENGTH);
// Read the end of the struct
r.readStructEnd();
// The signature will be verified later
r.removeConsumer(counting);
r.removeConsumer(copying);

View File

@@ -70,12 +70,17 @@ class PacketReaderImpl implements PacketReader {
}
public Ack readAck() throws IOException {
// Set up the reader
Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
r.addConsumer(counting);
r.readStructId(ACK);
// Read the start of the struct
r.readStructStart(ACK);
// Read the message IDs as byte arrays
r.setMaxBytesLength(UniqueId.LENGTH);
List<Bytes> raw = r.readList(Bytes.class);
// Read the end of the struct
r.readStructEnd();
// Reset the reader
r.resetMaxBytesLength();
r.removeConsumer(counting);
if(raw.isEmpty()) throw new FormatException();
@@ -103,12 +108,17 @@ class PacketReaderImpl implements PacketReader {
}
public Offer readOffer() throws IOException {
// Set up the reader
Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
r.addConsumer(counting);
r.readStructId(OFFER);
// Read the start of the struct
r.readStructStart(OFFER);
// Read the message IDs as byte arrays
r.setMaxBytesLength(UniqueId.LENGTH);
List<Bytes> raw = r.readList(Bytes.class);
// Read the end of the struct
r.readStructEnd();
// Reset the reader
r.resetMaxBytesLength();
r.removeConsumer(counting);
if(raw.isEmpty()) throw new FormatException();
@@ -128,14 +138,19 @@ class PacketReaderImpl implements PacketReader {
}
public Request readRequest() throws IOException {
// Set up the reader
Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
r.addConsumer(counting);
r.readStructId(REQUEST);
// Read the start of the struct
r.readStructStart(REQUEST);
// There may be up to 7 bits of padding at the end of the bitmap
int padding = r.readUint7();
if(padding > 7) throw new FormatException();
// Read the bitmap
byte[] bitmap = r.readBytes(MAX_PACKET_LENGTH);
// Read the end of the struct
r.readStructEnd();
// Reset the reader
r.removeConsumer(counting);
// Convert the bitmap into a BitSet
int length = bitmap.length * 8 - padding;
@@ -154,9 +169,10 @@ class PacketReaderImpl implements PacketReader {
}
public RetentionAck readRetentionAck() throws IOException {
r.readStructId(RETENTION_ACK);
long version = r.readInt64();
r.readStructStart(RETENTION_ACK);
long version = r.readIntAny();
if(version < 0) throw new FormatException();
r.readStructEnd();
return new RetentionAck(version);
}
@@ -165,11 +181,12 @@ class PacketReaderImpl implements PacketReader {
}
public RetentionUpdate readRetentionUpdate() throws IOException {
r.readStructId(RETENTION_UPDATE);
long retention = r.readInt64();
r.readStructStart(RETENTION_UPDATE);
long retention = r.readIntAny();
if(retention < 0) throw new FormatException();
long version = r.readInt64();
long version = r.readIntAny();
if(version < 0) throw new FormatException();
r.readStructEnd();
return new RetentionUpdate(retention, version);
}
@@ -178,9 +195,10 @@ class PacketReaderImpl implements PacketReader {
}
public SubscriptionAck readSubscriptionAck() throws IOException {
r.readStructId(SUBSCRIPTION_ACK);
long version = r.readInt64();
r.readStructStart(SUBSCRIPTION_ACK);
long version = r.readIntAny();
if(version < 0) throw new FormatException();
r.readStructEnd();
return new SubscriptionAck(version);
}
@@ -197,11 +215,12 @@ class PacketReaderImpl implements PacketReader {
}
public TransportAck readTransportAck() throws IOException {
r.readStructId(TRANSPORT_ACK);
r.readStructStart(TRANSPORT_ACK);
byte[] b = r.readBytes(UniqueId.LENGTH);
if(b.length < UniqueId.LENGTH) throw new FormatException();
long version = r.readInt64();
long version = r.readIntAny();
if(version < 0) throw new FormatException();
r.readStructEnd();
return new TransportAck(new TransportId(b), version);
}
@@ -210,9 +229,11 @@ class PacketReaderImpl implements PacketReader {
}
public TransportUpdate readTransportUpdate() throws IOException {
// Set up the reader
Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
r.addConsumer(counting);
r.readStructId(TRANSPORT_UPDATE);
// Read the start of the struct
r.readStructStart(TRANSPORT_UPDATE);
// Read the transport ID
byte[] b = r.readBytes(UniqueId.LENGTH);
if(b.length < UniqueId.LENGTH) throw new FormatException();
@@ -223,8 +244,11 @@ class PacketReaderImpl implements PacketReader {
r.resetMaxStringLength();
if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) throw new FormatException();
// Read the version number
long version = r.readInt64();
long version = r.readIntAny();
if(version < 0) throw new FormatException();
// Read the end of the struct
r.readStructEnd();
// Reset the reader
r.removeConsumer(counting);
// Build and return the transport update
return new TransportUpdate(id, new TransportProperties(m), version);

View File

@@ -50,27 +50,30 @@ class PacketWriterImpl implements PacketWriter {
public int getMaxMessagesForAck(long capacity) {
int packet = (int) Math.min(capacity, MAX_PACKET_LENGTH);
int overhead = serial.getSerialisedStructIdLength(ACK)
int overhead = serial.getSerialisedStructStartLength(ACK)
+ serial.getSerialisedListStartLength()
+ serial.getSerialisedListEndLength();
+ serial.getSerialisedListEndLength()
+ serial.getSerialisedStructEndLength();
int idLength = serial.getSerialisedUniqueIdLength();
return (packet - overhead) / idLength;
}
public int getMaxMessagesForOffer(long capacity) {
int packet = (int) Math.min(capacity, MAX_PACKET_LENGTH);
int overhead = serial.getSerialisedStructIdLength(OFFER)
int overhead = serial.getSerialisedStructStartLength(OFFER)
+ serial.getSerialisedListStartLength()
+ serial.getSerialisedListEndLength();
+ serial.getSerialisedListEndLength()
+ serial.getSerialisedStructEndLength();
int idLength = serial.getSerialisedUniqueIdLength();
return (packet - overhead) / idLength;
}
public void writeAck(Ack a) throws IOException {
w.writeStructId(ACK);
w.writeStructStart(ACK);
w.writeListStart();
for(MessageId m : a.getMessageIds()) w.writeBytes(m.getBytes());
w.writeListEnd();
w.writeStructEnd();
if(flush) out.flush();
}
@@ -80,10 +83,11 @@ class PacketWriterImpl implements PacketWriter {
}
public void writeOffer(Offer o) throws IOException {
w.writeStructId(OFFER);
w.writeStructStart(OFFER);
w.writeListStart();
for(MessageId m : o.getMessageIds()) w.writeBytes(m.getBytes());
w.writeListEnd();
w.writeStructEnd();
if(flush) out.flush();
}
@@ -101,57 +105,65 @@ class PacketWriterImpl implements PacketWriter {
bitmap[offset] |= bit;
}
}
w.writeStructId(REQUEST);
w.writeStructStart(REQUEST);
w.writeUint7((byte) (bytes * 8 - length));
w.writeBytes(bitmap);
w.writeStructEnd();
if(flush) out.flush();
}
public void writeRetentionAck(RetentionAck a) throws IOException {
w.writeStructId(RETENTION_ACK);
w.writeInt64(a.getVersion());
w.writeStructStart(RETENTION_ACK);
w.writeIntAny(a.getVersion());
w.writeStructEnd();
if(flush) out.flush();
}
public void writeRetentionUpdate(RetentionUpdate u) throws IOException {
w.writeStructId(RETENTION_UPDATE);
w.writeInt64(u.getRetentionTime());
w.writeInt64(u.getVersion());
w.writeStructStart(RETENTION_UPDATE);
w.writeIntAny(u.getRetentionTime());
w.writeIntAny(u.getVersion());
w.writeStructEnd();
if(flush) out.flush();
}
public void writeSubscriptionAck(SubscriptionAck a) throws IOException {
w.writeStructId(SUBSCRIPTION_ACK);
w.writeInt64(a.getVersion());
w.writeStructStart(SUBSCRIPTION_ACK);
w.writeIntAny(a.getVersion());
w.writeStructEnd();
if(flush) out.flush();
}
public void writeSubscriptionUpdate(SubscriptionUpdate u)
throws IOException {
w.writeStructId(SUBSCRIPTION_UPDATE);
w.writeStructStart(SUBSCRIPTION_UPDATE);
w.writeListStart();
for(Group g : u.getGroups()) {
w.writeStructId(GROUP);
w.writeStructStart(GROUP);
w.writeString(g.getName());
w.writeBytes(g.getSalt());
w.writeStructEnd();
}
w.writeListEnd();
w.writeInt64(u.getVersion());
w.writeIntAny(u.getVersion());
w.writeStructEnd();
if(flush) out.flush();
}
public void writeTransportAck(TransportAck a) throws IOException {
w.writeStructId(TRANSPORT_ACK);
w.writeStructStart(TRANSPORT_ACK);
w.writeBytes(a.getId().getBytes());
w.writeInt64(a.getVersion());
w.writeIntAny(a.getVersion());
w.writeStructEnd();
if(flush) out.flush();
}
public void writeTransportUpdate(TransportUpdate u) throws IOException {
w.writeStructId(TRANSPORT_UPDATE);
w.writeStructStart(TRANSPORT_UPDATE);
w.writeBytes(u.getId().getBytes());
w.writeMap(u.getProperties());
w.writeInt64(u.getVersion());
w.writeIntAny(u.getVersion());
w.writeStructEnd();
if(flush) out.flush();
}

View File

@@ -26,9 +26,11 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
}
public SubscriptionUpdate readStruct(Reader r) throws IOException {
// Set up the reader
Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
r.addConsumer(counting);
r.readStructId(SUBSCRIPTION_UPDATE);
// Read the start of the struct
r.readStructStart(SUBSCRIPTION_UPDATE);
// Read the subscriptions
List<Group> subs = new ArrayList<Group>();
r.readListStart();
@@ -36,8 +38,11 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
subs.add(groupReader.readStruct(r));
r.readListEnd();
// Read the version number
long version = r.readInt64();
long version = r.readIntAny();
if(version < 0) throw new FormatException();
// Read the end of the struct
r.readStructEnd();
// Reset the reader
r.removeConsumer(counting);
// Build and return the subscription update
subs = Collections.unmodifiableList(subs);

View File

@@ -23,8 +23,8 @@ class ReaderImpl implements Reader {
private final Collection<Consumer> consumers = new ArrayList<Consumer>(0);
private boolean hasLookahead = false, eof = false;
private byte next, nextNext;
private byte[] buf = null;
private byte next, nextStructId;
private byte[] buf = new byte[8];
private int maxStringLength = Integer.MAX_VALUE;
private int maxBytesLength = Integer.MAX_VALUE;
@@ -33,42 +33,40 @@ class ReaderImpl implements Reader {
}
public boolean eof() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
return eof;
}
private byte readLookahead(boolean eofAcceptable) throws IOException {
private void readLookahead() throws IOException {
assert !eof;
// If one or two lookahead bytes have been read, feed the consumers
if(hasLookahead) consumeLookahead();
// Read a lookahead byte
int i = in.read();
if(i == -1) {
if(!eofAcceptable) throw new FormatException();
eof = true;
return;
}
next = (byte) i;
// If necessary, read another lookahead byte
if(next == Tag.STRUCT) {
i = in.read();
if(i == -1) throw new FormatException();
nextNext = (byte) i;
nextStructId = (byte) i;
}
hasLookahead = true;
return next;
}
private void consumeLookahead() throws IOException {
assert hasLookahead;
for(Consumer c : consumers) {
c.write(next);
if(next == Tag.STRUCT) c.write(nextNext);
if(next == Tag.STRUCT) c.write(nextStructId);
}
hasLookahead = false;
}
public void close() throws IOException {
buf = null;
in.close();
}
@@ -97,7 +95,7 @@ class ReaderImpl implements Reader {
}
public boolean hasBoolean() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.FALSE || next == Tag.TRUE;
}
@@ -109,7 +107,7 @@ class ReaderImpl implements Reader {
}
public boolean hasUint7() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next >= 0;
}
@@ -121,34 +119,37 @@ class ReaderImpl implements Reader {
}
public boolean hasInt8() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.INT8;
}
public byte readInt8() throws IOException {
if(!hasInt8()) throw new FormatException();
readLookahead(false);
consumeLookahead();
return next;
int i = in.read();
if(i == -1) {
eof = true;
throw new FormatException();
}
return (byte) i;
}
public boolean hasInt16() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.INT16;
}
public short readInt16() throws IOException {
if(!hasInt16()) throw new FormatException();
byte b1 = readLookahead(false);
byte b2 = readLookahead(false);
consumeLookahead();
return (short) (((b1 & 0xFF) << 8) | (b2 & 0xFF));
readIntoBuffer(2);
return (short) (((buf[0] & 0xFF) << 8) | (buf[1] & 0xFF));
}
public boolean hasInt32() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.INT32;
}
@@ -166,7 +167,7 @@ class ReaderImpl implements Reader {
}
private void readIntoBuffer(int length) throws IOException {
if(buf == null || buf.length < length) buf = new byte[length];
if(buf.length < length) buf = new byte[length];
readIntoBuffer(buf, length);
}
@@ -177,17 +178,16 @@ class ReaderImpl implements Reader {
int read = in.read(b, offset, length - offset);
if(read == -1) {
eof = true;
break;
throw new FormatException();
}
offset += read;
}
if(offset < length) throw new FormatException();
// Feed the hungry mouths
for(Consumer c : consumers) c.write(b, 0, length);
}
public boolean hasInt64() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.INT64;
}
@@ -207,7 +207,7 @@ class ReaderImpl implements Reader {
}
public boolean hasIntAny() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next >= 0 || next == Tag.INT8 || next == Tag.INT16
|| next == Tag.INT32 || next == Tag.INT64;
@@ -224,7 +224,7 @@ class ReaderImpl implements Reader {
}
public boolean hasFloat32() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.FLOAT32;
}
@@ -236,7 +236,7 @@ class ReaderImpl implements Reader {
}
public boolean hasFloat64() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.FLOAT64;
}
@@ -248,15 +248,11 @@ class ReaderImpl implements Reader {
}
public boolean hasString() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.STRING;
}
public String readString() throws IOException {
return readString(maxStringLength);
}
public String readString(int maxLength) throws IOException {
if(!hasString()) throw new FormatException();
consumeLookahead();
@@ -269,30 +265,29 @@ class ReaderImpl implements Reader {
private int readLength() throws IOException {
if(!hasLength()) throw new FormatException();
if(next >= 0) return readUint7();
if(next == Tag.INT8) return readInt8();
if(next == Tag.INT16) return readInt16();
if(next == Tag.INT32) return readInt32();
throw new IllegalStateException();
int length;
if(next >= 0) length = readUint7();
else if(next == Tag.INT8) length = readInt8();
else if(next == Tag.INT16) length = readInt16();
else if(next == Tag.INT32) length = readInt32();
else throw new IllegalStateException();
if(length < 0) throw new FormatException();
return length;
}
private boolean hasLength() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next >= 0 || next == Tag.INT8 || next == Tag.INT16
|| next == Tag.INT32;
}
public boolean hasBytes() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.BYTES;
}
public byte[] readBytes() throws IOException {
return readBytes(maxBytesLength);
}
public byte[] readBytes(int maxLength) throws IOException {
if(!hasBytes()) throw new FormatException();
consumeLookahead();
@@ -305,7 +300,7 @@ class ReaderImpl implements Reader {
}
public boolean hasList() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.LIST;
}
@@ -320,13 +315,12 @@ class ReaderImpl implements Reader {
}
private boolean hasEnd() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.END;
}
private void readEnd() throws IOException {
if(!hasLookahead) throw new IllegalStateException();
if(!hasEnd()) throw new FormatException();
consumeLookahead();
}
@@ -340,8 +334,8 @@ class ReaderImpl implements Reader {
if(hasInt64()) return Long.valueOf(readInt64());
if(hasFloat32()) return Float.valueOf(readFloat32());
if(hasFloat64()) return Double.valueOf(readFloat64());
if(hasString()) return readString();
if(hasBytes()) return new Bytes(readBytes());
if(hasString()) return readString(maxStringLength);
if(hasBytes()) return new Bytes(readBytes(maxBytesLength));
if(hasList()) return readList(Object.class);
if(hasMap()) return readMap(Object.class, Object.class);
if(hasNull()) {
@@ -378,14 +372,8 @@ class ReaderImpl implements Reader {
}
}
public boolean hasListStart() throws IOException {
if(!hasLookahead) readLookahead(true);
if(eof) return false;
return next == Tag.LIST;
}
public void readListStart() throws IOException {
if(!hasListStart()) throw new FormatException();
if(!hasList()) throw new FormatException();
consumeLookahead();
}
@@ -398,7 +386,7 @@ class ReaderImpl implements Reader {
}
public boolean hasMap() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.MAP;
}
@@ -415,14 +403,8 @@ class ReaderImpl implements Reader {
return Collections.unmodifiableMap(m);
}
public boolean hasMapStart() throws IOException {
if(!hasLookahead) readLookahead(true);
if(eof) return false;
return next == Tag.MAP;
}
public void readMapStart() throws IOException {
if(!hasMapStart()) throw new FormatException();
if(!hasMap()) throw new FormatException();
consumeLookahead();
}
@@ -434,8 +416,28 @@ class ReaderImpl implements Reader {
readEnd();
}
public boolean hasStruct(int id) throws IOException {
if(id < 0 || id > 255) throw new IllegalArgumentException();
if(!hasLookahead) readLookahead();
if(eof) return false;
return (nextStructId & 0xFF) == id;
}
public void readStructStart(int id) throws IOException {
if(!hasStruct(id)) throw new FormatException();
consumeLookahead();
}
public boolean hasStructEnd() throws IOException {
return hasEnd();
}
public void readStructEnd() throws IOException {
readEnd();
}
public boolean hasNull() throws IOException {
if(!hasLookahead) readLookahead(true);
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == Tag.NULL;
}
@@ -444,16 +446,4 @@ class ReaderImpl implements Reader {
if(!hasNull()) throw new FormatException();
consumeLookahead();
}
public boolean hasStruct(int id) throws IOException {
if(id < 0 || id > 255) throw new IllegalArgumentException();
if(!hasLookahead) readLookahead(true);
if(eof) return false;
return id == (0xFF & nextNext);
}
public void readStructId(int id) throws IOException {
if(!hasStruct(id)) throw new FormatException();
consumeLookahead();
}
}

View File

@@ -5,31 +5,35 @@ import net.sf.briar.api.serial.SerialComponent;
class SerialComponentImpl implements SerialComponent {
public int getSerialisedListEndLength() {
// END tag
return 1;
}
public int getSerialisedListStartLength() {
// LIST tag
return 1;
}
public int getSerialisedStructIdLength(int id) {
if(id < 0 || id > 255) throw new IllegalArgumentException();
return id < 32 ? 1 : 2;
public int getSerialisedListEndLength() {
// END tag
return 1;
}
public int getSerialisedStructStartLength(int id) {
// STRUCT tag, ID
return 2;
}
public int getSerialisedStructEndLength() {
// END tag
return 1;
}
public int getSerialisedUniqueIdLength() {
// BYTES tag, length spec, bytes
return 1 + getSerialisedLengthSpecLength(UniqueId.LENGTH)
+ UniqueId.LENGTH;
+ UniqueId.LENGTH;
}
private int getSerialisedLengthSpecLength(int length) {
if(length < 0) throw new IllegalArgumentException();
if(length < 128) return 1; // Uint7
if(length < Short.MAX_VALUE) return 3; // Int16
return 5; // Int32
// Uint7, int16 or int32
return length <= Byte.MAX_VALUE ? 1 : length <= Short.MAX_VALUE ? 3 : 5;
}
}

View File

@@ -2,19 +2,19 @@ package net.sf.briar.serial;
interface Tag {
byte FALSE = (byte) 0xFF; // 1111 1111
byte TRUE = (byte) 0xFE; // 1111 1110
byte INT8 = (byte) 0xFD; // 1111 1101
byte INT16 = (byte) 0xFC; // 1111 1100
byte INT32 = (byte) 0xFB; // 1111 1011
byte INT64 = (byte) 0xFA; // 1111 1010
byte FLOAT32 = (byte) 0xF9; // 1111 1001
byte FLOAT64 = (byte) 0xF8; // 1111 1000
byte STRING = (byte) 0xF7; // 1111 0111
byte BYTES = (byte) 0xF6; // 1111 0110
byte LIST = (byte) 0xF5; // 1111 0111
byte MAP = (byte) 0xF4; // 1111 0100
byte END = (byte) 0xF3; // 1111 0011
byte NULL = (byte) 0xF2; // 1111 0010
byte STRUCT = (byte) 0xF1; // 1111 0001
byte FALSE = (byte) 0xFF;
byte TRUE = (byte) 0xFE;
byte INT8 = (byte) 0xFD;
byte INT16 = (byte) 0xFC;
byte INT32 = (byte) 0xFB;
byte INT64 = (byte) 0xFA;
byte FLOAT32 = (byte) 0xF9;
byte FLOAT64 = (byte) 0xF8;
byte STRING = (byte) 0xF7;
byte BYTES = (byte) 0xF6;
byte LIST = (byte) 0xF5;
byte MAP = (byte) 0xF4;
byte STRUCT = (byte) 0xF3;
byte END = (byte) 0xF2;
byte NULL = (byte) 0xF1;
}

View File

@@ -177,16 +177,20 @@ class WriterImpl implements Writer {
write(Tag.END);
}
public void writeNull() throws IOException {
write(Tag.NULL);
}
public void writeStructId(int id) throws IOException {
public void writeStructStart(int id) throws IOException {
if(id < 0 || id > 255) throw new IllegalArgumentException();
write(Tag.STRUCT);
write((byte) id);
}
public void writeStructEnd() throws IOException {
write(Tag.END);
}
public void writeNull() throws IOException {
write(Tag.NULL);
}
private void write(byte b) throws IOException {
out.write(b);
for(Consumer c : consumers) c.write(b);

View File

@@ -165,14 +165,17 @@ public class PacketReaderImplTest extends BriarTestCase {
private byte[] createAck(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(ACK);
w.writeStructStart(ACK);
w.writeListStart();
while(out.size() + serial.getSerialisedUniqueIdLength()
+ serial.getSerialisedListEndLength()
+ serial.getSerialisedStructEndLength()
< MAX_PACKET_LENGTH) {
w.writeBytes(TestUtils.getRandomId());
}
if(tooBig) w.writeBytes(TestUtils.getRandomId());
w.writeListEnd();
w.writeStructEnd();
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
return out.toByteArray();
}
@@ -180,23 +183,27 @@ public class PacketReaderImplTest extends BriarTestCase {
private byte[] createEmptyAck() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(ACK);
w.writeStructStart(ACK);
w.writeListStart();
w.writeListEnd();
w.writeStructEnd();
return out.toByteArray();
}
private byte[] createOffer(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(OFFER);
w.writeStructStart(OFFER);
w.writeListStart();
while(out.size() + serial.getSerialisedUniqueIdLength()
+ serial.getSerialisedListEndLength()
+ serial.getSerialisedStructEndLength()
< MAX_PACKET_LENGTH) {
w.writeBytes(TestUtils.getRandomId());
}
if(tooBig) w.writeBytes(TestUtils.getRandomId());
w.writeListEnd();
w.writeStructEnd();
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
return out.toByteArray();
}
@@ -204,24 +211,27 @@ public class PacketReaderImplTest extends BriarTestCase {
private byte[] createEmptyOffer() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(OFFER);
w.writeStructStart(OFFER);
w.writeListStart();
w.writeListEnd();
w.writeStructEnd();
return out.toByteArray();
}
private byte[] createRequest(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(REQUEST);
// Allow one byte for the STRUCT tag, one byte for the REQUEST tag,
w.writeStructStart(REQUEST);
// Allow one byte for the STRUCT tag, one byte for the struct ID,
// one byte for the padding length as a uint7, one byte for the BYTES
// tag, and five bytes for the length of the byte array as an int32
int size = MAX_PACKET_LENGTH - 9;
// tag, five bytes for the length of the byte array as an int32, and
// one byte for the END tag
int size = MAX_PACKET_LENGTH - 10;
if(tooBig) size++;
assertTrue(size > Short.MAX_VALUE);
w.writeUint7((byte) 0);
w.writeBytes(new byte[size]);
w.writeStructEnd();
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
return out.toByteArray();
}
@@ -229,9 +239,10 @@ public class PacketReaderImplTest extends BriarTestCase {
private byte[] createRequest(byte[] bitmap) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(REQUEST);
w.writeStructStart(REQUEST);
w.writeUint7((byte) 0);
w.writeBytes(bitmap);
w.writeStructEnd();
return out.toByteArray();
}
}

View File

@@ -61,9 +61,9 @@ public class PacketWriterImplTest extends BriarTestCase {
b.set(15);
w.writeRequest(new Request(b, 16));
// STRUCT tag, struct ID 5, 0 as uint7, BYTES tag, length 2 as uint7,
// 0xD959
// bitmap 0xD959, END tag
byte[] output = out.toByteArray();
assertEquals("F1" + "05" + "00" + "F6" + "02" + "D959",
assertEquals("F3" + "05" + "00" + "F6" + "02" + "D959" + "F2",
StringUtils.toHexString(output));
}
@@ -85,9 +85,9 @@ public class PacketWriterImplTest extends BriarTestCase {
b.set(12);
w.writeRequest(new Request(b, 13));
// STRUCT tag, struct ID 5, 3 as uint7, BYTES tag, length 2 as uint7,
// 0xD959
// bitmap 0x59D8, END tag
byte[] output = out.toByteArray();
assertEquals("F1" + "05" + "03" + "F6" + "02" + "59D8",
assertEquals("F3" + "05" + "03" + "F6" + "02" + "59D8" + "F2",
StringUtils.toHexString(output));
}
}

View File

@@ -122,8 +122,8 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadString() throws Exception {
setContents("F703666F6F" + "F700");
assertEquals("foo", r.readString());
assertEquals("", r.readString());
assertEquals("foo", r.readString(1000));
assertEquals("", r.readString(1000));
assertTrue(r.eof());
}
@@ -140,8 +140,8 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadBytes() throws Exception {
setContents("F603010203" + "F600");
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes());
assertArrayEquals(new byte[] {}, r.readBytes());
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(1000));
assertArrayEquals(new byte[] {}, r.readBytes(1000));
assertTrue(r.eof());
}
@@ -157,7 +157,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadList() throws Exception {
setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F3");
setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
List<Object> l = r.readList(Object.class);
assertNotNull(l);
assertEquals(3, l.size());
@@ -169,7 +169,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadListTypeSafe() throws Exception {
setContents("F5" + "01" + "02" + "03" + "F3");
setContents("F5" + "01" + "02" + "03" + "F2");
List<Byte> l = r.readList(Byte.class);
assertNotNull(l);
assertEquals(3, l.size());
@@ -181,7 +181,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadListTypeSafeThrowsFormatException() throws Exception {
setContents("F5" + "01" + "F703666F6F" + "03" + "F3");
setContents("F5" + "01" + "F703666F6F" + "03" + "F2");
// Trying to read a mixed list as a list of bytes should throw a
// FormatException
try {
@@ -192,7 +192,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadMap() throws Exception {
setContents("F4" + "F703666F6F" + "7B" + "F600" + "F2" + "F3");
setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
Map<Object, Object> m = r.readMap(Object.class, Object.class);
assertNotNull(m);
assertEquals(2, m.size());
@@ -205,7 +205,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadMapTypeSafe() throws Exception {
setContents("F4" + "F703666F6F" + "7B" + "F700" + "F2" + "F3");
setContents("F4" + "F703666F6F" + "7B" + "F700" + "F1" + "F2");
Map<String, Byte> m = r.readMap(String.class, Byte.class);
assertNotNull(m);
assertEquals(2, m.size());
@@ -217,8 +217,8 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testMapKeysMustBeUnique() throws Exception {
setContents("F4" + "F703666F6F" + "01" + "F703626172" + "02" + "F3"
+ "F4" + "F703666F6F" + "01" + "F703666F6F" + "02" + "F3");
setContents("F4" + "F703666F6F" + "01" + "F703626172" + "02" + "F2"
+ "F4" + "F703666F6F" + "01" + "F703666F6F" + "02" + "F2");
// The first map has unique keys
Map<String, Byte> m = r.readMap(String.class, Byte.class);
assertNotNull(m);
@@ -234,13 +234,13 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadDelimitedListElements() throws Exception {
setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F3");
assertTrue(r.hasListStart());
setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
assertTrue(r.hasList());
r.readListStart();
assertFalse(r.hasListEnd());
assertEquals((byte) 1, r.readIntAny());
assertFalse(r.hasListEnd());
assertEquals("foo", r.readString());
assertEquals("foo", r.readString(1000));
assertFalse(r.hasListEnd());
assertEquals((short) 128, r.readIntAny());
assertTrue(r.hasListEnd());
@@ -250,7 +250,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadDelimitedListTypeSafe() throws Exception {
setContents("F5" + "01" + "02" + "03" + "F3");
setContents("F5" + "01" + "02" + "03" + "F2");
List<Byte> l = r.readList(Byte.class);
assertNotNull(l);
assertEquals(3, l.size());
@@ -262,15 +262,15 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadDelimitedMapEntries() throws Exception {
setContents("F4" + "F703666F6F" + "7B" + "F600" + "F2" + "F3");
assertTrue(r.hasMapStart());
setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
assertTrue(r.hasMap());
r.readMapStart();
assertFalse(r.hasMapEnd());
assertEquals("foo", r.readString());
assertEquals("foo", r.readString(1000));
assertFalse(r.hasMapEnd());
assertEquals((byte) 123, r.readIntAny());
assertFalse(r.hasMapEnd());
assertArrayEquals(new byte[] {}, r.readBytes());
assertArrayEquals(new byte[] {}, r.readBytes(1000));
assertFalse(r.hasMapEnd());
assertTrue(r.hasNull());
r.readNull();
@@ -281,7 +281,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadDelimitedMapTypeSafe() throws Exception {
setContents("F4" + "F703666F6F" + "7B" + "F700" + "F2" + "F3");
setContents("F4" + "F703666F6F" + "7B" + "F700" + "F1" + "F2");
Map<String, Byte> m = r.readMap(String.class, Byte.class);
assertNotNull(m);
assertEquals(2, m.size());
@@ -294,8 +294,8 @@ public class ReaderImplTest extends BriarTestCase {
@Test
@SuppressWarnings("unchecked")
public void testReadNestedMapsAndLists() throws Exception {
setContents("F4" + "F4" + "F703666F6F" + "7B" + "F3"
+ "F5" + "01" + "F3" + "F3");
setContents("F4" + "F4" + "F703666F6F" + "7B" + "F2"
+ "F5" + "01" + "F2" + "F2");
Map<Object, Object> m = r.readMap(Object.class, Object.class);
assertNotNull(m);
assertEquals(1, m.size());
@@ -313,23 +313,34 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testMaxLengthAppliesInsideMap() throws Exception {
setContents("F4" + "F703666F6F" + "F603010203" + "F3");
public void testMaxStringLengthAppliesInsideMap() throws Exception {
setContents("F4" + "F703666F6F" + "F603010203" + "F2");
r.setMaxStringLength(3);
r.setMaxBytesLength(3);
Map<String, Bytes> m = r.readMap(String.class, Bytes.class);
assertTrue(r.eof());
String key = "foo";
Bytes value = new Bytes(new byte[] {1, 2, 3});
assertEquals(Collections.singletonMap(key, value), m);
// The max string length should be applied inside the map
setContents("F4" + "F703666F6F" + "F603010203" + "F3");
setContents("F4" + "F703666F6F" + "F603010203" + "F2");
r.setMaxStringLength(2);
try {
r.readMap(String.class, Bytes.class);
fail();
} catch(FormatException expected) {}
}
@Test
public void testMaxBytesLengthAppliesInsideMap() throws Exception {
setContents("F4" + "F703666F6F" + "F603010203" + "F2");
r.setMaxBytesLength(3);
Map<String, Bytes> m = r.readMap(String.class, Bytes.class);
assertTrue(r.eof());
String key = "foo";
Bytes value = new Bytes(new byte[] {1, 2, 3});
assertEquals(Collections.singletonMap(key, value), m);
// The max bytes length should be applied inside the map
setContents("F4" + "F703666F6F" + "F603010203" + "F3");
setContents("F4" + "F703666F6F" + "F603010203" + "F2");
r.setMaxBytesLength(2);
try {
r.readMap(String.class, Bytes.class);
@@ -337,6 +348,19 @@ public class ReaderImplTest extends BriarTestCase {
} catch(FormatException expected) {}
}
@Test
public void testReadStruct() throws Exception {
// Two structs with IDs 0 and 255, each containing 1 as a uint7
setContents("F300" + "01" + "F2" + "F3FF" + "01" + "F2");
r.readStructStart(0);
assertEquals(1, r.readUint7());
r.readStructEnd();
r.readStructStart(255);
assertEquals(1, r.readUint7());
r.readStructEnd();
assertTrue(r.eof());
}
@Test
public void testReadEmptyInput() throws Exception {
setContents("");

View File

@@ -155,7 +155,7 @@ public class WriterImplTest extends BriarTestCase {
for(int i = 0; i < 16; i++) l.add(i);
w.writeList(l);
// LIST tag, elements as uint7, END tag
checkContents("F5" + "000102030405060708090A0B0C0D0E0F" + "F3");
checkContents("F5" + "000102030405060708090A0B0C0D0E0F" + "F2");
}
@Test
@@ -166,7 +166,7 @@ public class WriterImplTest extends BriarTestCase {
l.add(2);
w.writeList(l);
// LIST tag, 1 as uint7, null, 2 as uint7, END tag
checkContents("F5" + "01" + "F2" + "02" + "F3");
checkContents("F5" + "01" + "F1" + "02" + "F2");
}
@Test
@@ -178,7 +178,7 @@ public class WriterImplTest extends BriarTestCase {
// MAP tag, entries as uint7, END tag
checkContents("F4" + "0001" + "0102" + "0203" + "0304" + "0405"
+ "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C"
+ "0C0D" + "0D0E" + "0E0F" + "0F10" + "F3");
+ "0C0D" + "0D0E" + "0E0F" + "0F10" + "F2");
}
@Test
@@ -189,7 +189,7 @@ public class WriterImplTest extends BriarTestCase {
w.writeIntAny(128L); // Written as int16
w.writeListEnd();
// LIST tag, 1 as uint7, "foo" as string, 128 as int16, END tag
checkContents("F5" + "01" + "F703666F6F" + "FC0080" + "F3");
checkContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
}
@Test
@@ -202,7 +202,7 @@ public class WriterImplTest extends BriarTestCase {
w.writeMapEnd();
// MAP tag, "foo" as string, 123 as uint7, byte[0] as bytes,
// NULL tag, END tag
checkContents("F4" + "F703666F6F" + "7B" + "F600" + "F2" + "F3");
checkContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
}
@Test
@@ -216,22 +216,22 @@ public class WriterImplTest extends BriarTestCase {
w.writeMap(m1);
// MAP tag, MAP tag, "foo" as string, 123 as uint7, END tag,
// LIST tag, 1 as uint7, END tag, END tag
checkContents("F4" + "F4" + "F703666F6F" + "7B" + "F3"
+ "F5" + "01" + "F3" + "F3");
checkContents("F4" + "F4" + "F703666F6F" + "7B" + "F2"
+ "F5" + "01" + "F2" + "F2");
}
@Test
public void testWriteStruct() throws IOException {
w.writeStructStart(123);
w.writeStructEnd();
// STRUCT tag, 123 as struct ID, END tag
checkContents("F3" + "7B" + "F2");
}
@Test
public void testWriteNull() throws IOException {
w.writeNull();
checkContents("F2");
}
@Test
public void testWriteStructId() throws IOException {
w.writeStructId(32);
w.writeStructId(255);
// STRUCT tag, 32 as uint8, STRUCT tag, 255 as uint8
checkContents("F1" + "20" + "F1" + "FF");
checkContents("F1");
}
private void checkContents(String hex) throws IOException {