From 92d5fb4f1d7e94d155db677f71fa4a01222548d8 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Fri, 7 Feb 2014 18:50:28 +0000
Subject: [PATCH] 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.
---
 .../org/briarproject/serial/ReaderImpl.java   | 100 +++-
 .../serial/SerialComponentImpl.java           |  10 +-
 .../src/org/briarproject/serial/Tag.java      |  29 +-
 .../org/briarproject/serial/WriterImpl.java   |  91 +++-
 briar-tests/build.xml                         |   3 +-
 .../src/org/briarproject/TestUtils.java       |  10 +-
 .../briarproject/serial/ReaderImplTest.java   | 476 ++++++++++++++++--
 .../briarproject/serial/WriterImplTest.java   | 127 +++--
 8 files changed, 683 insertions(+), 163 deletions(-)

diff --git a/briar-core/src/org/briarproject/serial/ReaderImpl.java b/briar-core/src/org/briarproject/serial/ReaderImpl.java
index 6a644bff6e..b8710776f6 100644
--- a/briar-core/src/org/briarproject/serial/ReaderImpl.java
+++ b/briar-core/src/org/briarproject/serial/ReaderImpl.java
@@ -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;
diff --git a/briar-core/src/org/briarproject/serial/SerialComponentImpl.java b/briar-core/src/org/briarproject/serial/SerialComponentImpl.java
index f94aeabbce..8a7a42ab1f 100644
--- a/briar-core/src/org/briarproject/serial/SerialComponentImpl.java
+++ b/briar-core/src/org/briarproject/serial/SerialComponentImpl.java
@@ -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;
 	}
 }
diff --git a/briar-core/src/org/briarproject/serial/Tag.java b/briar-core/src/org/briarproject/serial/Tag.java
index af7056186e..9fe385dfac 100644
--- a/briar-core/src/org/briarproject/serial/Tag.java
+++ b/briar-core/src/org/briarproject/serial/Tag.java
@@ -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;
 }
diff --git a/briar-core/src/org/briarproject/serial/WriterImpl.java b/briar-core/src/org/briarproject/serial/WriterImpl.java
index 9e8ffe7660..45995cdb0e 100644
--- a/briar-core/src/org/briarproject/serial/WriterImpl.java
+++ b/briar-core/src/org/briarproject/serial/WriterImpl.java
@@ -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 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 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 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);
 	}
 
diff --git a/briar-tests/build.xml b/briar-tests/build.xml
index f5d288ba6e..a7d749821f 100644
--- a/briar-tests/build.xml
+++ b/briar-tests/build.xml
@@ -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'/>
diff --git a/briar-tests/src/org/briarproject/TestUtils.java b/briar-tests/src/org/briarproject/TestUtils.java
index 4c39dec2a2..40618408cf 100644
--- a/briar-tests/src/org/briarproject/TestUtils.java
+++ b/briar-tests/src/org/briarproject/TestUtils.java
@@ -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);
 	}
 }
diff --git a/briar-tests/src/org/briarproject/serial/ReaderImplTest.java b/briar-tests/src/org/briarproject/serial/ReaderImplTest.java
index 30c4eb1e4f..54210b540a 100644
--- a/briar-tests/src/org/briarproject/serial/ReaderImplTest.java
+++ b/briar-tests/src/org/briarproject/serial/ReaderImplTest.java
@@ -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");
+	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 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[] {}, 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 testReadBytesMaxLength() throws Exception {
+	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());
 	}
diff --git a/briar-tests/src/org/briarproject/serial/WriterImplTest.java b/briar-tests/src/org/briarproject/serial/WriterImplTest.java
index f21da475a1..6535bd2cbb 100644
--- a/briar-tests/src/org/briarproject/serial/WriterImplTest.java
+++ b/briar-tests/src/org/briarproject/serial/WriterImplTest.java
@@ -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 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 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 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 {
-- 
GitLab