Compact encodings for integers, strings and byte arrays.

This adds complexity but will save a lot of bandwidth, as most of the
strings and byte arrays we want to send are less than 128 bytes.

The extra complexity isn't exposed outside of the serial component.
This commit is contained in:
akwizgran
2014-02-07 18:50:28 +00:00
parent 6296f0f790
commit 92d5fb4f1d
8 changed files with 684 additions and 164 deletions

View File

@@ -1,14 +1,21 @@
package org.briarproject.serial;
import static org.briarproject.serial.Tag.BYTES;
import static org.briarproject.serial.Tag.BYTES_16;
import static org.briarproject.serial.Tag.BYTES_32;
import static org.briarproject.serial.Tag.BYTES_8;
import static org.briarproject.serial.Tag.END;
import static org.briarproject.serial.Tag.FALSE;
import static org.briarproject.serial.Tag.FLOAT;
import static org.briarproject.serial.Tag.INTEGER;
import static org.briarproject.serial.Tag.INTEGER_16;
import static org.briarproject.serial.Tag.INTEGER_32;
import static org.briarproject.serial.Tag.INTEGER_64;
import static org.briarproject.serial.Tag.INTEGER_8;
import static org.briarproject.serial.Tag.LIST;
import static org.briarproject.serial.Tag.MAP;
import static org.briarproject.serial.Tag.NULL;
import static org.briarproject.serial.Tag.STRING;
import static org.briarproject.serial.Tag.STRING_16;
import static org.briarproject.serial.Tag.STRING_32;
import static org.briarproject.serial.Tag.STRING_8;
import static org.briarproject.serial.Tag.STRUCT;
import static org.briarproject.serial.Tag.TRUE;
@@ -82,20 +89,6 @@ class ReaderImpl implements Reader {
readIntoBuffer(buf, length, consume);
}
private int readInt32(boolean consume) throws IOException {
readIntoBuffer(4, consume);
int value = 0;
for(int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8);
return value;
}
private long readInt64(boolean consume) throws IOException {
readIntoBuffer(8, consume);
long value = 0;
for(int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
return value;
}
private void skip(int length) throws IOException {
while(length > 0) {
int read = in.read(buf, 0, Math.min(length, buf.length));
@@ -154,18 +147,56 @@ class ReaderImpl implements Reader {
public boolean hasInteger() throws IOException {
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == INTEGER;
return next == INTEGER_8 || next == INTEGER_16 || next == INTEGER_32 ||
next == INTEGER_64;
}
public long readInteger() throws IOException {
if(!hasInteger()) throw new FormatException();
consumeLookahead();
if(next == INTEGER_8) return readInt8(true);
if(next == INTEGER_16) return readInt16(true);
if(next == INTEGER_32) return readInt32(true);
return readInt64(true);
}
private int readInt8(boolean consume) throws IOException {
readIntoBuffer(1, consume);
return buf[0];
}
private short readInt16(boolean consume) throws IOException {
readIntoBuffer(2, consume);
short value = (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
if(value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)
throw new FormatException();
return value;
}
private int readInt32(boolean consume) throws IOException {
readIntoBuffer(4, consume);
int value = 0;
for(int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8);
if(value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
throw new FormatException();
return value;
}
private long readInt64(boolean consume) throws IOException {
readIntoBuffer(8, consume);
long value = 0;
for(int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
if(value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE)
throw new FormatException();
return value;
}
public void skipInteger() throws IOException {
if(!hasInteger()) throw new FormatException();
skip(8);
if(next == INTEGER_8) skip(1);
else if(next == INTEGER_16) skip(2);
else if(next == INTEGER_32) skip(4);
else skip(8);
hasLookahead = false;
}
@@ -178,7 +209,10 @@ class ReaderImpl implements Reader {
public double readFloat() throws IOException {
if(!hasFloat()) throw new FormatException();
consumeLookahead();
return Double.longBitsToDouble(readInt64(true));
readIntoBuffer(8, true);
long value = 0;
for(int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
return Double.longBitsToDouble(value);
}
public void skipFloat() throws IOException {
@@ -190,22 +224,29 @@ class ReaderImpl implements Reader {
public boolean hasString() throws IOException {
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == STRING;
return next == STRING_8 || next == STRING_16 || next == STRING_32;
}
public String readString(int maxLength) throws IOException {
if(!hasString()) throw new FormatException();
consumeLookahead();
int length = readInt32(true);
int length = readStringLength(true);
if(length < 0 || length > maxLength) throw new FormatException();
if(length == 0) return "";
readIntoBuffer(length, true);
return new String(buf, 0, length, "UTF-8");
}
private int readStringLength(boolean consume) throws IOException {
if(next == STRING_8) return readInt8(consume);
if(next == STRING_16) return readInt16(consume);
if(next == STRING_32) return readInt32(consume);
throw new FormatException();
}
public void skipString(int maxLength) throws IOException {
if(!hasString()) throw new FormatException();
int length = readInt32(false);
int length = readStringLength(false);
if(length < 0 || length > maxLength) throw new FormatException();
skip(length);
hasLookahead = false;
@@ -214,13 +255,13 @@ class ReaderImpl implements Reader {
public boolean hasBytes() throws IOException {
if(!hasLookahead) readLookahead();
if(eof) return false;
return next == BYTES;
return next == BYTES_8 || next == BYTES_16 || next == BYTES_32;
}
public byte[] readBytes(int maxLength) throws IOException {
if(!hasBytes()) throw new FormatException();
consumeLookahead();
int length = readInt32(true);
int length = readBytesLength(true);
if(length < 0 || length > maxLength) throw new FormatException();
if(length == 0) return EMPTY_BUFFER;
byte[] b = new byte[length];
@@ -228,9 +269,16 @@ class ReaderImpl implements Reader {
return b;
}
private int readBytesLength(boolean consume) throws IOException {
if(next == BYTES_8) return readInt8(consume);
if(next == BYTES_16) return readInt16(consume);
if(next == BYTES_32) return readInt32(consume);
throw new FormatException();
}
public void skipBytes(int maxLength) throws IOException {
if(!hasBytes()) throw new FormatException();
int length = readInt32(false);
int length = readBytesLength(false);
if(length < 0 || length > maxLength) throw new FormatException();
skip(length);
hasLookahead = false;

View File

@@ -26,7 +26,13 @@ class SerialComponentImpl implements SerialComponent {
}
public int getSerialisedUniqueIdLength() {
// BYTES tag, 32-bit length, bytes
return 5 + UniqueId.LENGTH;
// BYTES_8, BYTES_16 or BYTES_32 tag, length, bytes
return 1 + getLengthBytes(UniqueId.LENGTH) + UniqueId.LENGTH;
}
private int getLengthBytes(int length) {
if(length <= Byte.MAX_VALUE) return 1;
if(length <= Short.MAX_VALUE) return 2;
return 4;
}
}

View File

@@ -2,15 +2,22 @@ package org.briarproject.serial;
interface Tag {
byte FALSE = 0;
byte TRUE = 1;
byte INTEGER = 2;
byte FLOAT = 3;
byte STRING = 4;
byte BYTES = 5;
byte LIST = 6;
byte MAP = 7;
byte STRUCT = 8;
byte END = 9;
byte NULL = 10;
byte FALSE = 0x00;
byte TRUE = 0x01;
byte INTEGER_8 = 0x02;
byte INTEGER_16 = 0x03;
byte INTEGER_32 = 0x04;
byte INTEGER_64 = 0x05;
byte FLOAT = 0x06;
byte STRING_8 = 0x07;
byte STRING_16 = 0x08;
byte STRING_32 = 0x09;
byte BYTES_8 = 0x0A;
byte BYTES_16 = 0x0B;
byte BYTES_32 = 0x0C;
byte LIST = 0x0D;
byte MAP = 0x0E;
byte STRUCT = 0x0F;
byte END = 0x10;
byte NULL = 0x11;
}

View File

@@ -1,9 +1,17 @@
package org.briarproject.serial;
import static org.briarproject.serial.Tag.BYTES_16;
import static org.briarproject.serial.Tag.BYTES_32;
import static org.briarproject.serial.Tag.BYTES_8;
import static org.briarproject.serial.Tag.FALSE;
import static org.briarproject.serial.Tag.FLOAT;
import static org.briarproject.serial.Tag.INTEGER;
import static org.briarproject.serial.Tag.STRING;
import static org.briarproject.serial.Tag.INTEGER_16;
import static org.briarproject.serial.Tag.INTEGER_32;
import static org.briarproject.serial.Tag.INTEGER_64;
import static org.briarproject.serial.Tag.INTEGER_8;
import static org.briarproject.serial.Tag.STRING_16;
import static org.briarproject.serial.Tag.STRING_32;
import static org.briarproject.serial.Tag.STRING_8;
import static org.briarproject.serial.Tag.TRUE;
import java.io.IOException;
@@ -49,20 +57,43 @@ class WriterImpl implements Writer {
else write(FALSE);
}
public void writeInteger(long l) throws IOException {
write(INTEGER);
writeInt64(l);
public void writeInteger(long i) throws IOException {
if(i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
write(INTEGER_8);
write((byte) i);
} else if(i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
write(INTEGER_16);
writeInt16((short) i);
} else if(i >= Integer.MIN_VALUE && i <= Integer.MAX_VALUE) {
write(INTEGER_32);
writeInt32((int) i);
} else {
write(INTEGER_64);
writeInt64(i);
}
}
private void writeInt64(long l) throws IOException {
write((byte) (l >> 56));
write((byte) ((l << 8) >> 56));
write((byte) ((l << 16) >> 56));
write((byte) ((l << 24) >> 56));
write((byte) ((l << 32) >> 56));
write((byte) ((l << 40) >> 56));
write((byte) ((l << 48) >> 56));
write((byte) ((l << 56) >> 56));
private void writeInt16(short i) throws IOException {
write((byte) (i >> 8));
write((byte) ((i << 8) >> 8));
}
private void writeInt32(int i) throws IOException {
write((byte) (i >> 24));
write((byte) ((i << 8) >> 24));
write((byte) ((i << 16) >> 24));
write((byte) ((i << 24) >> 24));
}
private void writeInt64(long i) throws IOException {
write((byte) (i >> 56));
write((byte) ((i << 8) >> 56));
write((byte) ((i << 16) >> 56));
write((byte) ((i << 24) >> 56));
write((byte) ((i << 32) >> 56));
write((byte) ((i << 40) >> 56));
write((byte) ((i << 48) >> 56));
write((byte) ((i << 56) >> 56));
}
public void writeFloat(double d) throws IOException {
@@ -72,22 +103,30 @@ class WriterImpl implements Writer {
public void writeString(String s) throws IOException {
byte[] b = s.getBytes("UTF-8");
write(STRING);
writeLength(b.length);
if(b.length <= Byte.MAX_VALUE) {
write(STRING_8);
write((byte) b.length);
} else if(b.length <= Short.MAX_VALUE) {
write(STRING_16);
writeInt16((short) b.length);
} else {
write(STRING_32);
writeInt32(b.length);
}
write(b);
}
private void writeLength(int i) throws IOException {
assert i >= 0;
write((byte) (i >> 24));
write((byte) ((i << 8) >> 24));
write((byte) ((i << 16) >> 24));
write((byte) ((i << 24) >> 24));
}
public void writeBytes(byte[] b) throws IOException {
write(Tag.BYTES);
writeLength(b.length);
if(b.length <= Byte.MAX_VALUE) {
write(BYTES_8);
write((byte) b.length);
} else if(b.length <= Short.MAX_VALUE) {
write(BYTES_16);
writeInt16((short) b.length);
} else {
write(BYTES_32);
writeInt32(b.length);
}
write(b);
}

View File

@@ -76,11 +76,10 @@
</javac>
</target>
<target name='test' depends='compile'>
<junit printsummary='on' fork='yes' forkmode='once' haltonfailure='yes' showoutput='true'>
<junit printsummary='on' fork='yes' forkmode='once' haltonfailure='yes'>
<assertions>
<enable/>
</assertions>
<formatter type="plain" usefile="false"/>
<classpath>
<fileset refid='test-jars'/>
<fileset refid='desktop-jars'/>

View File

@@ -12,7 +12,7 @@ import org.briarproject.api.UniqueId;
public class TestUtils {
private static final AtomicInteger nextTestDir =
new AtomicInteger((int) (Math.random() * 1000 * 1000));
new AtomicInteger((int) (Math.random() * 1000 * 1000));
private static final Random random = new Random();
public static void delete(File f) {
@@ -45,10 +45,10 @@ public class TestUtils {
return b;
}
public static String createRandomString(int length) throws Exception {
StringBuilder s = new StringBuilder(length);
public static String createRandomString(int length) {
char[] c = new char[length];
for(int i = 0; i < length; i++)
s.append((char) ('a' + random.nextInt(26)));
return s.toString();
c[i] = (char) ('a' + random.nextInt(26));
return new String(c);
}
}

View File

@@ -5,6 +5,7 @@ import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.FormatException;
import org.briarproject.util.StringUtils;
import org.junit.Test;
@@ -31,31 +32,128 @@ public class ReaderImplTest extends BriarTestCase {
}
@Test
public void testReadInteger() throws Exception {
setContents("02" + "0000000000000000" + "02" + "FFFFFFFFFFFFFFFF"
+ "02" + "7FFFFFFFFFFFFFFF" + "02" + "8000000000000000");
public void testReadInt8() throws Exception {
setContents("02" + "00" + "02" + "FF"
+ "02" + "7F" + "02" + "80");
assertEquals(0, r.readInteger());
assertEquals(-1, r.readInteger());
assertEquals(Byte.MAX_VALUE, r.readInteger());
assertEquals(Byte.MIN_VALUE, r.readInteger());
assertTrue(r.eof());
}
@Test
public void testSkipInt8() throws Exception {
setContents("02" + "00");
r.skipInteger();
assertTrue(r.eof());
}
@Test
public void testReadInt16() throws Exception {
setContents("03" + "0080" + "03" + "FF7F"
+ "03" + "7FFF" + "03" + "8000");
assertEquals(Byte.MAX_VALUE + 1, r.readInteger());
assertEquals(Byte.MIN_VALUE - 1, r.readInteger());
assertEquals(Short.MAX_VALUE, r.readInteger());
assertEquals(Short.MIN_VALUE, r.readInteger());
assertTrue(r.eof());
}
@Test
public void testSkipInt16() throws Exception {
setContents("03" + "0080");
r.skipInteger();
assertTrue(r.eof());
}
@Test
public void testReadInt32() throws Exception {
setContents("04" + "00008000" + "04" + "FFFF7FFF"
+ "04" + "7FFFFFFF" + "04" + "80000000");
assertEquals(Short.MAX_VALUE + 1, r.readInteger());
assertEquals(Short.MIN_VALUE - 1, r.readInteger());
assertEquals(Integer.MAX_VALUE, r.readInteger());
assertEquals(Integer.MIN_VALUE, r.readInteger());
assertTrue(r.eof());
}
@Test
public void testSkipInt32() throws Exception {
setContents("04" + "00008000");
r.skipInteger();
assertTrue(r.eof());
}
@Test
public void testReadInt64() throws Exception {
setContents("05" + "0000000080000000" + "05" + "FFFFFFFF7FFFFFFF"
+ "05" + "7FFFFFFFFFFFFFFF" + "05" + "8000000000000000");
assertEquals(Integer.MAX_VALUE + 1L, r.readInteger());
assertEquals(Integer.MIN_VALUE - 1L, r.readInteger());
assertEquals(Long.MAX_VALUE, r.readInteger());
assertEquals(Long.MIN_VALUE, r.readInteger());
assertTrue(r.eof());
}
@Test
public void testSkipInteger() throws Exception {
setContents("02" + "0000000000000000");
public void testSkipInt64() throws Exception {
setContents("05" + "0000000080000000");
r.skipInteger();
assertTrue(r.eof());
}
@Test
public void testIntegersMustHaveMinimalLength() throws Exception {
// INTEGER_16 could be encoded as INTEGER_8
setContents("02" + "7F" + "03" + "007F");
assertEquals(Byte.MAX_VALUE, r.readInteger());
try {
r.readInteger();
fail();
} catch(FormatException expected) {}
setContents("02" + "80" + "03" + "FF80");
assertEquals(Byte.MIN_VALUE, r.readInteger());
try {
r.readInteger();
fail();
} catch(FormatException expected) {}
// INTEGER_32 could be encoded as INTEGER_16
setContents("03" + "7FFF" + "04" + "00007FFF");
assertEquals(Short.MAX_VALUE, r.readInteger());
try {
r.readInteger();
fail();
} catch(FormatException expected) {}
setContents("03" + "8000" + "04" + "FFFF8000");
assertEquals(Short.MIN_VALUE, r.readInteger());
try {
r.readInteger();
fail();
} catch(FormatException expected) {}
// INTEGER_64 could be encoded as INTEGER_32
setContents("04" + "7FFFFFFF" + "05" + "000000007FFFFFFF");
assertEquals(Integer.MAX_VALUE, r.readInteger());
try {
r.readInteger();
fail();
} catch(FormatException expected) {}
setContents("04" + "80000000" + "05" + "FFFFFFFF80000000");
assertEquals(Integer.MIN_VALUE, r.readInteger());
try {
r.readInteger();
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadFloat() throws Exception {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
setContents("03" + "0000000000000000" + "03" + "3FF0000000000000"
+ "03" + "4000000000000000" + "03" + "BFF0000000000000"
+ "03" + "8000000000000000" + "03" + "FFF0000000000000"
+ "03" + "7FF0000000000000" + "03" + "7FF8000000000000");
setContents("06" + "0000000000000000" + "06" + "3FF0000000000000"
+ "06" + "4000000000000000" + "06" + "BFF0000000000000"
+ "06" + "8000000000000000" + "06" + "FFF0000000000000"
+ "06" + "7FF0000000000000" + "06" + "7FF8000000000000");
assertEquals(0.0, r.readFloat());
assertEquals(1.0, r.readFloat());
assertEquals(2.0, r.readFloat());
@@ -69,25 +167,28 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testSkipFloat() throws Exception {
setContents("03" + "0000000000000000");
setContents("06" + "0000000000000000");
r.skipFloat();
assertTrue(r.eof());
}
@Test
public void testReadString() throws Exception {
// "foo" and the empty string
setContents("04" + "00000003" + "666F6F" + "04" + "00000000");
public void testReadString8() throws Exception {
String longest = TestUtils.createRandomString(Byte.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// "foo", the empty string, and 127 random letters
setContents("07" + "03" + "666F6F" + "07" + "00" +
"07" + "7F" + longHex);
assertEquals("foo", r.readString(Integer.MAX_VALUE));
assertEquals("", r.readString(Integer.MAX_VALUE));
assertEquals(longest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadStringMaxLength() throws Exception {
public void testReadString8ChecksMaxLength() throws Exception {
// "foo" twice
setContents("04" + "00000003" + "666F6F" +
"04" + "00000003" + "666F6F");
setContents("07" + "03" + "666F6F" + "07" + "03" + "666F6F");
assertEquals("foo", r.readString(3));
assertTrue(r.hasString());
try {
@@ -97,19 +198,22 @@ public class ReaderImplTest extends BriarTestCase {
}
@Test
public void testSkipString() throws Exception {
// "foo" and the empty string
setContents("04" + "00000003" + "666F6F" + "04" + "00000000");
public void testSkipString8() throws Exception {
String longest = TestUtils.createRandomString(Byte.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// "foo", the empty string, and 127 random letters
setContents("07" + "03" + "666F6F" + "07" + "00" +
"07" + "7F" + longHex);
r.skipString(Integer.MAX_VALUE);
r.skipString(Integer.MAX_VALUE);
r.skipString(Integer.MAX_VALUE);
assertTrue(r.eof());
}
@Test
public void testSkipStringMaxLength() throws Exception {
public void testSkipString8ChecksMaxLength() throws Exception {
// "foo" twice
setContents("04" + "00000003" + "666F6F" +
"04" + "00000003" + "666F6F");
setContents("07" + "03" + "666F6F" + "07" + "03" + "666F6F");
r.skipString(3);
assertTrue(r.hasString());
try {
@@ -119,19 +223,150 @@ public class ReaderImplTest extends BriarTestCase {
}
@Test
public void testReadBytes() throws Exception {
// {1, 2, 3} and {}
setContents("05" + "00000003" + "010203" + "05" + "00000000");
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(Integer.MAX_VALUE));
assertArrayEquals(new byte[] {}, r.readBytes(Integer.MAX_VALUE));
public void testReadString16() throws Exception {
String shortest = TestUtils.createRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = TestUtils.createRandomString(Short.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// 128 random letters and 2^15 -1 random letters
setContents("08" + "0080" + shortHex + "08" + "7FFF" + longHex);
assertEquals(shortest, r.readString(Integer.MAX_VALUE));
assertEquals(longest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadBytesMaxLength() throws Exception {
public void testReadString16ChecksMaxLength() throws Exception {
String shortest = TestUtils.createRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 128 random letters, twice
setContents("08" + "0080" + shortHex + "08" + "0080" + shortHex);
assertEquals(shortest, r.readString(Byte.MAX_VALUE + 1));
assertTrue(r.hasString());
try {
r.readString(Byte.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testSkipString16() throws Exception {
String shortest = TestUtils.createRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = TestUtils.createRandomString(Short.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// 128 random letters and 2^15 - 1 random letters
setContents("08" + "0080" + shortHex + "08" + "7FFF" + longHex);
r.skipString(Integer.MAX_VALUE);
r.skipString(Integer.MAX_VALUE);
assertTrue(r.eof());
}
@Test
public void testSkipString16ChecksMaxLength() throws Exception {
String shortest = TestUtils.createRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 128 random letters, twice
setContents("08" + "0080" + shortHex + "08" + "0080" + shortHex);
r.skipString(Byte.MAX_VALUE + 1);
assertTrue(r.hasString());
try {
r.skipString(Byte.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadString32() throws Exception {
String shortest = TestUtils.createRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters
setContents("09" + "00008000" + shortHex);
assertEquals(shortest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadString32ChecksMaxLength() throws Exception {
String shortest = TestUtils.createRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters, twice
setContents("09" + "00008000" + shortHex +
"09" + "00008000" + shortHex);
assertEquals(shortest, r.readString(Short.MAX_VALUE + 1));
assertTrue(r.hasString());
try {
r.readString(Short.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testSkipString32() throws Exception {
String shortest = TestUtils.createRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters, twice
setContents("09" + "00008000" + shortHex +
"09" + "00008000" + shortHex);
r.skipString(Integer.MAX_VALUE);
r.skipString(Integer.MAX_VALUE);
assertTrue(r.eof());
}
@Test
public void testSkipString32ChecksMaxLength() throws Exception {
String shortest = TestUtils.createRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters, twice
setContents("09" + "00008000" + shortHex +
"09" + "00008000" + shortHex);
r.skipString(Short.MAX_VALUE + 1);
assertTrue(r.hasString());
try {
r.skipString(Short.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testStringsMustHaveMinimalLength() throws Exception {
// STRING_16 could be encoded as STRING_8
String longest8 = TestUtils.createRandomString(Byte.MAX_VALUE);
String long8Hex = StringUtils.toHexString(longest8.getBytes("UTF-8"));
setContents("07" + "7F" + long8Hex + "08" + "007F" + long8Hex);
assertEquals(longest8, r.readString(Integer.MAX_VALUE));
try {
r.readString(Integer.MAX_VALUE);
fail();
} catch(FormatException expected) {}
// STRING_32 could be encoded as STRING_16
String longest16 = TestUtils.createRandomString(Short.MAX_VALUE);
String long16Hex = StringUtils.toHexString(longest16.getBytes("UTF-8"));
setContents("08" + "7FFF" + long16Hex + "09" + "00007FFF" + long16Hex);
assertEquals(longest16, r.readString(Integer.MAX_VALUE));
try {
r.readString(Integer.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadBytes8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// {1, 2, 3}, {}, and 127 zero bytes
setContents("0A" + "03" + "010203" + "0A" + "00" +
"0A" + "7F" + longHex);
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(Integer.MAX_VALUE));
assertArrayEquals(new byte[0], r.readBytes(Integer.MAX_VALUE));
assertArrayEquals(longest, r.readBytes(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadBytes8ChecksMaxLength() throws Exception {
// {1, 2, 3} twice
setContents("05" + "00000003" + "010203" +
"05" + "00000003" + "010203");
setContents("0A" + "03" + "010203" + "0A" + "03" + "010203");
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3));
assertTrue(r.hasBytes());
try {
@@ -141,19 +376,22 @@ public class ReaderImplTest extends BriarTestCase {
}
@Test
public void testSkipBytes() throws Exception {
// {1, 2, 3} and {}
setContents("05" + "00000003" + "010203" + "05" + "00000000");
public void testSkipBytes8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// {1, 2, 3}, {}, and 127 zero bytes
setContents("0A" + "03" + "010203" + "0A" + "00" +
"0A" + "7F" + longHex);
r.skipBytes(Integer.MAX_VALUE);
r.skipBytes(Integer.MAX_VALUE);
r.skipBytes(Integer.MAX_VALUE);
assertTrue(r.eof());
}
@Test
public void testSkipBytesMaxLength() throws Exception {
public void testSkipBytes8ChecksMaxLength() throws Exception {
// {1, 2, 3} twice
setContents("05" + "00000003" + "010203" +
"05" + "00000003" + "010203");
setContents("0A" + "03" + "010203" + "0A" + "03" + "010203");
r.skipBytes(3);
assertTrue(r.hasBytes());
try {
@@ -162,12 +400,140 @@ public class ReaderImplTest extends BriarTestCase {
} catch(FormatException expected) {}
}
@Test
public void testReadBytes16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// 128 zero bytes and 2^15 - 1 zero bytes
setContents("0B" + "0080" + shortHex + "0B" + "7FFF" + longHex);
assertArrayEquals(shortest, r.readBytes(Integer.MAX_VALUE));
assertArrayEquals(longest, r.readBytes(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadBytes16ChecksMaxLength() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 128 zero bytes, twice
setContents("0B" + "0080" + shortHex + "0B" + "0080" + shortHex);
assertArrayEquals(shortest, r.readBytes(Byte.MAX_VALUE + 1));
assertTrue(r.hasBytes());
try {
r.readBytes(Byte.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testSkipBytes16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// 128 zero bytes and 2^15 - 1 zero bytes
setContents("0B" + "0080" + shortHex + "0B" + "7FFF" + longHex);
r.skipBytes(Integer.MAX_VALUE);
r.skipBytes(Integer.MAX_VALUE);
assertTrue(r.eof());
}
@Test
public void testSkipBytes16ChecksMaxLength() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 128 zero bytes, twice
setContents("0B" + "0080" + shortHex + "0B" + "0080" + shortHex);
r.skipBytes(Byte.MAX_VALUE + 1);
assertTrue(r.hasBytes());
try {
r.skipBytes(Byte.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadBytes32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes
setContents("0C" + "00008000" + shortHex);
assertArrayEquals(shortest, r.readBytes(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadBytes32ChecksMaxLength() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes, twice
setContents("0C" + "00008000" + shortHex +
"0C" + "00008000" + shortHex);
assertArrayEquals(shortest, r.readBytes(Short.MAX_VALUE + 1));
assertTrue(r.hasBytes());
try {
r.readBytes(Short.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testSkipBytes32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes, twice
setContents("0C" + "00008000" + shortHex +
"0C" + "00008000" + shortHex);
r.skipBytes(Integer.MAX_VALUE);
r.skipBytes(Integer.MAX_VALUE);
assertTrue(r.eof());
}
@Test
public void testSkipBytes32ChecksMaxLength() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes, twice
setContents("0C" + "00008000" + shortHex +
"0C" + "00008000" + shortHex);
r.skipBytes(Short.MAX_VALUE + 1);
assertTrue(r.hasBytes());
try {
r.skipBytes(Short.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testBytesMustHaveMinimalLength() throws Exception {
// BYTES_16 could be encoded as BYTES_8
byte[] longest8 = new byte[Byte.MAX_VALUE];
String long8Hex = StringUtils.toHexString(longest8);
setContents("0A" + "7F" + long8Hex + "0B" + "007F" + long8Hex);
assertArrayEquals(longest8, r.readBytes(Integer.MAX_VALUE));
try {
r.readBytes(Integer.MAX_VALUE);
fail();
} catch(FormatException expected) {}
// BYTES_32 could be encoded as BYTES_16
byte[] longest16 = new byte[Short.MAX_VALUE];
String long16Hex = StringUtils.toHexString(longest16);
setContents("0B" + "7FFF" + long16Hex + "0C" + "00007FFF" + long16Hex);
assertArrayEquals(longest16, r.readBytes(Integer.MAX_VALUE));
try {
r.readBytes(Integer.MAX_VALUE);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadList() throws Exception {
// A list containing 2, "foo", and 128
setContents("06" + "02" + "0000000000000001" +
"04" + "00000003" + "666F6F" +
"02" + "0000000000000080" + "09");
// A list containing 1, "foo", and 128
setContents("0D" + "02" + "01" +
"07" + "03" + "666F6F" +
"03" + "0080" + "10");
r.readListStart();
assertFalse(r.hasListEnd());
assertEquals(1, r.readInteger());
@@ -182,26 +548,26 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testSkipList() throws Exception {
// A list containing 2, "foo", and 128
setContents("06" + "02" + "0000000000000001" +
"04" + "00000003" + "666F6F" +
"02" + "0000000000000080" + "09");
// A list containing 1, "foo", and 128
setContents("0D" + "02" + "01" +
"07" + "03" + "666F6F" +
"03" + "0080" + "10");
r.skipList();
assertTrue(r.eof());
}
@Test
public void testReadMap() throws Exception {
// A map containing "foo" -> 123 and {} -> null
setContents("07" + "04" + "00000003" + "666F6F" +
"02" + "000000000000007B" + "05" + "00000000" + "0A" + "09");
// A map containing "foo" -> 123 and byte[0] -> null
setContents("0E" + "07" + "03" + "666F6F" + "02" + "7B" +
"0A" + "00" + "11" + "10");
r.readMapStart();
assertFalse(r.hasMapEnd());
assertEquals("foo", r.readString(1000));
assertFalse(r.hasMapEnd());
assertEquals(123, r.readInteger());
assertFalse(r.hasMapEnd());
assertArrayEquals(new byte[] {}, r.readBytes(1000));
assertArrayEquals(new byte[0], r.readBytes(1000));
assertFalse(r.hasMapEnd());
assertTrue(r.hasNull());
r.readNull();
@@ -212,9 +578,9 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testSkipMap() throws Exception {
// A map containing "foo" -> 123 and {} -> null
setContents("07" + "04" + "00000003" + "666F6F" +
"02" + "000000000000007B" + "05" + "00000000" + "0A" + "09");
// A map containing "foo" -> 123 and byte[0] -> null
setContents("0E" + "07" + "03" + "666F6F" + "02" + "7B" +
"0A" + "00" + "11" + "10");
r.skipMap();
assertTrue(r.eof());
}
@@ -222,7 +588,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testReadStruct() throws Exception {
// Two empty structs with IDs 0 and 255
setContents("0800" + "09" + "08FF" + "09");
setContents("0F00" + "10" + "0FFF" + "10");
r.readStructStart(0);
r.readStructEnd();
r.readStructStart(255);
@@ -233,7 +599,7 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testSkipStruct() throws Exception {
// Two empty structs with IDs 0 and 255
setContents("0800" + "09" + "08FF" + "09");
setContents("0F00" + "10" + "0FFF" + "10");
r.skipStruct();
r.skipStruct();
assertTrue(r.eof());
@@ -242,21 +608,21 @@ public class ReaderImplTest extends BriarTestCase {
@Test
public void testSkipNestedStructMapAndList() throws Exception {
// A struct containing a map containing two empty lists
setContents("0800" + "07" + "06" + "09" + "06" + "09" + "09" + "09");
setContents("0F00" + "0E" + "0D" + "10" + "0D" + "10" + "10" + "10");
r.skipStruct();
assertTrue(r.eof());
}
@Test
public void testReadNull() throws Exception {
setContents("0A");
setContents("11");
r.readNull();
assertTrue(r.eof());
}
@Test
public void testSkipNull() throws Exception {
setContents("0A");
setContents("11");
r.skipNull();
assertTrue(r.eof());
}

View File

@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
@@ -36,11 +37,20 @@ public class WriterImplTest extends BriarTestCase {
public void testWriteInteger() throws IOException {
w.writeInteger(0);
w.writeInteger(-1);
w.writeInteger(Long.MIN_VALUE);
w.writeInteger(Byte.MAX_VALUE);
w.writeInteger(Byte.MIN_VALUE);
w.writeInteger(Short.MAX_VALUE);
w.writeInteger(Short.MIN_VALUE);
w.writeInteger(Integer.MAX_VALUE);
w.writeInteger(Integer.MIN_VALUE);
w.writeInteger(Long.MAX_VALUE);
// INTEGER tag, 0, INTEGER tag, -1, etc
checkContents("02" + "0000000000000000" + "02" + "FFFFFFFFFFFFFFFF"
+ "02" + "8000000000000000" + "02" + "7FFFFFFFFFFFFFFF");
w.writeInteger(Long.MIN_VALUE);
// INTEGER_8 tag, 0, INTEGER_8 tag, -1, etc
checkContents("02" + "00" + "02" + "FF" +
"02" + "7F" + "02" + "80" +
"03" + "7FFF" + "03" + "8000" +
"04" + "7FFFFFFF" + "04" + "80000000" +
"05" + "7FFFFFFFFFFFFFFF" + "05" + "8000000000000000");
}
@Test
@@ -55,26 +65,75 @@ public class WriterImplTest extends BriarTestCase {
w.writeFloat(Double.NEGATIVE_INFINITY); // 1 2047 0 -> 0xFFF00000...
w.writeFloat(Double.POSITIVE_INFINITY); // 0 2047 0 -> 0x7FF00000...
w.writeFloat(Double.NaN); // 0 2047 1 -> 0x7FF8000000000000
checkContents("03" + "0000000000000000" + "03" + "3FF0000000000000"
+ "03" + "4000000000000000" + "03" + "BFF0000000000000"
+ "03" + "8000000000000000" + "03" + "FFF0000000000000"
+ "03" + "7FF0000000000000" + "03" + "7FF8000000000000");
checkContents("06" + "0000000000000000" + "06" + "3FF0000000000000"
+ "06" + "4000000000000000" + "06" + "BFF0000000000000"
+ "06" + "8000000000000000" + "06" + "FFF0000000000000"
+ "06" + "7FF0000000000000" + "06" + "7FF8000000000000");
}
@Test
public void testWriteString() throws IOException {
public void testWriteString8() throws IOException {
String longest = TestUtils.createRandomString(Byte.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
w.writeString("foo bar baz bam ");
// STRING tag, length 16, UTF-8 bytes
checkContents("04" + "00000010" + "666F6F206261722062617A2062616D20");
w.writeString(longest);
// STRING_8 tag, length 16, UTF-8 bytes, STRING_8 tag, length 127,
// UTF-8 bytes
checkContents("07" + "10" + "666F6F206261722062617A2062616D20" +
"07" + "7F" + longHex);
}
@Test
public void testWriteBytes() throws IOException {
w.writeBytes(new byte[] {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
});
// BYTES tag, length 16, bytes
checkContents("05" + "00000010" + "000102030405060708090A0B0C0D0E0F");
public void testWriteString16() throws IOException {
String shortest = TestUtils.createRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = TestUtils.createRandomString(Short.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
w.writeString(shortest);
w.writeString(longest);
// STRING_16 tag, length 128, UTF-8 bytes, STRING_16 tag,
// length 2^15 - 1, UTF-8 bytes
checkContents("08" + "0080" + shortHex + "08" + "7FFF" + longHex);
}
@Test
public void testWriteString32() throws IOException {
String shortest = TestUtils.createRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
w.writeString(shortest);
// STRING_32 tag, length 2^15, UTF-8 bytes
checkContents("09" + "00008000" + shortHex);
}
@Test
public void testWriteBytes8() throws IOException {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
w.writeBytes(new byte[] {1, 2, 3});
w.writeBytes(longest);
// BYTES_8 tag, length 3, bytes, BYTES_8 tag, length 127, bytes
checkContents("0A" + "03" + "010203" + "0A" + "7F" + longHex);
}
@Test
public void testWriteBytes16() throws IOException {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
w.writeBytes(shortest);
w.writeBytes(longest);
// BYTES_16 tag, length 128, bytes, BYTES_16 tag, length 2^15 - 1, bytes
checkContents("0B" + "0080" + shortHex + "0B" + "7FFF" + longHex);
}
@Test
public void testWriteBytes32() throws IOException {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
w.writeBytes(shortest);
// BYTES_32 tag, length 2^15, bytes
checkContents("0C" + "00008000" + shortHex);
}
@Test
@@ -83,8 +142,7 @@ public class WriterImplTest extends BriarTestCase {
for(int i = 0; i < 3; i++) l.add(i);
w.writeList(l);
// LIST tag, elements as integers, END tag
checkContents("06" + "02" + "0000000000000000" +
"02" + "0000000000000001" + "02" + "0000000000000002" + "09");
checkContents("0D" + "02" + "00" + "02" + "01" + "02" + "02" + "10");
}
@Test
@@ -95,8 +153,7 @@ public class WriterImplTest extends BriarTestCase {
l.add(2);
w.writeList(l);
// LIST tag, 1 as integer, NULL tag, 2 as integer, END tag
checkContents("06" + "02" + "0000000000000001" + "0A" +
"02" + "0000000000000002" + "09");
checkContents("0D" + "02" + "01" + "11" + "02" + "02" + "10");
}
@Test
@@ -106,11 +163,10 @@ public class WriterImplTest extends BriarTestCase {
for(int i = 0; i < 4; i++) m.put(i, i + 1);
w.writeMap(m);
// MAP tag, entries as integers, END tag
checkContents("07" + "02" + "0000000000000000" +
"02" + "0000000000000001" + "02" + "0000000000000001" +
"02" + "0000000000000002" + "02" + "0000000000000002" +
"02" + "0000000000000003" + "02" + "0000000000000003" +
"02" + "0000000000000004" + "09");
checkContents("0E" + "02" + "00" + "02" + "01" +
"02" + "01" + "02" + "02" +
"02" + "02" + "02" + "03" +
"02" + "03" + "02" + "04" + "10");
}
@Test
@@ -121,9 +177,9 @@ public class WriterImplTest extends BriarTestCase {
w.writeInteger(128);
w.writeListEnd();
// LIST tag, 1 as integer, "foo" as string, 128 as integer, END tag
checkContents("06" + "02" + "0000000000000001" +
"04" + "00000003" + "666F6F" +
"02" + "0000000000000080" + "09");
checkContents("0D" + "02" + "01" +
"07" + "03" + "666F6F" +
"03" + "0080" + "10");
}
@Test
@@ -136,8 +192,8 @@ public class WriterImplTest extends BriarTestCase {
w.writeMapEnd();
// MAP tag, "foo" as string, 123 as integer, {} as bytes, NULL tag,
// END tag
checkContents("07" + "04" + "00000003" + "666F6F" +
"02" + "000000000000007B" + "05" + "00000000" + "0A" + "09");
checkContents("0E" + "07" + "03" + "666F6F" +
"02" + "7B" + "0A" + "00" + "11" + "10");
}
@Test
@@ -151,9 +207,8 @@ public class WriterImplTest extends BriarTestCase {
w.writeMap(m1);
// MAP tag, MAP tag, "foo" as string, 123 as integer, END tag,
// LIST tag, 1 as integer, END tag, END tag
checkContents("07" + "07" + "04" + "00000003" + "666F6F" +
"02" + "000000000000007B" + "09" + "06" +
"02" + "0000000000000001" + "09" + "09");
checkContents("0E" + "0E" + "07" + "03" + "666F6F" +
"02" + "7B" + "10" + "0D" + "02" + "01" + "10" + "10");
}
@Test
@@ -161,13 +216,13 @@ public class WriterImplTest extends BriarTestCase {
w.writeStructStart(123);
w.writeStructEnd();
// STRUCT tag, 123 as struct ID, END tag
checkContents("08" + "7B" + "09");
checkContents("0F" + "7B" + "10");
}
@Test
public void testWriteNull() throws IOException {
w.writeNull();
checkContents("0A");
checkContents("11");
}
private void checkContents(String hex) throws IOException {