From 32c9ce50d98f00e3485f9aed04b3d43fdb80ca45 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Fri, 1 May 2015 16:58:49 +0100
Subject: [PATCH] Moved the messaging protocol one step closer to BSP.

This breaks backward compatibility for the wire protocol and messages
stored in the database. The database schema version has been
incremented.
---
 briar-android/AndroidManifest.xml             |   4 +-
 .../api/messaging/MessagingConstants.java     |  21 +-
 .../api/messaging/PacketTypes.java            |  16 +
 .../org/briarproject/api/messaging/Types.java |  18 -
 .../briarproject/api/serial/ObjectReader.java |   8 +
 .../org/briarproject/api/serial/Reader.java   |  19 +-
 .../api/serial/SerialComponent.java           |   4 -
 .../briarproject/api/serial/StructReader.java |   8 -
 .../org/briarproject/api/serial/Writer.java   |   6 +-
 .../api/transport/TransportConstants.java     |   2 +-
 .../src/org/briarproject/db/JdbcDatabase.java |   4 +-
 .../messaging/AuthorFactoryImpl.java          |   6 +-
 .../briarproject/messaging/AuthorReader.java  |  11 +-
 .../messaging/DuplexOutgoingSession.java      |   4 +-
 .../messaging/GroupFactoryImpl.java           |   5 +-
 .../briarproject/messaging/GroupReader.java   |  11 +-
 .../messaging/MessageFactoryImpl.java         |  19 +-
 .../briarproject/messaging/MessageReader.java |  34 +-
 .../messaging/MessagingModule.java            |  16 +-
 .../messaging/PacketReaderFactoryImpl.java    |  10 +-
 .../messaging/PacketReaderImpl.java           | 250 ++++++++++----
 .../messaging/PacketWriterImpl.java           | 134 +++++---
 .../messaging/SimplexOutgoingSession.java     |   4 +-
 .../messaging/SubscriptionUpdateReader.java   |  25 +-
 .../org/briarproject/serial/ObjectTypes.java  |  21 ++
 .../org/briarproject/serial/ReaderImpl.java   | 161 ++++-----
 .../serial/SerialComponentImpl.java           |  10 -
 .../src/org/briarproject/serial/Tag.java      |  23 --
 .../org/briarproject/serial/WriterImpl.java   |  84 +++--
 .../briarproject/messaging/ConstantsTest.java |  14 +-
 .../messaging/PacketReaderImplTest.java       |  91 +++--
 .../briarproject/serial/ReaderImplTest.java   | 318 ++++++------------
 .../briarproject/serial/WriterImplTest.java   | 121 ++++---
 33 files changed, 732 insertions(+), 750 deletions(-)
 create mode 100644 briar-api/src/org/briarproject/api/messaging/PacketTypes.java
 delete mode 100644 briar-api/src/org/briarproject/api/messaging/Types.java
 create mode 100644 briar-api/src/org/briarproject/api/serial/ObjectReader.java
 delete mode 100644 briar-api/src/org/briarproject/api/serial/StructReader.java
 create mode 100644 briar-core/src/org/briarproject/serial/ObjectTypes.java
 delete mode 100644 briar-core/src/org/briarproject/serial/Tag.java

diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index d46693223a..e3c64dfbd8 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 	package="org.briarproject"
-	android:versionCode="9"
-	android:versionName="0.9" >
+	android:versionCode="10"
+	android:versionName="0.10" >
 
 	<uses-sdk
 		android:minSdkVersion="7"
diff --git a/briar-api/src/org/briarproject/api/messaging/MessagingConstants.java b/briar-api/src/org/briarproject/api/messaging/MessagingConstants.java
index 0f52b49010..fefe79a3ba 100644
--- a/briar-api/src/org/briarproject/api/messaging/MessagingConstants.java
+++ b/briar-api/src/org/briarproject/api/messaging/MessagingConstants.java
@@ -1,18 +1,19 @@
 package org.briarproject.api.messaging;
 
-import static org.briarproject.api.transport.TransportConstants.MIN_STREAM_LENGTH;
 
 public interface MessagingConstants {
 
-	/**
-	 * The maximum length of a serialised packet in bytes. To allow for future
-	 * changes in the protocol, this is smaller than the minimum stream length
-	 * minus the maximum encryption and authentication overhead.
-	 */
-	int MAX_PACKET_LENGTH = MIN_STREAM_LENGTH / 2;
+	/** The current version of the messaging protocol. */
+	byte PROTOCOL_VERSION = 0;
+
+	/** The length of the packet header in bytes. */
+	int HEADER_LENGTH = 4;
+
+	/** The maximum length of the packet payload in bytes. */
+	int MAX_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
 
 	/** The maximum number of public groups a user may subscribe to. */
-	int MAX_SUBSCRIPTIONS = 3000;
+	int MAX_SUBSCRIPTIONS = 300;
 
 	/** The maximum length of a group's name in UTF-8 bytes. */
 	int MAX_GROUP_NAME_LENGTH = 50;
@@ -22,10 +23,10 @@ public interface MessagingConstants {
 
 	/**
 	 * The maximum length of a message body in bytes. To allow for future
-	 * changes in the protocol, this is smaller than the maximum packet length
+	 * changes in the protocol, this is smaller than the maximum payload length
 	 * even when all the message's other fields have their maximum lengths.
 	 */
-	int MAX_BODY_LENGTH = MAX_PACKET_LENGTH - 1024;
+	int MAX_BODY_LENGTH = MAX_PAYLOAD_LENGTH - 1024;
 
 	/** The maximum length of a message's content type in UTF-8 bytes. */
 	int MAX_CONTENT_TYPE_LENGTH = 50;
diff --git a/briar-api/src/org/briarproject/api/messaging/PacketTypes.java b/briar-api/src/org/briarproject/api/messaging/PacketTypes.java
new file mode 100644
index 0000000000..41c2eee6f7
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/messaging/PacketTypes.java
@@ -0,0 +1,16 @@
+package org.briarproject.api.messaging;
+
+/** Packet types for the messaging protocol. */
+public interface PacketTypes {
+
+	byte ACK = 0;
+	byte MESSAGE = 1;
+	byte OFFER = 2;
+	byte REQUEST = 3;
+	byte RETENTION_ACK = 4;
+	byte RETENTION_UPDATE = 5;
+	byte SUBSCRIPTION_ACK = 6;
+	byte SUBSCRIPTION_UPDATE = 7;
+	byte TRANSPORT_ACK = 8;
+	byte TRANSPORT_UPDATE = 9;
+}
diff --git a/briar-api/src/org/briarproject/api/messaging/Types.java b/briar-api/src/org/briarproject/api/messaging/Types.java
deleted file mode 100644
index 83db6e6c9b..0000000000
--- a/briar-api/src/org/briarproject/api/messaging/Types.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.briarproject.api.messaging;
-
-/** Struct identifiers for encoding and decoding protocol objects. */
-public interface Types {
-
-	int AUTHOR = 0;
-	int GROUP = 1;
-	int ACK = 2;
-	int MESSAGE = 3;
-	int OFFER = 4;
-	int REQUEST = 5;
-	int RETENTION_ACK = 6;
-	int RETENTION_UPDATE = 7;
-	int SUBSCRIPTION_ACK = 8;
-	int SUBSCRIPTION_UPDATE = 9;
-	int TRANSPORT_ACK = 10;
-	int TRANSPORT_UPDATE = 11;
-}
diff --git a/briar-api/src/org/briarproject/api/serial/ObjectReader.java b/briar-api/src/org/briarproject/api/serial/ObjectReader.java
new file mode 100644
index 0000000000..4606c33004
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/serial/ObjectReader.java
@@ -0,0 +1,8 @@
+package org.briarproject.api.serial;
+
+import java.io.IOException;
+
+public interface ObjectReader<T> {
+
+	T readObject(Reader r) throws IOException;
+}
diff --git a/briar-api/src/org/briarproject/api/serial/Reader.java b/briar-api/src/org/briarproject/api/serial/Reader.java
index 59113c414c..401ec7930f 100644
--- a/briar-api/src/org/briarproject/api/serial/Reader.java
+++ b/briar-api/src/org/briarproject/api/serial/Reader.java
@@ -10,6 +10,10 @@ public interface Reader {
 	void addConsumer(Consumer c);
 	void removeConsumer(Consumer c);
 
+	boolean hasNull() throws IOException;
+	void readNull() throws IOException;
+	void skipNull() throws IOException;
+
 	boolean hasBoolean() throws IOException;
 	boolean readBoolean() throws IOException;
 	void skipBoolean() throws IOException;
@@ -24,11 +28,11 @@ public interface Reader {
 
 	boolean hasString() throws IOException;
 	String readString(int maxLength) throws IOException;
-	void skipString(int maxLength) throws IOException;
+	void skipString() throws IOException;
 
 	boolean hasBytes() throws IOException;
 	byte[] readBytes(int maxLength) throws IOException;
-	void skipBytes(int maxLength) throws IOException;
+	void skipBytes() throws IOException;
 
 	boolean hasList() throws IOException;
 	void readListStart() throws IOException;
@@ -41,15 +45,4 @@ public interface Reader {
 	boolean hasMapEnd() throws IOException;
 	void readMapEnd() throws IOException;
 	void skipMap() throws IOException;
-
-	boolean hasStruct() throws IOException;
-	boolean hasStruct(int id) throws IOException;
-	void readStructStart(int id) throws IOException;
-	boolean hasStructEnd() throws IOException;
-	void readStructEnd() throws IOException;
-	void skipStruct() throws IOException;
-
-	boolean hasNull() throws IOException;
-	void readNull() throws IOException;
-	void skipNull() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/serial/SerialComponent.java b/briar-api/src/org/briarproject/api/serial/SerialComponent.java
index be3d619684..ecaf2c3657 100644
--- a/briar-api/src/org/briarproject/api/serial/SerialComponent.java
+++ b/briar-api/src/org/briarproject/api/serial/SerialComponent.java
@@ -6,9 +6,5 @@ public interface SerialComponent {
 
 	int getSerialisedListEndLength();
 
-	int getSerialisedStructStartLength(int id);
-
-	int getSerialisedStructEndLength();
-
 	int getSerialisedUniqueIdLength();
 }
diff --git a/briar-api/src/org/briarproject/api/serial/StructReader.java b/briar-api/src/org/briarproject/api/serial/StructReader.java
deleted file mode 100644
index 14e1474664..0000000000
--- a/briar-api/src/org/briarproject/api/serial/StructReader.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.briarproject.api.serial;
-
-import java.io.IOException;
-
-public interface StructReader<T> {
-
-	T readStruct(Reader r) throws IOException;
-}
diff --git a/briar-api/src/org/briarproject/api/serial/Writer.java b/briar-api/src/org/briarproject/api/serial/Writer.java
index 2ad638bbc8..3d70451172 100644
--- a/briar-api/src/org/briarproject/api/serial/Writer.java
+++ b/briar-api/src/org/briarproject/api/serial/Writer.java
@@ -12,6 +12,7 @@ public interface Writer {
 	void addConsumer(Consumer c);
 	void removeConsumer(Consumer c);
 
+	void writeNull() throws IOException;
 	void writeBoolean(boolean b) throws IOException;
 	void writeInteger(long l) throws IOException;
 	void writeFloat(double d) throws IOException;
@@ -25,9 +26,4 @@ public interface Writer {
 	void writeMap(Map<?, ?> m) throws IOException;
 	void writeMapStart() throws IOException;
 	void writeMapEnd() throws IOException;
-
-	void writeStructStart(int id) throws IOException;
-	void writeStructEnd() throws IOException;
-
-	void writeNull() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/transport/TransportConstants.java b/briar-api/src/org/briarproject/api/transport/TransportConstants.java
index 8f05eb23ab..51444c4c79 100644
--- a/briar-api/src/org/briarproject/api/transport/TransportConstants.java
+++ b/briar-api/src/org/briarproject/api/transport/TransportConstants.java
@@ -26,7 +26,7 @@ public interface TransportConstants {
 	 * support. Streams may be shorter than this length, but all transport
 	 * plugins must support streams of at least this length.
 	 */
-	int MIN_STREAM_LENGTH = 1024 * 1024; // 2^20, 1 MiB
+	int MIN_STREAM_LENGTH = 64 * 1024; // 64 KiB
 
 	/** The maximum difference between two communicating devices' clocks. */
 	int MAX_CLOCK_DIFFERENCE = 60 * 60 * 1000; // 1 hour
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 06402b1cce..13248ddca7 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -64,8 +64,8 @@ import org.briarproject.api.transport.TemporarySecret;
  */
 abstract class JdbcDatabase implements Database<Connection> {
 
-	private static final int SCHEMA_VERSION = 8;
-	private static final int MIN_SCHEMA_VERSION = 8;
+	private static final int SCHEMA_VERSION = 9;
+	private static final int MIN_SCHEMA_VERSION = 9;
 
 	private static final String CREATE_SETTINGS =
 			"CREATE TABLE settings"
diff --git a/briar-core/src/org/briarproject/messaging/AuthorFactoryImpl.java b/briar-core/src/org/briarproject/messaging/AuthorFactoryImpl.java
index 6fca9a6c8e..e9c05a92a1 100644
--- a/briar-core/src/org/briarproject/messaging/AuthorFactoryImpl.java
+++ b/briar-core/src/org/briarproject/messaging/AuthorFactoryImpl.java
@@ -1,7 +1,5 @@
 package org.briarproject.messaging;
 
-import static org.briarproject.api.messaging.Types.AUTHOR;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
@@ -45,10 +43,10 @@ class AuthorFactoryImpl implements AuthorFactory {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		try {
-			w.writeStructStart(AUTHOR);
+			w.writeListStart();
 			w.writeString(name);
 			w.writeBytes(publicKey);
-			w.writeStructEnd();
+			w.writeListEnd();
 		} catch(IOException e) {
 			// Shouldn't happen with ByteArrayOutputStream
 			throw new RuntimeException();
diff --git a/briar-core/src/org/briarproject/messaging/AuthorReader.java b/briar-core/src/org/briarproject/messaging/AuthorReader.java
index b30e10920c..cdb1c958ae 100644
--- a/briar-core/src/org/briarproject/messaging/AuthorReader.java
+++ b/briar-core/src/org/briarproject/messaging/AuthorReader.java
@@ -2,7 +2,6 @@ package org.briarproject.messaging;
 
 import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.api.messaging.Types.AUTHOR;
 
 import java.io.IOException;
 
@@ -13,9 +12,9 @@ import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.MessageDigest;
 import org.briarproject.api.serial.DigestingConsumer;
 import org.briarproject.api.serial.Reader;
-import org.briarproject.api.serial.StructReader;
+import org.briarproject.api.serial.ObjectReader;
 
-class AuthorReader implements StructReader<Author> {
+class AuthorReader implements ObjectReader<Author> {
 
 	private final MessageDigest messageDigest;
 
@@ -23,16 +22,16 @@ class AuthorReader implements StructReader<Author> {
 		messageDigest = crypto.getMessageDigest();
 	}
 
-	public Author readStruct(Reader r) throws IOException {
+	public Author readObject(Reader r) throws IOException {
 		// Set up the reader
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		r.addConsumer(digesting);
 		// Read and digest the data
-		r.readStructStart(AUTHOR);
+		r.readListStart();
 		String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
 		if(name.length() == 0) throw new FormatException();
 		byte[] publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
-		r.readStructEnd();
+		r.readListEnd();
 		// Reset the reader
 		r.removeConsumer(digesting);
 		// Build and return the author
diff --git a/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java b/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java
index 23177097f9..c64c768182 100644
--- a/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java
@@ -3,7 +3,7 @@ package org.briarproject.messaging;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -260,7 +260,7 @@ class DuplexOutgoingSession implements MessagingSession, EventListener {
 			if(interrupted) return;
 			try {
 				Collection<byte[]> b = db.generateRequestedBatch(contactId,
-						MAX_PACKET_LENGTH, maxLatency);
+						MAX_PAYLOAD_LENGTH, maxLatency);
 				if(LOG.isLoggable(INFO))
 					LOG.info("Generated batch: " + (b != null));
 				if(b != null) writerTasks.add(new WriteBatch(b));
diff --git a/briar-core/src/org/briarproject/messaging/GroupFactoryImpl.java b/briar-core/src/org/briarproject/messaging/GroupFactoryImpl.java
index ec88fca9c0..b2f3e0cad9 100644
--- a/briar-core/src/org/briarproject/messaging/GroupFactoryImpl.java
+++ b/briar-core/src/org/briarproject/messaging/GroupFactoryImpl.java
@@ -1,7 +1,6 @@
 package org.briarproject.messaging;
 
 import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
-import static org.briarproject.api.messaging.Types.GROUP;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -37,10 +36,10 @@ class GroupFactoryImpl implements GroupFactory {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		try {
-			w.writeStructStart(GROUP);
+			w.writeListStart();
 			w.writeString(name);
 			w.writeBytes(salt);
-			w.writeStructEnd();
+			w.writeListEnd();
 		} catch(IOException e) {
 			// Shouldn't happen with ByteArrayOutputStream
 			throw new RuntimeException();
diff --git a/briar-core/src/org/briarproject/messaging/GroupReader.java b/briar-core/src/org/briarproject/messaging/GroupReader.java
index 771bfe6e64..1bfd090f8d 100644
--- a/briar-core/src/org/briarproject/messaging/GroupReader.java
+++ b/briar-core/src/org/briarproject/messaging/GroupReader.java
@@ -2,7 +2,6 @@ package org.briarproject.messaging;
 
 import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
-import static org.briarproject.api.messaging.Types.GROUP;
 
 import java.io.IOException;
 
@@ -13,9 +12,9 @@ import org.briarproject.api.messaging.Group;
 import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.serial.DigestingConsumer;
 import org.briarproject.api.serial.Reader;
-import org.briarproject.api.serial.StructReader;
+import org.briarproject.api.serial.ObjectReader;
 
-class GroupReader implements StructReader<Group> {
+class GroupReader implements ObjectReader<Group> {
 
 	private final MessageDigest messageDigest;
 
@@ -23,16 +22,16 @@ class GroupReader implements StructReader<Group> {
 		messageDigest = crypto.getMessageDigest();
 	}
 
-	public Group readStruct(Reader r) throws IOException {
+	public Group readObject(Reader r) throws IOException {
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		// Read and digest the data
 		r.addConsumer(digesting);
-		r.readStructStart(GROUP);
+		r.readListStart();
 		String name = r.readString(MAX_GROUP_NAME_LENGTH);
 		if(name.length() == 0) throw new FormatException();
 		byte[] salt = r.readBytes(GROUP_SALT_LENGTH);
 		if(salt.length != GROUP_SALT_LENGTH) throw new FormatException();
-		r.readStructEnd();
+		r.readListEnd();
 		r.removeConsumer(digesting);
 		// Build and return the group
 		GroupId id = new GroupId(messageDigest.digest());
diff --git a/briar-core/src/org/briarproject/messaging/MessageFactoryImpl.java b/briar-core/src/org/briarproject/messaging/MessageFactoryImpl.java
index 4b144ad5b3..03a1b7d399 100644
--- a/briar-core/src/org/briarproject/messaging/MessageFactoryImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessageFactoryImpl.java
@@ -3,11 +3,8 @@ package org.briarproject.messaging;
 import static org.briarproject.api.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
-import static org.briarproject.api.messaging.Types.AUTHOR;
-import static org.briarproject.api.messaging.Types.GROUP;
-import static org.briarproject.api.messaging.Types.MESSAGE;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -77,7 +74,7 @@ class MessageFactoryImpl implements MessageFactory {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		// Initialise the consumers
-		CountingConsumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
+		CountingConsumer counting = new CountingConsumer(MAX_PAYLOAD_LENGTH);
 		w.addConsumer(counting);
 		Consumer digestingConsumer = new DigestingConsumer(messageDigest);
 		w.addConsumer(digestingConsumer);
@@ -88,7 +85,7 @@ class MessageFactoryImpl implements MessageFactory {
 			w.addConsumer(signingConsumer);
 		}
 		// Write the message
-		w.writeStructStart(MESSAGE);
+		w.writeListStart();
 		if(parent == null) w.writeNull();
 		else w.writeBytes(parent.getBytes());
 		writeGroup(w, group);
@@ -111,7 +108,7 @@ class MessageFactoryImpl implements MessageFactory {
 				throw new IllegalArgumentException();
 			w.writeBytes(sig);
 		}
-		w.writeStructEnd();
+		w.writeListEnd();
 		// Hash the message, including the signature, to get the message ID
 		w.removeConsumer(digestingConsumer);
 		MessageId id = new MessageId(messageDigest.digest());
@@ -120,16 +117,16 @@ class MessageFactoryImpl implements MessageFactory {
 	}
 
 	private void writeGroup(Writer w, Group g) throws IOException {
-		w.writeStructStart(GROUP);
+		w.writeListStart();
 		w.writeString(g.getName());
 		w.writeBytes(g.getSalt());
-		w.writeStructEnd();
+		w.writeListEnd();
 	}
 
 	private void writeAuthor(Writer w, Author a) throws IOException {
-		w.writeStructStart(AUTHOR);
+		w.writeListStart();
 		w.writeString(a.getName());
 		w.writeBytes(a.getPublicKey());
-		w.writeStructEnd();
+		w.writeListEnd();
 	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/MessageReader.java b/briar-core/src/org/briarproject/messaging/MessageReader.java
index 198c34a750..6069eb8d49 100644
--- a/briar-core/src/org/briarproject/messaging/MessageReader.java
+++ b/briar-core/src/org/briarproject/messaging/MessageReader.java
@@ -3,9 +3,8 @@ package org.briarproject.messaging;
 import static org.briarproject.api.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
-import static org.briarproject.api.messaging.Types.MESSAGE;
 
 import java.io.IOException;
 
@@ -17,27 +16,27 @@ import org.briarproject.api.messaging.MessageId;
 import org.briarproject.api.messaging.UnverifiedMessage;
 import org.briarproject.api.serial.CopyingConsumer;
 import org.briarproject.api.serial.CountingConsumer;
+import org.briarproject.api.serial.ObjectReader;
 import org.briarproject.api.serial.Reader;
-import org.briarproject.api.serial.StructReader;
 
-class MessageReader implements StructReader<UnverifiedMessage> {
+class MessageReader implements ObjectReader<UnverifiedMessage> {
 
-	private final StructReader<Group> groupReader;
-	private final StructReader<Author> authorReader;
+	private final ObjectReader<Group> groupReader;
+	private final ObjectReader<Author> authorReader;
 
-	MessageReader(StructReader<Group> groupReader,
-			StructReader<Author> authorReader) {
+	MessageReader(ObjectReader<Group> groupReader,
+			ObjectReader<Author> authorReader) {
 		this.groupReader = groupReader;
 		this.authorReader = authorReader;
 	}
 
-	public UnverifiedMessage readStruct(Reader r) throws IOException {
+	public UnverifiedMessage readObject(Reader r) throws IOException {
 		CopyingConsumer copying = new CopyingConsumer();
-		CountingConsumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
+		CountingConsumer counting = new CountingConsumer(MAX_PAYLOAD_LENGTH);
 		r.addConsumer(copying);
 		r.addConsumer(counting);
-		// Read the start of the struct
-		r.readStructStart(MESSAGE);
+		// Read the start of the message
+		r.readListStart();
 		// Read the parent's message ID, if there is one
 		MessageId parent = null;
 		if(r.hasNull()) {
@@ -48,11 +47,11 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 			parent = new MessageId(b);
 		}
 		// Read the group
-		Group group = groupReader.readStruct(r);
+		Group group = groupReader.readObject(r);
 		// Read the author, if there is one
 		Author author = null;
 		if(r.hasNull()) r.readNull();
-		else author = authorReader.readStruct(r);
+		else author = authorReader.readObject(r);
 		// Read the content type
 		String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
 		// Read the timestamp
@@ -71,11 +70,12 @@ 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
+		// Read the end of the message
+		r.readListEnd();
+		// Reset the reader
 		r.removeConsumer(counting);
 		r.removeConsumer(copying);
+		// Build and return the unverified message
 		byte[] raw = copying.getCopy();
 		return new UnverifiedMessage(parent, group, author, contentType,
 				timestamp, raw, signature, bodyStart, body.length,
diff --git a/briar-core/src/org/briarproject/messaging/MessagingModule.java b/briar-core/src/org/briarproject/messaging/MessagingModule.java
index e99c2a42c3..a209155b95 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingModule.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingModule.java
@@ -14,7 +14,7 @@ import org.briarproject.api.messaging.PacketReaderFactory;
 import org.briarproject.api.messaging.PacketWriterFactory;
 import org.briarproject.api.messaging.SubscriptionUpdate;
 import org.briarproject.api.messaging.UnverifiedMessage;
-import org.briarproject.api.serial.StructReader;
+import org.briarproject.api.serial.ObjectReader;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
@@ -34,25 +34,25 @@ public class MessagingModule extends AbstractModule {
 	}
 
 	@Provides
-	StructReader<Author> getAuthorReader(CryptoComponent crypto) {
+	ObjectReader<Author> getAuthorReader(CryptoComponent crypto) {
 		return new AuthorReader(crypto);
 	}
 
 	@Provides
-	StructReader<Group> getGroupReader(CryptoComponent crypto) {
+	ObjectReader<Group> getGroupReader(CryptoComponent crypto) {
 		return new GroupReader(crypto);
 	}
 
 	@Provides
-	StructReader<UnverifiedMessage> getMessageReader(
-			StructReader<Group> groupReader,
-			StructReader<Author> authorReader) {
+	ObjectReader<UnverifiedMessage> getMessageReader(
+			ObjectReader<Group> groupReader,
+			ObjectReader<Author> authorReader) {
 		return new MessageReader(groupReader, authorReader);
 	}
 
 	@Provides
-	StructReader<SubscriptionUpdate> getSubscriptionUpdateReader(
-			StructReader<Group> groupReader) {
+	ObjectReader<SubscriptionUpdate> getSubscriptionUpdateReader(
+			ObjectReader<Group> groupReader) {
 		return new SubscriptionUpdateReader(groupReader);
 	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/PacketReaderFactoryImpl.java b/briar-core/src/org/briarproject/messaging/PacketReaderFactoryImpl.java
index ec9a59dd2a..5e9879554a 100644
--- a/briar-core/src/org/briarproject/messaging/PacketReaderFactoryImpl.java
+++ b/briar-core/src/org/briarproject/messaging/PacketReaderFactoryImpl.java
@@ -9,18 +9,18 @@ import org.briarproject.api.messaging.PacketReaderFactory;
 import org.briarproject.api.messaging.SubscriptionUpdate;
 import org.briarproject.api.messaging.UnverifiedMessage;
 import org.briarproject.api.serial.ReaderFactory;
-import org.briarproject.api.serial.StructReader;
+import org.briarproject.api.serial.ObjectReader;
 
 class PacketReaderFactoryImpl implements PacketReaderFactory {
 
 	private final ReaderFactory readerFactory;
-	private final StructReader<UnverifiedMessage> messageReader;
-	private final StructReader<SubscriptionUpdate> subscriptionUpdateReader;
+	private final ObjectReader<UnverifiedMessage> messageReader;
+	private final ObjectReader<SubscriptionUpdate> subscriptionUpdateReader;
 
 	@Inject
 	PacketReaderFactoryImpl(ReaderFactory readerFactory,
-			StructReader<UnverifiedMessage> messageReader,
-			StructReader<SubscriptionUpdate> subscriptionUpdateReader) {
+			ObjectReader<UnverifiedMessage> messageReader,
+			ObjectReader<SubscriptionUpdate> subscriptionUpdateReader) {
 		this.readerFactory = readerFactory;
 		this.messageReader = messageReader;
 		this.subscriptionUpdateReader = subscriptionUpdateReader;
diff --git a/briar-core/src/org/briarproject/messaging/PacketReaderImpl.java b/briar-core/src/org/briarproject/messaging/PacketReaderImpl.java
index b34244fd0f..8a13733f4e 100644
--- a/briar-core/src/org/briarproject/messaging/PacketReaderImpl.java
+++ b/briar-core/src/org/briarproject/messaging/PacketReaderImpl.java
@@ -3,18 +3,21 @@ package org.briarproject.messaging;
 import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
 import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
 import static org.briarproject.api.TransportPropertyConstants.MAX_TRANSPORT_ID_LENGTH;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
-import static org.briarproject.api.messaging.Types.ACK;
-import static org.briarproject.api.messaging.Types.MESSAGE;
-import static org.briarproject.api.messaging.Types.OFFER;
-import static org.briarproject.api.messaging.Types.REQUEST;
-import static org.briarproject.api.messaging.Types.RETENTION_ACK;
-import static org.briarproject.api.messaging.Types.RETENTION_UPDATE;
-import static org.briarproject.api.messaging.Types.SUBSCRIPTION_ACK;
-import static org.briarproject.api.messaging.Types.SUBSCRIPTION_UPDATE;
-import static org.briarproject.api.messaging.Types.TRANSPORT_ACK;
-import static org.briarproject.api.messaging.Types.TRANSPORT_UPDATE;
+import static org.briarproject.api.messaging.MessagingConstants.HEADER_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.PROTOCOL_VERSION;
+import static org.briarproject.api.messaging.PacketTypes.ACK;
+import static org.briarproject.api.messaging.PacketTypes.MESSAGE;
+import static org.briarproject.api.messaging.PacketTypes.OFFER;
+import static org.briarproject.api.messaging.PacketTypes.REQUEST;
+import static org.briarproject.api.messaging.PacketTypes.RETENTION_ACK;
+import static org.briarproject.api.messaging.PacketTypes.RETENTION_UPDATE;
+import static org.briarproject.api.messaging.PacketTypes.SUBSCRIPTION_ACK;
+import static org.briarproject.api.messaging.PacketTypes.SUBSCRIPTION_UPDATE;
+import static org.briarproject.api.messaging.PacketTypes.TRANSPORT_ACK;
+import static org.briarproject.api.messaging.PacketTypes.TRANSPORT_UPDATE;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -39,42 +42,82 @@ import org.briarproject.api.messaging.SubscriptionUpdate;
 import org.briarproject.api.messaging.TransportAck;
 import org.briarproject.api.messaging.TransportUpdate;
 import org.briarproject.api.messaging.UnverifiedMessage;
-import org.briarproject.api.serial.Consumer;
-import org.briarproject.api.serial.CountingConsumer;
 import org.briarproject.api.serial.Reader;
 import org.briarproject.api.serial.ReaderFactory;
-import org.briarproject.api.serial.StructReader;
+import org.briarproject.api.serial.ObjectReader;
+import org.briarproject.util.ByteUtils;
 
 // This class is not thread-safe
 class PacketReaderImpl implements PacketReader {
 
-	private final StructReader<UnverifiedMessage> messageReader;
-	private final StructReader<SubscriptionUpdate> subscriptionUpdateReader;
-	private final Reader r;
+	private enum State { BUFFER_EMPTY, BUFFER_FULL, EOF };
+
+	private final ReaderFactory readerFactory;
+	private final ObjectReader<UnverifiedMessage> messageReader;
+	private final ObjectReader<SubscriptionUpdate> subscriptionUpdateReader;
+	private final InputStream in;
+	private final byte[] header, payload;
+
+	private State state = State.BUFFER_EMPTY;
+	private int payloadLength = 0;
 
 	PacketReaderImpl(ReaderFactory readerFactory,
-			StructReader<UnverifiedMessage> messageReader,
-			StructReader<SubscriptionUpdate> subscriptionUpdateReader,
+			ObjectReader<UnverifiedMessage> messageReader,
+			ObjectReader<SubscriptionUpdate> subscriptionUpdateReader,
 			InputStream in) {
+		this.readerFactory = readerFactory;
 		this.messageReader = messageReader;
 		this.subscriptionUpdateReader = subscriptionUpdateReader;
-		r = readerFactory.createReader(in);
+		this.in = in;
+		header = new byte[HEADER_LENGTH];
+		payload = new byte[MAX_PAYLOAD_LENGTH];
+	}
+
+	private void readPacket() throws IOException {
+		assert state == State.BUFFER_EMPTY;
+		// Read the header
+		int offset = 0;
+		while(offset < HEADER_LENGTH) {
+			int read = in.read(header, offset, HEADER_LENGTH - offset);
+			if(read == -1) {
+				if(offset > 0) throw new FormatException();
+				state = State.EOF;
+				return;
+			}
+			offset += read;
+		}
+		// Check the protocol version
+		if(header[0] != PROTOCOL_VERSION) throw new FormatException();
+		// Read the payload length
+		payloadLength = ByteUtils.readUint16(header, 2);
+		if(payloadLength > MAX_PAYLOAD_LENGTH) throw new FormatException();
+		// Read the payload
+		offset = 0;
+		while(offset < payloadLength) {
+			int read = in.read(payload, offset, payloadLength - offset);
+			if(read == -1) throw new FormatException();
+			offset += read;
+		}
+		state = State.BUFFER_FULL;
 	}
 
 	public boolean eof() throws IOException {
-		return r.eof();
+		if(state == State.BUFFER_EMPTY) readPacket();
+		assert state != State.BUFFER_EMPTY;
+		return state == State.EOF;
 	}
 
 	public boolean hasAck() throws IOException {
-		return r.hasStruct(ACK);
+		return !eof() && header[1] == ACK;
 	}
 
 	public Ack readAck() throws IOException {
+		if(!hasAck()) throw new FormatException();
 		// Set up the reader
-		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
-		r.addConsumer(counting);
-		// Read the start of the struct
-		r.readStructStart(ACK);
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
 		// Read the message IDs
 		List<MessageId> acked = new ArrayList<MessageId>();
 		r.readListStart();
@@ -86,32 +129,41 @@ class PacketReaderImpl implements PacketReader {
 		}
 		if(acked.isEmpty()) throw new FormatException();
 		r.readListEnd();
-		// Read the end of the struct
-		r.readStructEnd();
-		// Reset the reader
-		r.removeConsumer(counting);
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
 		// Build and return the ack
 		return new Ack(Collections.unmodifiableList(acked));
 	}
 
 	public boolean hasMessage() throws IOException {
-		return r.hasStruct(MESSAGE);
+		return !eof() && header[1] == MESSAGE;
 	}
 
 	public UnverifiedMessage readMessage() throws IOException {
-		return messageReader.readStruct(r);
+		if(!hasMessage()) throw new FormatException();
+		// Set up the reader
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read and build the message
+		UnverifiedMessage m = messageReader.readObject(r);
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
+		return m;
 	}
 
 	public boolean hasOffer() throws IOException {
-		return r.hasStruct(OFFER);
+		return !eof() && header[1] == OFFER;
 	}
 
 	public Offer readOffer() throws IOException {
+		if(!hasOffer()) throw new FormatException();
 		// Set up the reader
-		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
-		r.addConsumer(counting);
-		// Read the start of the struct
-		r.readStructStart(OFFER);
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
 		// Read the message IDs
 		List<MessageId> offered = new ArrayList<MessageId>();
 		r.readListStart();
@@ -123,27 +175,28 @@ class PacketReaderImpl implements PacketReader {
 		}
 		if(offered.isEmpty()) throw new FormatException();
 		r.readListEnd();
-		// Read the end of the struct
-		r.readStructEnd();
-		// Reset the reader
-		r.removeConsumer(counting);
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
 		// Build and return the offer
 		return new Offer(Collections.unmodifiableList(offered));
 	}
 
 	public boolean hasRequest() throws IOException {
-		return r.hasStruct(REQUEST);
+		return !eof() && header[1] == REQUEST;
 	}
 
 	public Request readRequest() throws IOException {
+		if(!hasRequest()) throw new FormatException();
 		// Set up the reader
-		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
-		r.addConsumer(counting);
-		// Read the start of the struct
-		r.readStructStart(REQUEST);
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
 		// Read the message IDs
-		List<MessageId> requested = new ArrayList<MessageId>();
 		r.readListStart();
+		List<MessageId> requested = new ArrayList<MessageId>();
 		while(!r.hasListEnd()) {
 			byte[] b = r.readBytes(UniqueId.LENGTH);
 			if(b.length != UniqueId.LENGTH)
@@ -152,85 +205,134 @@ class PacketReaderImpl implements PacketReader {
 		}
 		if(requested.isEmpty()) throw new FormatException();
 		r.readListEnd();
-		// Read the end of the struct
-		r.readStructEnd();
-		// Reset the reader
-		r.removeConsumer(counting);
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
 		// Build and return the request
 		return new Request(Collections.unmodifiableList(requested));
 	}
 
 	public boolean hasRetentionAck() throws IOException {
-		return r.hasStruct(RETENTION_ACK);
+		return !eof() && header[1] == RETENTION_ACK;
 	}
 
 	public RetentionAck readRetentionAck() throws IOException {
-		r.readStructStart(RETENTION_ACK);
+		if(!hasRetentionAck()) throw new FormatException();
+		// Set up the reader
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
+		// Read the version
 		long version = r.readInteger();
 		if(version < 0) throw new FormatException();
-		r.readStructEnd();
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
+		// Build and return the retention ack
 		return new RetentionAck(version);
 	}
 
 	public boolean hasRetentionUpdate() throws IOException {
-		return r.hasStruct(RETENTION_UPDATE);
+		return !eof() && header[1] == RETENTION_UPDATE;
 	}
 
 	public RetentionUpdate readRetentionUpdate() throws IOException {
-		r.readStructStart(RETENTION_UPDATE);
+		if(!hasRetentionUpdate()) throw new FormatException();
+		// Set up the reader
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
+		// Read the retention time and version
 		long retention = r.readInteger();
 		if(retention < 0) throw new FormatException();
 		long version = r.readInteger();
 		if(version < 0) throw new FormatException();
-		r.readStructEnd();
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
+		// Build and return the retention update
 		return new RetentionUpdate(retention, version);
 	}
 
 	public boolean hasSubscriptionAck() throws IOException {
-		return r.hasStruct(SUBSCRIPTION_ACK);
+		return !eof() && header[1] == SUBSCRIPTION_ACK;
 	}
 
 	public SubscriptionAck readSubscriptionAck() throws IOException {
-		r.readStructStart(SUBSCRIPTION_ACK);
+		if(!hasSubscriptionAck()) throw new FormatException();
+		// Set up the reader
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
+		// Read the version
 		long version = r.readInteger();
 		if(version < 0) throw new FormatException();
-		r.readStructEnd();
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
+		// Build and return the subscription ack
 		return new SubscriptionAck(version);
 	}
 
 	public boolean hasSubscriptionUpdate() throws IOException {
-		return r.hasStruct(SUBSCRIPTION_UPDATE);
+		return !eof() && header[1] == SUBSCRIPTION_UPDATE;
 	}
 
 	public SubscriptionUpdate readSubscriptionUpdate() throws IOException {
-		return subscriptionUpdateReader.readStruct(r);
+		if(!hasSubscriptionUpdate()) throw new FormatException();
+		// Set up the reader
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read and build the subscription update
+		SubscriptionUpdate u = subscriptionUpdateReader.readObject(r);
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
+		return u;
 	}
 
 	public boolean hasTransportAck() throws IOException {
-		return r.hasStruct(TRANSPORT_ACK);
+		return !eof() && header[1] == TRANSPORT_ACK;
 	}
 
 	public TransportAck readTransportAck() throws IOException {
-		r.readStructStart(TRANSPORT_ACK);
+		if(!hasTransportAck()) throw new FormatException();
+		// Set up the reader
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
+		// Read the transport ID and version
 		String idString = r.readString(MAX_TRANSPORT_ID_LENGTH);
 		if(idString.length() == 0) throw new FormatException();
 		TransportId id = new TransportId(idString);
 		long version = r.readInteger();
 		if(version < 0) throw new FormatException();
-		r.readStructEnd();
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
+		// Build and return the transport ack
 		return new TransportAck(id, version);
 	}
 
 	public boolean hasTransportUpdate() throws IOException {
-		return r.hasStruct(TRANSPORT_UPDATE);
+		return !eof() && header[1] == TRANSPORT_UPDATE;
 	}
 
 	public TransportUpdate readTransportUpdate() throws IOException {
+		if(!hasTransportUpdate()) throw new FormatException();
 		// Set up the reader
-		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
-		r.addConsumer(counting);
-		// Read the start of the struct
-		r.readStructStart(TRANSPORT_UPDATE);
+		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
+		Reader r = readerFactory.createReader(bais);
+		// Read the start of the payload
+		r.readListStart();
 		// Read the transport ID
 		String idString = r.readString(MAX_TRANSPORT_ID_LENGTH);
 		if(idString.length() == 0) throw new FormatException();
@@ -249,10 +351,10 @@ class PacketReaderImpl implements PacketReader {
 		// Read the version number
 		long version = r.readInteger();
 		if(version < 0) throw new FormatException();
-		// Read the end of the struct
-		r.readStructEnd();
-		// Reset the reader
-		r.removeConsumer(counting);
+		// Read the end of the payload
+		r.readListEnd();
+		if(!r.eof()) throw new FormatException();
+		state = State.BUFFER_EMPTY;
 		// Build and return the transport update
 		return new TransportUpdate(id, new TransportProperties(p), version);
 	}
diff --git a/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java b/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java
index 1ef8e348c9..68787848ab 100644
--- a/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java
+++ b/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java
@@ -1,17 +1,19 @@
 package org.briarproject.messaging;
 
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
-import static org.briarproject.api.messaging.Types.ACK;
-import static org.briarproject.api.messaging.Types.GROUP;
-import static org.briarproject.api.messaging.Types.OFFER;
-import static org.briarproject.api.messaging.Types.REQUEST;
-import static org.briarproject.api.messaging.Types.RETENTION_ACK;
-import static org.briarproject.api.messaging.Types.RETENTION_UPDATE;
-import static org.briarproject.api.messaging.Types.SUBSCRIPTION_ACK;
-import static org.briarproject.api.messaging.Types.SUBSCRIPTION_UPDATE;
-import static org.briarproject.api.messaging.Types.TRANSPORT_ACK;
-import static org.briarproject.api.messaging.Types.TRANSPORT_UPDATE;
-
+import static org.briarproject.api.messaging.MessagingConstants.HEADER_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.PROTOCOL_VERSION;
+import static org.briarproject.api.messaging.PacketTypes.ACK;
+import static org.briarproject.api.messaging.PacketTypes.OFFER;
+import static org.briarproject.api.messaging.PacketTypes.REQUEST;
+import static org.briarproject.api.messaging.PacketTypes.RETENTION_ACK;
+import static org.briarproject.api.messaging.PacketTypes.RETENTION_UPDATE;
+import static org.briarproject.api.messaging.PacketTypes.SUBSCRIPTION_ACK;
+import static org.briarproject.api.messaging.PacketTypes.SUBSCRIPTION_UPDATE;
+import static org.briarproject.api.messaging.PacketTypes.TRANSPORT_ACK;
+import static org.briarproject.api.messaging.PacketTypes.TRANSPORT_UPDATE;
+
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
@@ -19,6 +21,7 @@ import org.briarproject.api.messaging.Ack;
 import org.briarproject.api.messaging.Group;
 import org.briarproject.api.messaging.MessageId;
 import org.briarproject.api.messaging.Offer;
+import org.briarproject.api.messaging.PacketTypes;
 import org.briarproject.api.messaging.PacketWriter;
 import org.briarproject.api.messaging.Request;
 import org.briarproject.api.messaging.RetentionAck;
@@ -30,118 +33,161 @@ import org.briarproject.api.messaging.TransportUpdate;
 import org.briarproject.api.serial.SerialComponent;
 import org.briarproject.api.serial.Writer;
 import org.briarproject.api.serial.WriterFactory;
+import org.briarproject.util.ByteUtils;
 
 // This class is not thread-safe
 class PacketWriterImpl implements PacketWriter {
 
 	private final SerialComponent serial;
+	private final WriterFactory writerFactory;
 	private final OutputStream out;
-	private final Writer w;
+	private final byte[] header;
+	private final ByteArrayOutputStream payload;
 
 	PacketWriterImpl(SerialComponent serial, WriterFactory writerFactory,
 			OutputStream out) {
 		this.serial = serial;
+		this.writerFactory = writerFactory;
 		this.out = out;
-		w = writerFactory.createWriter(out);
+		header = new byte[HEADER_LENGTH];
+		header[0] = PROTOCOL_VERSION;
+		payload = new ByteArrayOutputStream(MAX_PAYLOAD_LENGTH);
 	}
 
 	public int getMaxMessagesForAck(long capacity) {
-		return getMaxMessagesForPacket(capacity, ACK);
+		return getMaxMessagesForPacket(capacity);
 	}
 
 	public int getMaxMessagesForRequest(long capacity) {
-		return getMaxMessagesForPacket(capacity, REQUEST);
+		return getMaxMessagesForPacket(capacity);
 	}
 
 	public int getMaxMessagesForOffer(long capacity) {
-		return getMaxMessagesForPacket(capacity, OFFER);
+		return getMaxMessagesForPacket(capacity);
 	}
 
-	private int getMaxMessagesForPacket(long capacity, int structId) {
-		int packet = (int) Math.min(capacity, MAX_PACKET_LENGTH);
-		int overhead = serial.getSerialisedStructStartLength(structId)
-				+ serial.getSerialisedListStartLength()
-				+ serial.getSerialisedListEndLength()
-				+ serial.getSerialisedStructEndLength();
+	private int getMaxMessagesForPacket(long capacity) {
+		int payload = (int) Math.min(capacity - HEADER_LENGTH,
+				MAX_PAYLOAD_LENGTH);
+		int overhead = serial.getSerialisedListStartLength() * 2
+				+ serial.getSerialisedListEndLength() * 2;
 		int idLength = serial.getSerialisedUniqueIdLength();
-		return (packet - overhead) / idLength;
+		return (payload - overhead) / idLength;
+	}
+
+	private void writePacket(byte packetType) throws IOException {
+		header[1] = packetType;
+		ByteUtils.writeUint16(payload.size(), header, 2);
+		out.write(header);
+		payload.writeTo(out);
+		payload.reset();
 	}
 
 	public void writeAck(Ack a) throws IOException {
-		w.writeStructStart(ACK);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeListStart();
 		for(MessageId m : a.getMessageIds()) w.writeBytes(m.getBytes());
 		w.writeListEnd();
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(ACK);
 	}
 
 	public void writeMessage(byte[] raw) throws IOException {
+		header[1] = PacketTypes.MESSAGE;
+		ByteUtils.writeUint16(raw.length, header, 2);
+		out.write(header);
 		out.write(raw);
 	}
 
 	public void writeOffer(Offer o) throws IOException {
-		w.writeStructStart(OFFER);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeListStart();
 		for(MessageId m : o.getMessageIds()) w.writeBytes(m.getBytes());
 		w.writeListEnd();
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(OFFER);
 	}
 
 	public void writeRequest(Request r) throws IOException {
-		w.writeStructStart(REQUEST);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeListStart();
 		for(MessageId m : r.getMessageIds()) w.writeBytes(m.getBytes());
 		w.writeListEnd();
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(REQUEST);
 	}
 
 	public void writeRetentionAck(RetentionAck a) throws IOException {
-		w.writeStructStart(RETENTION_ACK);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeInteger(a.getVersion());
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(RETENTION_ACK);
 	}
 
 	public void writeRetentionUpdate(RetentionUpdate u) throws IOException {
-		w.writeStructStart(RETENTION_UPDATE);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeInteger(u.getRetentionTime());
 		w.writeInteger(u.getVersion());
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(RETENTION_UPDATE);
 	}
 
 	public void writeSubscriptionAck(SubscriptionAck a) throws IOException {
-		w.writeStructStart(SUBSCRIPTION_ACK);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeInteger(a.getVersion());
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(SUBSCRIPTION_ACK);
 	}
 
 	public void writeSubscriptionUpdate(SubscriptionUpdate u)
 			throws IOException {
-		w.writeStructStart(SUBSCRIPTION_UPDATE);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeListStart();
 		for(Group g : u.getGroups()) {
-			w.writeStructStart(GROUP);
+			w.writeListStart();
 			w.writeString(g.getName());
 			w.writeBytes(g.getSalt());
-			w.writeStructEnd();
+			w.writeListEnd();
 		}
 		w.writeListEnd();
 		w.writeInteger(u.getVersion());
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(SUBSCRIPTION_UPDATE);
 	}
 
 	public void writeTransportAck(TransportAck a) throws IOException {
-		w.writeStructStart(TRANSPORT_ACK);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeString(a.getId().getString());
 		w.writeInteger(a.getVersion());
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(TRANSPORT_ACK);
 	}
 
 	public void writeTransportUpdate(TransportUpdate u) throws IOException {
-		w.writeStructStart(TRANSPORT_UPDATE);
+		assert payload.size() == 0;
+		Writer w = writerFactory.createWriter(payload);
+		w.writeListStart();
 		w.writeString(u.getId().getString());
 		w.writeMap(u.getProperties());
 		w.writeInteger(u.getVersion());
-		w.writeStructEnd();
+		w.writeListEnd();
+		writePacket(TRANSPORT_UPDATE);
 	}
 
 	public void flush() throws IOException {
diff --git a/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java b/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java
index 6d8dfabf1a..1a5df4a26e 100644
--- a/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java
@@ -2,7 +2,7 @@ package org.briarproject.messaging;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -167,7 +167,7 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
 			if(interrupted) return;
 			try {
 				Collection<byte[]> b = db.generateBatch(contactId,
-						MAX_PACKET_LENGTH, maxLatency);
+						MAX_PAYLOAD_LENGTH, maxLatency);
 				if(LOG.isLoggable(INFO))
 					LOG.info("Generated batch: " + (b != null));
 				if(b == null) decrementOutstandingQueries();
diff --git a/briar-core/src/org/briarproject/messaging/SubscriptionUpdateReader.java b/briar-core/src/org/briarproject/messaging/SubscriptionUpdateReader.java
index 983be4a049..67d7dd88c5 100644
--- a/briar-core/src/org/briarproject/messaging/SubscriptionUpdateReader.java
+++ b/briar-core/src/org/briarproject/messaging/SubscriptionUpdateReader.java
@@ -1,8 +1,7 @@
 package org.briarproject.messaging;
 
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
-import static org.briarproject.api.messaging.Types.SUBSCRIPTION_UPDATE;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -17,29 +16,29 @@ import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.messaging.SubscriptionUpdate;
 import org.briarproject.api.serial.Consumer;
 import org.briarproject.api.serial.CountingConsumer;
+import org.briarproject.api.serial.ObjectReader;
 import org.briarproject.api.serial.Reader;
-import org.briarproject.api.serial.StructReader;
 
-class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
+class SubscriptionUpdateReader implements ObjectReader<SubscriptionUpdate> {
 
-	private final StructReader<Group> groupReader;
+	private final ObjectReader<Group> groupReader;
 
-	SubscriptionUpdateReader(StructReader<Group> groupReader) {
+	SubscriptionUpdateReader(ObjectReader<Group> groupReader) {
 		this.groupReader = groupReader;
 	}
 
-	public SubscriptionUpdate readStruct(Reader r) throws IOException {
+	public SubscriptionUpdate readObject(Reader r) throws IOException {
 		// Set up the reader
-		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
+		Consumer counting = new CountingConsumer(MAX_PAYLOAD_LENGTH);
 		r.addConsumer(counting);
-		// Read the start of the struct
-		r.readStructStart(SUBSCRIPTION_UPDATE);
+		// Read the start of the update
+		r.readListStart();
 		// Read the subscriptions, rejecting duplicates
 		List<Group> groups = new ArrayList<Group>();
 		Set<GroupId> ids = new HashSet<GroupId>();
 		r.readListStart();
 		for(int i = 0; i < MAX_SUBSCRIPTIONS && !r.hasListEnd(); i++) {
-			Group g = groupReader.readStruct(r);
+			Group g = groupReader.readObject(r);
 			if(!ids.add(g.getId())) throw new FormatException(); // Duplicate
 			groups.add(g);
 		}
@@ -47,8 +46,8 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
 		// Read the version number
 		long version = r.readInteger();
 		if(version < 0) throw new FormatException();
-		// Read the end of the struct
-		r.readStructEnd();
+		// Read the end of the update
+		r.readListEnd();
 		// Reset the reader
 		r.removeConsumer(counting);
 		// Build and return the subscription update
diff --git a/briar-core/src/org/briarproject/serial/ObjectTypes.java b/briar-core/src/org/briarproject/serial/ObjectTypes.java
new file mode 100644
index 0000000000..69bd14174f
--- /dev/null
+++ b/briar-core/src/org/briarproject/serial/ObjectTypes.java
@@ -0,0 +1,21 @@
+package org.briarproject.serial;
+
+interface ObjectTypes {
+
+	byte NULL = 0x00;
+	byte BOOLEAN = 0x11;
+	byte INT_8 = 0x21;
+	byte INT_16 = 0x22;
+	byte INT_32 = 0x24;
+	byte INT_64 = 0x28;
+	byte FLOAT_64 = 0x38;
+	byte STRING_8 = 0x41;
+	byte STRING_16 = 0x42;
+	byte STRING_32 = 0x44;
+	byte RAW_8 = 0x51;
+	byte RAW_16 = 0x52;
+	byte RAW_32 = 0x54;
+	byte LIST = 0x60;
+	byte MAP = 0x70;
+	byte END = (byte) 0x80;
+}
diff --git a/briar-core/src/org/briarproject/serial/ReaderImpl.java b/briar-core/src/org/briarproject/serial/ReaderImpl.java
index b8710776f6..efd4f88108 100644
--- a/briar-core/src/org/briarproject/serial/ReaderImpl.java
+++ b/briar-core/src/org/briarproject/serial/ReaderImpl.java
@@ -1,23 +1,20 @@
 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.END;
-import static org.briarproject.serial.Tag.FALSE;
-import static org.briarproject.serial.Tag.FLOAT;
-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_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;
+import static org.briarproject.serial.ObjectTypes.END;
+import static org.briarproject.serial.ObjectTypes.FLOAT_64;
+import static org.briarproject.serial.ObjectTypes.INT_16;
+import static org.briarproject.serial.ObjectTypes.INT_32;
+import static org.briarproject.serial.ObjectTypes.INT_64;
+import static org.briarproject.serial.ObjectTypes.INT_8;
+import static org.briarproject.serial.ObjectTypes.LIST;
+import static org.briarproject.serial.ObjectTypes.MAP;
+import static org.briarproject.serial.ObjectTypes.NULL;
+import static org.briarproject.serial.ObjectTypes.RAW_16;
+import static org.briarproject.serial.ObjectTypes.RAW_32;
+import static org.briarproject.serial.ObjectTypes.RAW_8;
+import static org.briarproject.serial.ObjectTypes.STRING_16;
+import static org.briarproject.serial.ObjectTypes.STRING_32;
+import static org.briarproject.serial.ObjectTypes.STRING_8;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,7 +34,7 @@ class ReaderImpl implements Reader {
 	private final Collection<Consumer> consumers = new ArrayList<Consumer>(0);
 
 	private boolean hasLookahead = false, eof = false;
-	private byte next, nextStructId;
+	private byte next;
 	private byte[] buf = new byte[8];
 
 	ReaderImpl(InputStream in) {
@@ -54,21 +51,12 @@ class ReaderImpl implements Reader {
 			return;
 		}
 		next = (byte) i;
-		// If necessary, read another lookahead byte
-		if(next == STRUCT) {
-			i = in.read();
-			if(i == -1) throw new FormatException();
-			nextStructId = (byte) i;
-		}
 		hasLookahead = true;
 	}
 
 	private void consumeLookahead() throws IOException {
 		assert hasLookahead;
-		for(Consumer c : consumers) {
-			c.write(next);
-			if(next == STRUCT) c.write(nextStructId);
-		}
+		for(Consumer c : consumers) c.write(next);
 		hasLookahead = false;
 	}
 
@@ -101,11 +89,10 @@ class ReaderImpl implements Reader {
 		if(hasBoolean()) skipBoolean();
 		else if(hasInteger()) skipInteger();
 		else if(hasFloat()) skipFloat();
-		else if(hasString()) skipString(Integer.MAX_VALUE);
-		else if(hasBytes()) skipBytes(Integer.MAX_VALUE);
+		else if(hasString()) skipString();
+		else if(hasBytes()) skipBytes();
 		else if(hasList()) skipList();
 		else if(hasMap()) skipMap();
-		else if(hasStruct()) skipStruct();
 		else if(hasNull()) skipNull();
 		else throw new FormatException();
 	}
@@ -127,36 +114,59 @@ class ReaderImpl implements Reader {
 		if(!consumers.remove(c)) throw new IllegalArgumentException();
 	}
 
+	public boolean hasNull() throws IOException {
+		if(!hasLookahead) readLookahead();
+		if(eof) return false;
+		return next == NULL;
+	}
+
+	public void readNull() throws IOException {
+		if(!hasNull()) throw new FormatException();
+		consumeLookahead();
+	}
+
+	public void skipNull() throws IOException {
+		if(!hasNull()) throw new FormatException();
+		hasLookahead = false;
+	}
+
 	public boolean hasBoolean() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
-		return next == FALSE || next == TRUE;
+		return next == ObjectTypes.BOOLEAN;
 	}
 
 	public boolean readBoolean() throws IOException {
 		if(!hasBoolean()) throw new FormatException();
 		consumeLookahead();
-		return next == TRUE;
+		return readBoolean(true);
+	}
+
+	private boolean readBoolean(boolean consume) throws IOException {
+		readIntoBuffer(1, consume);
+		if(buf[0] != 0 && buf[0] != 1) throw new FormatException();
+		return buf[0] == 1;
 	}
 
 	public void skipBoolean() throws IOException {
 		if(!hasBoolean()) throw new FormatException();
+		skip(1);
 		hasLookahead = false;
 	}
 
 	public boolean hasInteger() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
-		return next == INTEGER_8 || next == INTEGER_16 || next == INTEGER_32 ||
-				next == INTEGER_64;
+		return next == INT_8 || next == INT_16 || next == INT_32 ||
+				next == INT_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);
+		if(next == INT_8) return readInt8(true);
+		if(next == INT_16) return readInt16(true);
+		if(next == INT_32) return readInt32(true);
 		return readInt64(true);
 	}
 
@@ -193,9 +203,9 @@ class ReaderImpl implements Reader {
 
 	public void skipInteger() throws IOException {
 		if(!hasInteger()) throw new FormatException();
-		if(next == INTEGER_8) skip(1);
-		else if(next == INTEGER_16) skip(2);
-		else if(next == INTEGER_32) skip(4);
+		if(next == INT_8) skip(1);
+		else if(next == INT_16) skip(2);
+		else if(next == INT_32) skip(4);
 		else skip(8);
 		hasLookahead = false;
 	}
@@ -203,7 +213,7 @@ class ReaderImpl implements Reader {
 	public boolean hasFloat() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
-		return next == FLOAT;
+		return next == FLOAT_64;
 	}
 
 	public double readFloat() throws IOException {
@@ -244,10 +254,10 @@ class ReaderImpl implements Reader {
 		throw new FormatException();
 	}
 
-	public void skipString(int maxLength) throws IOException {
+	public void skipString() throws IOException {
 		if(!hasString()) throw new FormatException();
 		int length = readStringLength(false);
-		if(length < 0 || length > maxLength) throw new FormatException();
+		if(length < 0) throw new FormatException();
 		skip(length);
 		hasLookahead = false;
 	}
@@ -255,7 +265,7 @@ class ReaderImpl implements Reader {
 	public boolean hasBytes() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
-		return next == BYTES_8 || next == BYTES_16 || next == BYTES_32;
+		return next == RAW_8 || next == RAW_16 || next == RAW_32;
 	}
 
 	public byte[] readBytes(int maxLength) throws IOException {
@@ -270,16 +280,16 @@ class ReaderImpl implements Reader {
 	}
 
 	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);
+		if(next == RAW_8) return readInt8(consume);
+		if(next == RAW_16) return readInt16(consume);
+		if(next == RAW_32) return readInt32(consume);
 		throw new FormatException();
 	}
 
-	public void skipBytes(int maxLength) throws IOException {
+	public void skipBytes() throws IOException {
 		if(!hasBytes()) throw new FormatException();
 		int length = readBytesLength(false);
-		if(length < 0 || length > maxLength) throw new FormatException();
+		if(length < 0) throw new FormatException();
 		skip(length);
 		hasLookahead = false;
 	}
@@ -349,53 +359,4 @@ class ReaderImpl implements Reader {
 		}
 		hasLookahead = false;
 	}
-
-	public boolean hasStruct() throws IOException {
-		if(!hasLookahead) readLookahead();
-		if(eof) return false;
-		return next == STRUCT;
-	}
-
-	public boolean hasStruct(int id) throws IOException {
-		if(id < 0 || id > 255) throw new IllegalArgumentException();
-		if(!hasLookahead) readLookahead();
-		if(eof) return false;
-		return next == STRUCT && (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 void skipStruct() throws IOException {
-		if(!hasStruct()) throw new FormatException();
-		hasLookahead = false;
-		while(!hasStructEnd()) skipObject();
-		hasLookahead = false;
-	}
-
-	public boolean hasNull() throws IOException {
-		if(!hasLookahead) readLookahead();
-		if(eof) return false;
-		return next == NULL;
-	}
-
-	public void readNull() throws IOException {
-		if(!hasNull()) throw new FormatException();
-		consumeLookahead();
-	}
-
-	public void skipNull() throws IOException {
-		if(!hasNull()) throw new FormatException();
-		hasLookahead = false;
-	}
 }
diff --git a/briar-core/src/org/briarproject/serial/SerialComponentImpl.java b/briar-core/src/org/briarproject/serial/SerialComponentImpl.java
index 8a7a42ab1f..ce6516260e 100644
--- a/briar-core/src/org/briarproject/serial/SerialComponentImpl.java
+++ b/briar-core/src/org/briarproject/serial/SerialComponentImpl.java
@@ -15,16 +15,6 @@ class SerialComponentImpl implements SerialComponent {
 		return 1;
 	}
 
-	public int getSerialisedStructStartLength(int id) {
-		// STRUCT tag, 8-bit ID
-		return 2;
-	}
-
-	public int getSerialisedStructEndLength() {
-		// END tag
-		return 1;
-	}
-
 	public int getSerialisedUniqueIdLength() {
 		// BYTES_8, BYTES_16 or BYTES_32 tag, length, bytes
 		return 1 + getLengthBytes(UniqueId.LENGTH) + UniqueId.LENGTH;
diff --git a/briar-core/src/org/briarproject/serial/Tag.java b/briar-core/src/org/briarproject/serial/Tag.java
deleted file mode 100644
index 9fe385dfac..0000000000
--- a/briar-core/src/org/briarproject/serial/Tag.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.briarproject.serial;
-
-interface Tag {
-
-	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 45995cdb0e..4b7a2de937 100644
--- a/briar-core/src/org/briarproject/serial/WriterImpl.java
+++ b/briar-core/src/org/briarproject/serial/WriterImpl.java
@@ -1,18 +1,21 @@
 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_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 static org.briarproject.serial.ObjectTypes.BOOLEAN;
+import static org.briarproject.serial.ObjectTypes.END;
+import static org.briarproject.serial.ObjectTypes.FLOAT_64;
+import static org.briarproject.serial.ObjectTypes.INT_16;
+import static org.briarproject.serial.ObjectTypes.INT_32;
+import static org.briarproject.serial.ObjectTypes.INT_64;
+import static org.briarproject.serial.ObjectTypes.INT_8;
+import static org.briarproject.serial.ObjectTypes.LIST;
+import static org.briarproject.serial.ObjectTypes.MAP;
+import static org.briarproject.serial.ObjectTypes.NULL;
+import static org.briarproject.serial.ObjectTypes.RAW_16;
+import static org.briarproject.serial.ObjectTypes.RAW_32;
+import static org.briarproject.serial.ObjectTypes.RAW_8;
+import static org.briarproject.serial.ObjectTypes.STRING_16;
+import static org.briarproject.serial.ObjectTypes.STRING_32;
+import static org.briarproject.serial.ObjectTypes.STRING_8;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -52,23 +55,28 @@ class WriterImpl implements Writer {
 		if(!consumers.remove(c)) throw new IllegalArgumentException();
 	}
 
+	public void writeNull() throws IOException {
+		write(NULL);
+	}
+
 	public void writeBoolean(boolean b) throws IOException {
-		if(b) write(TRUE);
-		else write(FALSE);
+		write(BOOLEAN);
+		if(b) write((byte) 1);
+		else write((byte) 0);
 	}
 
 	public void writeInteger(long i) throws IOException {
 		if(i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
-			write(INTEGER_8);
+			write(INT_8);
 			write((byte) i);
 		} else if(i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
-			write(INTEGER_16);
+			write(INT_16);
 			writeInt16((short) i);
 		} else if(i >= Integer.MIN_VALUE && i <= Integer.MAX_VALUE) {
-			write(INTEGER_32);
+			write(INT_32);
 			writeInt32((int) i);
 		} else {
-			write(INTEGER_64);
+			write(INT_64);
 			writeInt64(i);
 		}
 	}
@@ -97,7 +105,7 @@ class WriterImpl implements Writer {
 	}
 
 	public void writeFloat(double d) throws IOException {
-		write(FLOAT);
+		write(FLOAT_64);
 		writeInt64(Double.doubleToRawLongBits(d));
 	}
 
@@ -118,22 +126,22 @@ class WriterImpl implements Writer {
 
 	public void writeBytes(byte[] b) throws IOException {
 		if(b.length <= Byte.MAX_VALUE) {
-			write(BYTES_8);
+			write(RAW_8);
 			write((byte) b.length);
 		} else if(b.length <= Short.MAX_VALUE) {
-			write(BYTES_16);
+			write(RAW_16);
 			writeInt16((short) b.length);
 		} else {
-			write(BYTES_32);
+			write(RAW_32);
 			writeInt32(b.length);
 		}
 		write(b);
 	}
 
 	public void writeList(Collection<?> c) throws IOException {
-		write(Tag.LIST);
+		write(ObjectTypes.LIST);
 		for(Object o : c) writeObject(o);
-		write(Tag.END);
+		write(ObjectTypes.END);
 	}
 
 	private void writeObject(Object o) throws IOException {
@@ -154,42 +162,28 @@ class WriterImpl implements Writer {
 	}
 
 	public void writeListStart() throws IOException {
-		write(Tag.LIST);
+		write(LIST);
 	}
 
 	public void writeListEnd() throws IOException {
-		write(Tag.END);
+		write(END);
 	}
 
 	public void writeMap(Map<?, ?> m) throws IOException {
-		write(Tag.MAP);
+		write(MAP);
 		for(Entry<?, ?> e : m.entrySet()) {
 			writeObject(e.getKey());
 			writeObject(e.getValue());
 		}
-		write(Tag.END);
+		write(END);
 	}
 
 	public void writeMapStart() throws IOException {
-		write(Tag.MAP);
+		write(MAP);
 	}
 
 	public void writeMapEnd() throws IOException {
-		write(Tag.END);
-	}
-
-	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);
+		write(END);
 	}
 
 	private void write(byte b) throws IOException {
diff --git a/briar-tests/src/org/briarproject/messaging/ConstantsTest.java b/briar-tests/src/org/briarproject/messaging/ConstantsTest.java
index 300190d1f3..affa0f573d 100644
--- a/briar-tests/src/org/briarproject/messaging/ConstantsTest.java
+++ b/briar-tests/src/org/briarproject/messaging/ConstantsTest.java
@@ -9,7 +9,7 @@ import static org.briarproject.api.TransportPropertyConstants.MAX_TRANSPORT_ID_L
 import static org.briarproject.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
 
 import java.io.ByteArrayOutputStream;
@@ -106,7 +106,7 @@ public class ConstantsTest extends BriarTestCase {
 
 	@Test
 	public void testMessageIdsFitIntoLargeAck() throws Exception {
-		testMessageIdsFitIntoAck(MAX_PACKET_LENGTH);
+		testMessageIdsFitIntoAck(MAX_PAYLOAD_LENGTH);
 	}
 
 	@Test
@@ -139,12 +139,12 @@ public class ConstantsTest extends BriarTestCase {
 				+ MAX_PUBLIC_KEY_LENGTH + MAX_AUTHOR_NAME_LENGTH
 				+ MAX_PUBLIC_KEY_LENGTH + MAX_CONTENT_TYPE_LENGTH
 				+ MAX_BODY_LENGTH);
-		assertTrue(length <= MAX_PACKET_LENGTH);
+		assertTrue(length <= MAX_PAYLOAD_LENGTH);
 	}
 
 	@Test
 	public void testMessageIdsFitIntoLargeOffer() throws Exception {
-		testMessageIdsFitIntoOffer(MAX_PACKET_LENGTH);
+		testMessageIdsFitIntoOffer(MAX_PAYLOAD_LENGTH);
 	}
 
 	@Test
@@ -154,7 +154,7 @@ public class ConstantsTest extends BriarTestCase {
 
 	@Test
 	public void testMessageIdsFitIntoLargeRequest() throws Exception {
-		testMessageIdsFitIntoRequest(MAX_PACKET_LENGTH);
+		testMessageIdsFitIntoRequest(MAX_PAYLOAD_LENGTH);
 	}
 
 	@Test
@@ -180,7 +180,7 @@ public class ConstantsTest extends BriarTestCase {
 		PacketWriter writer = packetWriterFactory.createPacketWriter(out);
 		writer.writeTransportUpdate(u);
 		// Check the size of the serialised transport update
-		assertTrue(out.size() <= MAX_PACKET_LENGTH);
+		assertTrue(out.size() <= MAX_PAYLOAD_LENGTH);
 	}
 
 	@Test
@@ -198,7 +198,7 @@ public class ConstantsTest extends BriarTestCase {
 		PacketWriter writer = packetWriterFactory.createPacketWriter(out);
 		writer.writeSubscriptionUpdate(u);
 		// Check the size of the serialised subscription update
-		assertTrue(out.size() <= MAX_PACKET_LENGTH);
+		assertTrue(out.size() <= MAX_PAYLOAD_LENGTH);
 	}
 
 	private void testMessageIdsFitIntoAck(int length) throws Exception {
diff --git a/briar-tests/src/org/briarproject/messaging/PacketReaderImplTest.java b/briar-tests/src/org/briarproject/messaging/PacketReaderImplTest.java
index 097f2dab19..ec3562250a 100644
--- a/briar-tests/src/org/briarproject/messaging/PacketReaderImplTest.java
+++ b/briar-tests/src/org/briarproject/messaging/PacketReaderImplTest.java
@@ -1,9 +1,10 @@
 package org.briarproject.messaging;
 
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
-import static org.briarproject.api.messaging.Types.ACK;
-import static org.briarproject.api.messaging.Types.OFFER;
-import static org.briarproject.api.messaging.Types.REQUEST;
+import static org.briarproject.api.messaging.MessagingConstants.HEADER_LENGTH;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PAYLOAD_LENGTH;
+import static org.briarproject.api.messaging.PacketTypes.ACK;
+import static org.briarproject.api.messaging.PacketTypes.OFFER;
+import static org.briarproject.api.messaging.PacketTypes.REQUEST;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -16,6 +17,7 @@ import org.briarproject.api.serial.SerialComponent;
 import org.briarproject.api.serial.Writer;
 import org.briarproject.api.serial.WriterFactory;
 import org.briarproject.serial.SerialModule;
+import org.briarproject.util.ByteUtils;
 import org.junit.Test;
 
 import com.google.inject.Guice;
@@ -137,85 +139,106 @@ public class PacketReaderImplTest extends BriarTestCase {
 
 	private byte[] createAck(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(new byte[HEADER_LENGTH]);
 		Writer w = writerFactory.createWriter(out);
-		w.writeStructStart(ACK);
+		w.writeListStart();
 		w.writeListStart();
 		while(out.size() + serial.getSerialisedUniqueIdLength()
-				+ serial.getSerialisedListEndLength()
-				+ serial.getSerialisedStructEndLength()
-				< MAX_PACKET_LENGTH) {
+				+ serial.getSerialisedListEndLength() * 2
+				< HEADER_LENGTH + MAX_PAYLOAD_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();
+		w.writeListEnd();
+		assertEquals(tooBig, out.size() > HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
+		byte[] packet = out.toByteArray();
+		packet[1] = ACK;
+		ByteUtils.writeUint16(packet.length - HEADER_LENGTH, packet, 2);
+		return packet;
 	}
 
 	private byte[] createEmptyAck() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(new byte[HEADER_LENGTH]);
 		Writer w = writerFactory.createWriter(out);
-		w.writeStructStart(ACK);
+		w.writeListStart();
 		w.writeListStart();
 		w.writeListEnd();
-		w.writeStructEnd();
-		return out.toByteArray();
+		w.writeListEnd();
+		byte[] packet = out.toByteArray();
+		packet[1] = ACK;
+		ByteUtils.writeUint16(packet.length - HEADER_LENGTH, packet, 2);
+		return packet;
 	}
 
 	private byte[] createOffer(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(new byte[HEADER_LENGTH]);
 		Writer w = writerFactory.createWriter(out);
-		w.writeStructStart(OFFER);
+		w.writeListStart();
 		w.writeListStart();
 		while(out.size() + serial.getSerialisedUniqueIdLength()
-				+ serial.getSerialisedListEndLength()
-				+ serial.getSerialisedStructEndLength()
-				< MAX_PACKET_LENGTH) {
+				+ serial.getSerialisedListEndLength() * 2
+				< HEADER_LENGTH + MAX_PAYLOAD_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();
+		w.writeListEnd();
+		assertEquals(tooBig, out.size() > HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
+		byte[] packet = out.toByteArray();
+		packet[1] = OFFER;
+		ByteUtils.writeUint16(packet.length - HEADER_LENGTH, packet, 2);
+		return packet;
 	}
 
 	private byte[] createEmptyOffer() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(new byte[HEADER_LENGTH]);
 		Writer w = writerFactory.createWriter(out);
-		w.writeStructStart(OFFER);
 		w.writeListStart();
+		w.writeListStart();
+		w.writeListEnd();
 		w.writeListEnd();
-		w.writeStructEnd();
-		return out.toByteArray();
+		byte[] packet = out.toByteArray();
+		packet[1] = OFFER;
+		ByteUtils.writeUint16(packet.length - HEADER_LENGTH, packet, 2);
+		return packet;
 	}
 
 	private byte[] createRequest(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(new byte[HEADER_LENGTH]);
 		Writer w = writerFactory.createWriter(out);
-		w.writeStructStart(REQUEST);
+		w.writeListStart();
 		w.writeListStart();
 		while(out.size() + serial.getSerialisedUniqueIdLength()
-				+ serial.getSerialisedListEndLength()
-				+ serial.getSerialisedStructEndLength()
-				< MAX_PACKET_LENGTH) {
+				+ serial.getSerialisedListEndLength() * 2
+				< HEADER_LENGTH + MAX_PAYLOAD_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();
+		w.writeListEnd();
+		assertEquals(tooBig, out.size() > HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
+		byte[] packet = out.toByteArray();
+		packet[1] = REQUEST;
+		ByteUtils.writeUint16(packet.length - HEADER_LENGTH, packet, 2);
+		return packet;
 	}
 
 	private byte[] createEmptyRequest() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(new byte[HEADER_LENGTH]);
 		Writer w = writerFactory.createWriter(out);
-		w.writeStructStart(REQUEST);
 		w.writeListStart();
+		w.writeListStart();
+		w.writeListEnd();
 		w.writeListEnd();
-		w.writeStructEnd();
-		return out.toByteArray();
+		byte[] packet = out.toByteArray();
+		packet[1] = REQUEST;
+		ByteUtils.writeUint16(packet.length - HEADER_LENGTH, packet, 2);
+		return packet;
 	}
 }
diff --git a/briar-tests/src/org/briarproject/serial/ReaderImplTest.java b/briar-tests/src/org/briarproject/serial/ReaderImplTest.java
index 54210b540a..6b093c2e02 100644
--- a/briar-tests/src/org/briarproject/serial/ReaderImplTest.java
+++ b/briar-tests/src/org/briarproject/serial/ReaderImplTest.java
@@ -15,9 +15,29 @@ public class ReaderImplTest extends BriarTestCase {
 	private ByteArrayInputStream in = null;
 	private ReaderImpl r = null;
 
+	@Test
+	public void testReadEmptyInput() throws Exception {
+		setContents("");
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadNull() throws Exception {
+		setContents("00");
+		r.readNull();
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testSkipNull() throws Exception {
+		setContents("00");
+		r.skipNull();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadBoolean() throws Exception {
-		setContents("00" + "01");
+		setContents("11" + "00" + "11" + "01");
 		assertFalse(r.readBoolean());
 		assertTrue(r.readBoolean());
 		assertTrue(r.eof());
@@ -25,7 +45,7 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipBoolean() throws Exception {
-		setContents("00" + "01");
+		setContents("11" + "00" + "11" + "01");
 		r.skipBoolean();
 		r.skipBoolean();
 		assertTrue(r.eof());
@@ -33,8 +53,8 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testReadInt8() throws Exception {
-		setContents("02" + "00" + "02" + "FF"
-				+ "02" + "7F" + "02" + "80");
+		setContents("21" + "00" + "21" + "FF"
+				+ "21" + "7F" + "21" + "80");
 		assertEquals(0, r.readInteger());
 		assertEquals(-1, r.readInteger());
 		assertEquals(Byte.MAX_VALUE, r.readInteger());
@@ -44,15 +64,15 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipInt8() throws Exception {
-		setContents("02" + "00");
+		setContents("21" + "00");
 		r.skipInteger();
 		assertTrue(r.eof());
 	}
 
 	@Test
 	public void testReadInt16() throws Exception {
-		setContents("03" + "0080" + "03" + "FF7F"
-				+ "03" + "7FFF" + "03" + "8000");
+		setContents("22" + "0080" + "22" + "FF7F"
+				+ "22" + "7FFF" + "22" + "8000");
 		assertEquals(Byte.MAX_VALUE + 1, r.readInteger());
 		assertEquals(Byte.MIN_VALUE - 1, r.readInteger());
 		assertEquals(Short.MAX_VALUE, r.readInteger());
@@ -62,15 +82,15 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipInt16() throws Exception {
-		setContents("03" + "0080");
+		setContents("22" + "0080");
 		r.skipInteger();
 		assertTrue(r.eof());
 	}
 
 	@Test
 	public void testReadInt32() throws Exception {
-		setContents("04" + "00008000" + "04" + "FFFF7FFF"
-				+ "04" + "7FFFFFFF" + "04" + "80000000");
+		setContents("24" + "00008000" + "24" + "FFFF7FFF"
+				+ "24" + "7FFFFFFF" + "24" + "80000000");
 		assertEquals(Short.MAX_VALUE + 1, r.readInteger());
 		assertEquals(Short.MIN_VALUE - 1, r.readInteger());
 		assertEquals(Integer.MAX_VALUE, r.readInteger());
@@ -80,15 +100,15 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipInt32() throws Exception {
-		setContents("04" + "00008000");
+		setContents("24" + "00008000");
 		r.skipInteger();
 		assertTrue(r.eof());
 	}
 
 	@Test
 	public void testReadInt64() throws Exception {
-		setContents("05" + "0000000080000000" + "05" + "FFFFFFFF7FFFFFFF"
-				+ "05" + "7FFFFFFFFFFFFFFF" + "05" + "8000000000000000");
+		setContents("28" + "0000000080000000" + "28" + "FFFFFFFF7FFFFFFF"
+				+ "28" + "7FFFFFFFFFFFFFFF" + "28" + "8000000000000000");
 		assertEquals(Integer.MAX_VALUE + 1L, r.readInteger());
 		assertEquals(Integer.MIN_VALUE - 1L, r.readInteger());
 		assertEquals(Long.MAX_VALUE, r.readInteger());
@@ -98,7 +118,7 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipInt64() throws Exception {
-		setContents("05" + "0000000080000000");
+		setContents("28" + "0000000080000000");
 		r.skipInteger();
 		assertTrue(r.eof());
 	}
@@ -106,39 +126,39 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testIntegersMustHaveMinimalLength() throws Exception {
 		// INTEGER_16 could be encoded as INTEGER_8
-		setContents("02" + "7F" + "03" + "007F");
+		setContents("21" + "7F" + "22" + "007F");
 		assertEquals(Byte.MAX_VALUE, r.readInteger());
 		try {
 			r.readInteger();
 			fail();
 		} catch(FormatException expected) {}
-		setContents("02" + "80" + "03" + "FF80");
+		setContents("21" + "80" + "22" + "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");
+		setContents("22" + "7FFF" + "24" + "00007FFF");
 		assertEquals(Short.MAX_VALUE, r.readInteger());
 		try {
 			r.readInteger();
 			fail();
 		} catch(FormatException expected) {}
-		setContents("03" + "8000" + "04" + "FFFF8000");
+		setContents("22" + "8000" + "24" + "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");
+		setContents("24" + "7FFFFFFF" + "28" + "000000007FFFFFFF");
 		assertEquals(Integer.MAX_VALUE, r.readInteger());
 		try {
 			r.readInteger();
 			fail();
 		} catch(FormatException expected) {}
-		setContents("04" + "80000000" + "05" + "FFFFFFFF80000000");
+		setContents("24" + "80000000" + "28" + "FFFFFFFF80000000");
 		assertEquals(Integer.MIN_VALUE, r.readInteger());
 		try {
 			r.readInteger();
@@ -150,10 +170,10 @@ public class ReaderImplTest extends BriarTestCase {
 	public void testReadFloat() throws Exception {
 		// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
 		// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
-		setContents("06" + "0000000000000000" + "06" + "3FF0000000000000"
-				+ "06" + "4000000000000000" + "06" + "BFF0000000000000"
-				+ "06" + "8000000000000000" + "06" + "FFF0000000000000"
-				+ "06" + "7FF0000000000000" + "06" + "7FF8000000000000");
+		setContents("38" + "0000000000000000" + "38" + "3FF0000000000000"
+				+ "38" + "4000000000000000" + "38" + "BFF0000000000000"
+				+ "38" + "8000000000000000" + "38" + "FFF0000000000000"
+				+ "38" + "7FF0000000000000" + "38" + "7FF8000000000000");
 		assertEquals(0.0, r.readFloat());
 		assertEquals(1.0, r.readFloat());
 		assertEquals(2.0, r.readFloat());
@@ -167,7 +187,7 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipFloat() throws Exception {
-		setContents("06" + "0000000000000000");
+		setContents("38" + "0000000000000000");
 		r.skipFloat();
 		assertTrue(r.eof());
 	}
@@ -177,8 +197,8 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("41" + "03" + "666F6F" + "41" + "00" +
+				"41" + "7F" + longHex);
 		assertEquals("foo", r.readString(Integer.MAX_VALUE));
 		assertEquals("", r.readString(Integer.MAX_VALUE));
 		assertEquals(longest, r.readString(Integer.MAX_VALUE));
@@ -188,7 +208,7 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testReadString8ChecksMaxLength() throws Exception {
 		// "foo" twice
-		setContents("07" + "03" + "666F6F" + "07" + "03" + "666F6F");
+		setContents("41" + "03" + "666F6F" + "41" + "03" + "666F6F");
 		assertEquals("foo", r.readString(3));
 		assertTrue(r.hasString());
 		try {
@@ -202,26 +222,14 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("41" + "03" + "666F6F" + "41" + "00" +
+				"41" + "7F" + longHex);
+		r.skipString();
+		r.skipString();
+		r.skipString();
 		assertTrue(r.eof());
 	}
 
-	@Test
-	public void testSkipString8ChecksMaxLength() throws Exception {
-		// "foo" twice
-		setContents("07" + "03" + "666F6F" + "07" + "03" + "666F6F");
-		r.skipString(3);
-		assertTrue(r.hasString());
-		try {
-			r.skipString(2);
-			fail();
-		} catch(FormatException expected) {}
-	}
-
 	@Test
 	public void testReadString16() throws Exception {
 		String shortest = TestUtils.createRandomString(Byte.MAX_VALUE + 1);
@@ -229,7 +237,7 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
 		assertEquals(shortest, r.readString(Integer.MAX_VALUE));
 		assertEquals(longest, r.readString(Integer.MAX_VALUE));
 		assertTrue(r.eof());
@@ -240,7 +248,7 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("42" + "0080" + shortHex + "42" + "0080" + shortHex);
 		assertEquals(shortest, r.readString(Byte.MAX_VALUE + 1));
 		assertTrue(r.hasString());
 		try {
@@ -256,32 +264,18 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
+		r.skipString();
+		r.skipString();
 		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);
+		setContents("44" + "00008000" + shortHex);
 		assertEquals(shortest, r.readString(Integer.MAX_VALUE));
 		assertTrue(r.eof());
 	}
@@ -291,8 +285,8 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("44" + "00008000" + shortHex +
+				"44" + "00008000" + shortHex);
 		assertEquals(shortest, r.readString(Short.MAX_VALUE + 1));
 		assertTrue(r.hasString());
 		try {
@@ -306,34 +300,19 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("44" + "00008000" + shortHex +
+				"44" + "00008000" + shortHex);
+		r.skipString();
+		r.skipString();
 		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);
+		setContents("41" + "7F" + long8Hex + "42" + "007F" + long8Hex);
 		assertEquals(longest8, r.readString(Integer.MAX_VALUE));
 		try {
 			r.readString(Integer.MAX_VALUE);
@@ -342,7 +321,7 @@ public class ReaderImplTest extends BriarTestCase {
 		// 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);
+		setContents("42" + "7FFF" + long16Hex + "44" + "00007FFF" + long16Hex);
 		assertEquals(longest16, r.readString(Integer.MAX_VALUE));
 		try {
 			r.readString(Integer.MAX_VALUE);
@@ -355,8 +334,8 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("51" + "03" + "010203" + "51" + "00" +
+				"51" + "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));
@@ -366,7 +345,7 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testReadBytes8ChecksMaxLength() throws Exception {
 		// {1, 2, 3} twice
-		setContents("0A" + "03" + "010203" + "0A" + "03" + "010203");
+		setContents("51" + "03" + "010203" + "51" + "03" + "010203");
 		assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3));
 		assertTrue(r.hasBytes());
 		try {
@@ -380,26 +359,14 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("51" + "03" + "010203" + "51" + "00" +
+				"51" + "7F" + longHex);
+		r.skipBytes();
+		r.skipBytes();
+		r.skipBytes();
 		assertTrue(r.eof());
 	}
 
-	@Test
-	public void testSkipBytes8ChecksMaxLength() throws Exception {
-		// {1, 2, 3} twice
-		setContents("0A" + "03" + "010203" + "0A" + "03" + "010203");
-		r.skipBytes(3);
-		assertTrue(r.hasBytes());
-		try {
-			r.skipBytes(2);
-			fail();
-		} catch(FormatException expected) {}
-	}
-
 	@Test
 	public void testReadBytes16() throws Exception {
 		byte[] shortest = new byte[Byte.MAX_VALUE + 1];
@@ -407,7 +374,7 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
 		assertArrayEquals(shortest, r.readBytes(Integer.MAX_VALUE));
 		assertArrayEquals(longest, r.readBytes(Integer.MAX_VALUE));
 		assertTrue(r.eof());
@@ -418,7 +385,7 @@ public class ReaderImplTest extends BriarTestCase {
 		byte[] shortest = new byte[Byte.MAX_VALUE + 1];
 		String shortHex = StringUtils.toHexString(shortest);
 		// 128 zero bytes, twice
-		setContents("0B" + "0080" + shortHex + "0B" + "0080" + shortHex);
+		setContents("52" + "0080" + shortHex + "52" + "0080" + shortHex);
 		assertArrayEquals(shortest, r.readBytes(Byte.MAX_VALUE + 1));
 		assertTrue(r.hasBytes());
 		try {
@@ -434,32 +401,18 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
+		r.skipBytes();
+		r.skipBytes();
 		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);
+		setContents("54" + "00008000" + shortHex);
 		assertArrayEquals(shortest, r.readBytes(Integer.MAX_VALUE));
 		assertTrue(r.eof());
 	}
@@ -469,8 +422,8 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("54" + "00008000" + shortHex +
+				"54" + "00008000" + shortHex);
 		assertArrayEquals(shortest, r.readBytes(Short.MAX_VALUE + 1));
 		assertTrue(r.hasBytes());
 		try {
@@ -484,43 +437,28 @@ public class ReaderImplTest extends BriarTestCase {
 		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);
+		setContents("54" + "00008000" + shortHex +
+				"54" + "00008000" + shortHex);
+		r.skipBytes();
+		r.skipBytes();
 		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
+		// RAW_16 could be encoded as RAW_8
 		byte[] longest8 = new byte[Byte.MAX_VALUE];
 		String long8Hex = StringUtils.toHexString(longest8);
-		setContents("0A" + "7F" + long8Hex + "0B" + "007F" + long8Hex);
+		setContents("51" + "7F" + long8Hex + "52" + "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
+		// RAW_32 could be encoded as RAW_16
 		byte[] longest16 = new byte[Short.MAX_VALUE];
 		String long16Hex = StringUtils.toHexString(longest16);
-		setContents("0B" + "7FFF" + long16Hex + "0C" + "00007FFF" + long16Hex);
+		setContents("52" + "7FFF" + long16Hex + "54" + "00007FFF" + long16Hex);
 		assertArrayEquals(longest16, r.readBytes(Integer.MAX_VALUE));
 		try {
 			r.readBytes(Integer.MAX_VALUE);
@@ -531,9 +469,9 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testReadList() throws Exception {
 		// A list containing 1, "foo", and 128
-		setContents("0D" + "02" + "01" +
-				"07" + "03" + "666F6F" +
-				"03" + "0080" + "10");
+		setContents("60" + "21" + "01" +
+				"41" + "03" + "666F6F" +
+				"22" + "0080" + "80");
 		r.readListStart();
 		assertFalse(r.hasListEnd());
 		assertEquals(1, r.readInteger());
@@ -549,25 +487,25 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testSkipList() throws Exception {
 		// A list containing 1, "foo", and 128
-		setContents("0D" + "02" + "01" +
-				"07" + "03" + "666F6F" +
-				"03" + "0080" + "10");
+		setContents("60" + "21" + "01" +
+				"41" + "03" + "666F6F" +
+				"22" + "0080" + "80");
 		r.skipList();
 		assertTrue(r.eof());
 	}
 
 	@Test
 	public void testReadMap() throws Exception {
-		// A map containing "foo" -> 123 and byte[0] -> null
-		setContents("0E" + "07" + "03" + "666F6F" + "02" + "7B" +
-				"0A" + "00" + "11" + "10");
+		// A map containing "foo" -> 123 and "bar" -> null
+		setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
+				"41" + "03" + "626172" + "00" + "80");
 		r.readMapStart();
 		assertFalse(r.hasMapEnd());
 		assertEquals("foo", r.readString(1000));
 		assertFalse(r.hasMapEnd());
 		assertEquals(123, r.readInteger());
 		assertFalse(r.hasMapEnd());
-		assertArrayEquals(new byte[0], r.readBytes(1000));
+		assertEquals("bar", r.readString(1000));
 		assertFalse(r.hasMapEnd());
 		assertTrue(r.hasNull());
 		r.readNull();
@@ -578,58 +516,18 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testSkipMap() throws Exception {
-		// A map containing "foo" -> 123 and byte[0] -> null
-		setContents("0E" + "07" + "03" + "666F6F" + "02" + "7B" +
-				"0A" + "00" + "11" + "10");
+		// A map containing "foo" -> 123 and "bar" -> null
+		setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
+				"41" + "03" + "626172" + "00" + "80");
 		r.skipMap();
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testReadStruct() throws Exception {
-		// Two empty structs with IDs 0 and 255
-		setContents("0F00" + "10" + "0FFF" + "10");
-		r.readStructStart(0);
-		r.readStructEnd();
-		r.readStructStart(255);
-		r.readStructEnd();
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testSkipStruct() throws Exception {
-		// Two empty structs with IDs 0 and 255
-		setContents("0F00" + "10" + "0FFF" + "10");
-		r.skipStruct();
-		r.skipStruct();
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testSkipNestedStructMapAndList() throws Exception {
-		// A struct containing a map containing two empty lists
-		setContents("0F00" + "0E" + "0D" + "10" + "0D" + "10" + "10" + "10");
-		r.skipStruct();
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testReadNull() throws Exception {
-		setContents("11");
-		r.readNull();
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testSkipNull() throws Exception {
-		setContents("11");
-		r.skipNull();
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testReadEmptyInput() throws Exception {
-		setContents("");
+	public void testSkipNestedListsAndMaps() throws Exception {
+		// A list containing a map containing two empty lists
+		setContents("60" + "70" + "60" + "80" + "60" + "80" + "80" + "80");
+		r.skipList();
 		assertTrue(r.eof());
 	}
 
diff --git a/briar-tests/src/org/briarproject/serial/WriterImplTest.java b/briar-tests/src/org/briarproject/serial/WriterImplTest.java
index 6535bd2cbb..4a73af42c6 100644
--- a/briar-tests/src/org/briarproject/serial/WriterImplTest.java
+++ b/briar-tests/src/org/briarproject/serial/WriterImplTest.java
@@ -19,18 +19,25 @@ public class WriterImplTest extends BriarTestCase {
 	private ByteArrayOutputStream out = null;
 	private WriterImpl w = null;
 
+	@Override
 	@Before
 	public void setUp() {
 		out = new ByteArrayOutputStream();
 		w = new WriterImpl(out);
 	}
 
+	@Test
+	public void testWriteNull() throws IOException {
+		w.writeNull();
+		checkContents("00");
+	}
+
 	@Test
 	public void testWriteBoolean() throws IOException {
 		w.writeBoolean(true);
 		w.writeBoolean(false);
-		// TRUE tag, FALSE tag
-		checkContents("01" + "00");
+		// BOOLEAN tag, 1, BOOLEAN tag, 0
+		checkContents("11" + "01" + "11" + "00");
 	}
 
 	@Test
@@ -46,11 +53,11 @@ public class WriterImplTest extends BriarTestCase {
 		w.writeInteger(Long.MAX_VALUE);
 		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");
+		checkContents("21" + "00" + "21" + "FF" +
+				"21" + "7F" + "21" + "80" +
+				"22" + "7FFF" + "22" + "8000" +
+				"24" + "7FFFFFFF" + "24" + "80000000" +
+				"28" + "7FFFFFFFFFFFFFFF" + "28" + "8000000000000000");
 	}
 
 	@Test
@@ -65,10 +72,10 @@ 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("06" + "0000000000000000" + "06" + "3FF0000000000000"
-				+ "06" + "4000000000000000" + "06" + "BFF0000000000000"
-				+ "06" + "8000000000000000" + "06" + "FFF0000000000000"
-				+ "06" + "7FF0000000000000" + "06" + "7FF8000000000000");
+		checkContents("38" + "0000000000000000" + "38" + "3FF0000000000000"
+				+ "38" + "4000000000000000" + "38" + "BFF0000000000000"
+				+ "38" + "8000000000000000" + "38" + "FFF0000000000000"
+				+ "38" + "7FF0000000000000" + "38" + "7FF8000000000000");
 	}
 
 	@Test
@@ -79,8 +86,8 @@ public class WriterImplTest extends BriarTestCase {
 		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);
+		checkContents("41" + "10" + "666F6F206261722062617A2062616D20" +
+				"41" + "7F" + longHex);
 	}
 
 	@Test
@@ -93,7 +100,7 @@ public class WriterImplTest extends BriarTestCase {
 		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);
+		checkContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
 	}
 
 	@Test
@@ -102,7 +109,7 @@ public class WriterImplTest extends BriarTestCase {
 		String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
 		w.writeString(shortest);
 		// STRING_32 tag, length 2^15, UTF-8 bytes
-		checkContents("09" + "00008000" + shortHex);
+		checkContents("44" + "00008000" + shortHex);
 	}
 
 	@Test
@@ -111,8 +118,8 @@ public class WriterImplTest extends BriarTestCase {
 		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);
+		// RAW_8 tag, length 3, bytes, RAW_8 tag, length 127, bytes
+		checkContents("51" + "03" + "010203" + "51" + "7F" + longHex);
 	}
 
 	@Test
@@ -123,8 +130,8 @@ public class WriterImplTest extends BriarTestCase {
 		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);
+		// RAW_16 tag, length 128, bytes, RAW_16 tag, length 2^15 - 1, bytes
+		checkContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
 	}
 
 	@Test
@@ -132,8 +139,8 @@ public class WriterImplTest extends BriarTestCase {
 		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);
+		// RAW_32 tag, length 2^15, bytes
+		checkContents("54" + "00008000" + shortHex);
 	}
 
 	@Test
@@ -142,7 +149,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("0D" + "02" + "00" + "02" + "01" + "02" + "02" + "10");
+		checkContents("60" + "21" + "00" + "21" + "01" + "21" + "02" + "80");
 	}
 
 	@Test
@@ -153,20 +160,20 @@ public class WriterImplTest extends BriarTestCase {
 		l.add(2);
 		w.writeList(l);
 		// LIST tag, 1 as integer, NULL tag, 2 as integer, END tag
-		checkContents("0D" + "02" + "01" + "11" + "02" + "02" + "10");
+		checkContents("60" + "21" + "01" + "00" + "21" + "02" + "80");
 	}
 
 	@Test
 	public void testWriteMap() throws IOException {
 		// Use LinkedHashMap to get predictable iteration order
-		Map<Object, Object> m = new LinkedHashMap<Object, Object>();
-		for(int i = 0; i < 4; i++) m.put(i, i + 1);
+		Map<String, Object> m = new LinkedHashMap<String, Object>();
+		for(int i = 0; i < 4; i++) m.put(String.valueOf(i), i);
 		w.writeMap(m);
-		// MAP tag, entries as integers, END tag
-		checkContents("0E" + "02" + "00" + "02" + "01" +
-				"02" + "01" + "02" + "02" +
-				"02" + "02" + "02" + "03" +
-				"02" + "03" + "02" + "04" + "10");
+		// MAP tag, keys as strings and values as integers, END tag
+		checkContents("70" + "41" + "01" + "30" + "21" + "00" +
+				"41" + "01" + "31" + "21" + "01" +
+				"41" + "01" + "32" + "21" + "02" +
+				"41" + "01" + "33" + "21" + "03" + "80");
 	}
 
 	@Test
@@ -177,9 +184,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("0D" + "02" + "01" +
-				"07" + "03" + "666F6F" +
-				"03" + "0080" + "10");
+		checkContents("60" + "21" + "01" +
+				"41" + "03" + "666F6F" +
+				"22" + "0080" + "80");
 	}
 
 	@Test
@@ -187,42 +194,30 @@ public class WriterImplTest extends BriarTestCase {
 		w.writeMapStart();
 		w.writeString("foo");
 		w.writeInteger(123);
-		w.writeBytes(new byte[0]);
+		w.writeString("bar");
 		w.writeNull();
 		w.writeMapEnd();
-		// MAP tag, "foo" as string, 123 as integer, {} as bytes, NULL tag,
-		// END tag
-		checkContents("0E" + "07" + "03" + "666F6F" +
-				"02" + "7B" + "0A" + "00" + "11" + "10");
+		// MAP tag, "foo" as string, 123 as integer, "bar" as string,
+		// NULL tag, END tag
+		checkContents("70" + "41" + "03" + "666F6F" +
+				"21" + "7B" + "41" + "03" + "626172" + "00" + "80");
 	}
 
 	@Test
 	public void testWriteNestedMapsAndLists() throws IOException {
-		Map<Object, Object> m = new LinkedHashMap<Object, Object>();
-		m.put("foo", 123);
-		List<Object> l = new ArrayList<Object>();
-		l.add((byte) 1);
-		Map<Object, Object> m1 = new LinkedHashMap<Object, Object>();
-		m1.put(m, l);
-		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("0E" + "0E" + "07" + "03" + "666F6F" +
-				"02" + "7B" + "10" + "0D" + "02" + "01" + "10" + "10");
-	}
-
-	@Test
-	public void testWriteStruct() throws IOException {
-		w.writeStructStart(123);
-		w.writeStructEnd();
-		// STRUCT tag, 123 as struct ID, END tag
-		checkContents("0F" + "7B" + "10");
-	}
-
-	@Test
-	public void testWriteNull() throws IOException {
-		w.writeNull();
-		checkContents("11");
+		Map<String, Object> inner = new LinkedHashMap<String, Object>();
+		inner.put("bar", new byte[0]);
+		List<Object> list = new ArrayList<Object>();
+		list.add(1);
+		list.add(inner);
+		Map<String, Object> outer = new LinkedHashMap<String, Object>();
+		outer.put("foo", list);
+		w.writeMap(outer);
+		// MAP tag, "foo" as string, LIST tag, 1 as integer, MAP tag,
+		// "bar" as string, {} as raw, END tag, END tag, END tag
+		checkContents("70" + "41" + "03" + "666F6F" + "60" +
+				"21" + "01" + "70" + "41" + "03" + "626172" + "51" + "00" +
+				"80" + "80" + "80");
 	}
 
 	private void checkContents(String hex) throws IOException {
-- 
GitLab