From 2e472c1d161c2aed116d08af788fea0bfd437a55 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Tue, 19 Nov 2013 21:28:53 +0000
Subject: [PATCH] Added the ability to skip serialised objects.

---
 .../src/net/sf/briar/api/serial/Reader.java   |  32 +-
 .../net/sf/briar/invitation/Connector.java    |  15 +-
 .../sf/briar/messaging/PacketReaderImpl.java  |  64 ++--
 .../src/net/sf/briar/serial/ReaderImpl.java   | 279 ++++++++++--------
 .../net/sf/briar/serial/ReaderImplTest.java   | 268 ++++++++---------
 5 files changed, 352 insertions(+), 306 deletions(-)

diff --git a/briar-api/src/net/sf/briar/api/serial/Reader.java b/briar-api/src/net/sf/briar/api/serial/Reader.java
index c81826f15a..fa55469fb1 100644
--- a/briar-api/src/net/sf/briar/api/serial/Reader.java
+++ b/briar-api/src/net/sf/briar/api/serial/Reader.java
@@ -1,67 +1,79 @@
 package net.sf.briar.api.serial;
 
 import java.io.IOException;
-import java.util.List;
-import java.util.Map;
 
 public interface Reader {
 
 	boolean eof() throws IOException;
 	void close() throws IOException;
 
-	void setMaxStringLength(int length);
-	void resetMaxStringLength();
-
-	void setMaxBytesLength(int length);
-	void resetMaxBytesLength();
-
 	void addConsumer(Consumer c);
 	void removeConsumer(Consumer c);
 
 	boolean hasBoolean() throws IOException;
 	boolean readBoolean() throws IOException;
+	void skipBoolean() throws IOException;
 
 	boolean hasUint7() throws IOException;
 	byte readUint7() throws IOException;
+	void skipUint7() throws IOException;
+
 	boolean hasInt8() throws IOException;
 	byte readInt8() throws IOException;
+	void skipInt8() throws IOException;
+
 	boolean hasInt16() throws IOException;
 	short readInt16() throws IOException;
+	void skipInt16() throws IOException;
+
 	boolean hasInt32() throws IOException;
 	int readInt32() throws IOException;
+	void skipInt32() throws IOException;
+
 	boolean hasInt64() throws IOException;
 	long readInt64() throws IOException;
+	void skipInt64() throws IOException;
+
 	boolean hasIntAny() throws IOException;
 	long readIntAny() throws IOException;
+	void skipIntAny() throws IOException;
 
 	boolean hasFloat32() throws IOException;
 	float readFloat32() throws IOException;
+	void skipFloat32() throws IOException;
+
 	boolean hasFloat64() throws IOException;
 	double readFloat64() throws IOException;
+	void skipFloat64() throws IOException;
 
 	boolean hasString() throws IOException;
 	String readString(int maxLength) throws IOException;
+	void skipString(int maxLength) throws IOException;
 
 	boolean hasBytes() throws IOException;
 	byte[] readBytes(int maxLength) throws IOException;
+	void skipBytes(int maxLength) throws IOException;
 
 	boolean hasList() throws IOException;
-	<E> List<E> readList(Class<E> e) throws IOException;
 	void readListStart() throws IOException;
 	boolean hasListEnd() throws IOException;
 	void readListEnd() throws IOException;
+	void skipList() throws IOException;
 
 	boolean hasMap() throws IOException;
-	<K, V> Map<K, V> readMap(Class<K> k, Class<V> v) throws IOException;
 	void readMapStart() throws IOException;
 	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-core/src/net/sf/briar/invitation/Connector.java b/briar-core/src/net/sf/briar/invitation/Connector.java
index 7ccd4952f7..5cceedb6cb 100644
--- a/briar-core/src/net/sf/briar/invitation/Connector.java
+++ b/briar-core/src/net/sf/briar/invitation/Connector.java
@@ -244,11 +244,16 @@ abstract class Connector extends Thread {
 			byte[] b = r.readBytes(UniqueId.LENGTH);
 			if(b.length != UniqueId.LENGTH) throw new FormatException();
 			TransportId id = new TransportId(b);
-			r.setMaxStringLength(MAX_PROPERTY_LENGTH);
-			Map<String, String> p = r.readMap(String.class, String.class);
-			r.resetMaxStringLength();
-			if(p.size() > MAX_PROPERTIES_PER_TRANSPORT)
-				throw new FormatException();
+			Map<String, String> p = new HashMap<String, String>();
+			r.readMapStart();
+			for(int i = 0; !r.hasMapEnd(); i++) {
+				if(i == MAX_PROPERTIES_PER_TRANSPORT)
+					throw new FormatException();
+				String key = r.readString(MAX_PROPERTY_LENGTH);
+				String value = r.readString(MAX_PROPERTY_LENGTH);
+				p.put(key, value);
+			}
+			r.readMapEnd();
 			remoteProps.put(id, new TransportProperties(p));
 		}
 		r.readListEnd();
diff --git a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java
index 07b4bd8688..ff30595e40 100644
--- a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java
@@ -19,10 +19,10 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import net.sf.briar.api.Bytes;
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.TransportId;
 import net.sf.briar.api.TransportProperties;
@@ -75,22 +75,21 @@ class PacketReaderImpl implements PacketReader {
 		r.addConsumer(counting);
 		// Read the start of the struct
 		r.readStructStart(ACK);
-		// Read the message IDs as byte arrays
-		r.setMaxBytesLength(UniqueId.LENGTH);
-		List<Bytes> raw = r.readList(Bytes.class);
+		// Read the message IDs
+		List<MessageId> acked = new ArrayList<MessageId>();
+		r.readListStart();
+		while(!r.hasListEnd()) {
+			byte[] b = r.readBytes(UniqueId.LENGTH);
+			if(b.length != UniqueId.LENGTH)
+				throw new FormatException();
+			acked.add(new MessageId(b));
+		}
+		if(acked.isEmpty()) throw new FormatException();
+		r.readListEnd();
 		// Read the end of the struct
 		r.readStructEnd();
 		// Reset the reader
-		r.resetMaxBytesLength();
 		r.removeConsumer(counting);
-		if(raw.isEmpty()) throw new FormatException();
-		// Convert the byte arrays to message IDs
-		List<MessageId> acked = new ArrayList<MessageId>();
-		for(Bytes b : raw) {
-			if(b.getBytes().length != UniqueId.LENGTH)
-				throw new FormatException();
-			acked.add(new MessageId(b.getBytes()));
-		}
 		// Build and return the ack
 		return new Ack(Collections.unmodifiableList(acked));
 	}
@@ -113,22 +112,21 @@ class PacketReaderImpl implements PacketReader {
 		r.addConsumer(counting);
 		// Read the start of the struct
 		r.readStructStart(OFFER);
-		// Read the message IDs as byte arrays
-		r.setMaxBytesLength(UniqueId.LENGTH);
-		List<Bytes> raw = r.readList(Bytes.class);
+		// Read the message IDs
+		List<MessageId> messages = new ArrayList<MessageId>();
+		r.readListStart();
+		while(!r.hasListEnd()) {
+			byte[] b = r.readBytes(UniqueId.LENGTH);
+			if(b.length != UniqueId.LENGTH)
+				throw new FormatException();
+			messages.add(new MessageId(b));
+		}
+		if(messages.isEmpty()) throw new FormatException();
+		r.readListEnd();
 		// Read the end of the struct
 		r.readStructEnd();
 		// Reset the reader
-		r.resetMaxBytesLength();
 		r.removeConsumer(counting);
-		if(raw.isEmpty()) throw new FormatException();
-		// Convert the byte arrays to message IDs
-		List<MessageId> messages = new ArrayList<MessageId>();
-		for(Bytes b : raw) {
-			if(b.getBytes().length != UniqueId.LENGTH)
-				throw new FormatException();
-			messages.add(new MessageId(b.getBytes()));
-		}
 		// Build and return the offer
 		return new Offer(Collections.unmodifiableList(messages));
 	}
@@ -239,10 +237,16 @@ class PacketReaderImpl implements PacketReader {
 		if(b.length < UniqueId.LENGTH) throw new FormatException();
 		TransportId id = new TransportId(b);
 		// Read the transport properties
-		r.setMaxStringLength(MAX_PROPERTY_LENGTH);
-		Map<String, String> m = r.readMap(String.class, String.class);
-		r.resetMaxStringLength();
-		if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) throw new FormatException();
+		Map<String, String> p = new HashMap<String, String>();
+		r.readMapStart();
+		for(int i = 0; !r.hasMapEnd(); i++) {
+			if(i == MAX_PROPERTIES_PER_TRANSPORT)
+				throw new FormatException();
+			String key = r.readString(MAX_PROPERTY_LENGTH);
+			String value = r.readString(MAX_PROPERTY_LENGTH);
+			p.put(key, value);
+		}
+		r.readMapEnd();
 		// Read the version number
 		long version = r.readIntAny();
 		if(version < 0) throw new FormatException();
@@ -251,6 +255,6 @@ class PacketReaderImpl implements PacketReader {
 		// Reset the reader
 		r.removeConsumer(counting);
 		// Build and return the transport update
-		return new TransportUpdate(id, new TransportProperties(m), version);
+		return new TransportUpdate(id, new TransportProperties(p), version);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/serial/ReaderImpl.java b/briar-core/src/net/sf/briar/serial/ReaderImpl.java
index 6ab7a5b1a8..3206bbfc48 100644
--- a/briar-core/src/net/sf/briar/serial/ReaderImpl.java
+++ b/briar-core/src/net/sf/briar/serial/ReaderImpl.java
@@ -4,12 +4,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
-import net.sf.briar.api.Bytes;
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.Reader;
@@ -25,8 +20,6 @@ class ReaderImpl implements Reader {
 	private boolean hasLookahead = false, eof = false;
 	private byte next, nextStructId;
 	private byte[] buf = new byte[8];
-	private int maxStringLength = Integer.MAX_VALUE;
-	private int maxBytesLength = Integer.MAX_VALUE;
 
 	ReaderImpl(InputStream in) {
 		this.in = in;
@@ -70,22 +63,6 @@ class ReaderImpl implements Reader {
 		in.close();
 	}
 
-	public void setMaxStringLength(int length) {
-		maxStringLength = length;
-	}
-
-	public void resetMaxStringLength() {
-		maxStringLength = Integer.MAX_VALUE;
-	}
-
-	public void setMaxBytesLength(int length) {
-		maxBytesLength = length;
-	}
-
-	public void resetMaxBytesLength() {
-		maxBytesLength = Integer.MAX_VALUE;
-	}
-
 	public void addConsumer(Consumer c) {
 		consumers.add(c);
 	}
@@ -106,6 +83,11 @@ class ReaderImpl implements Reader {
 		return next == Tag.TRUE;
 	}
 
+	public void skipBoolean() throws IOException {
+		if(!hasBoolean()) throw new FormatException();
+		hasLookahead = false;
+	}
+
 	public boolean hasUint7() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -118,6 +100,11 @@ class ReaderImpl implements Reader {
 		return next;
 	}
 
+	public void skipUint7() throws IOException {
+		if(!hasUint7()) throw new FormatException();
+		hasLookahead = false;
+	}
+
 	public boolean hasInt8() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -132,7 +119,19 @@ class ReaderImpl implements Reader {
 			eof = true;
 			throw new FormatException();
 		}
-		return (byte) i;
+		byte b = (byte) i;
+		// Feed the hungry mouths
+		for(Consumer c : consumers) c.write(b);
+		return b;
+	}
+
+	public void skipInt8() throws IOException {
+		if(!hasInt8()) throw new FormatException();
+		if(in.read() == -1) {
+			eof = true;
+			throw new FormatException();
+		}
+		hasLookahead = false;
 	}
 
 	public boolean hasInt16() throws IOException {
@@ -148,24 +147,6 @@ class ReaderImpl implements Reader {
 		return (short) (((buf[0] & 0xFF) << 8) | (buf[1] & 0xFF));
 	}
 
-	public boolean hasInt32() throws IOException {
-		if(!hasLookahead) readLookahead();
-		if(eof) return false;
-		return next == Tag.INT32;
-	}
-
-	public int readInt32() throws IOException {
-		if(!hasInt32()) throw new FormatException();
-		consumeLookahead();
-		return readInt32Bits();
-	}
-
-	private int readInt32Bits() throws IOException {
-		readIntoBuffer(4);
-		return ((buf[0] & 0xFF) << 24) | ((buf[1] & 0xFF) << 16) |
-				((buf[2] & 0xFF) << 8) | (buf[3] & 0xFF);
-	}
-
 	private void readIntoBuffer(int length) throws IOException {
 		if(buf.length < length) buf = new byte[length];
 		readIntoBuffer(buf, length);
@@ -186,6 +167,47 @@ class ReaderImpl implements Reader {
 		for(Consumer c : consumers) c.write(b, 0, length);
 	}
 
+	public void skipInt16() throws IOException {
+		if(!hasInt16()) throw new FormatException();
+		hasLookahead = false;
+		skip(2);
+	}
+
+	private void skip(int length) throws IOException {
+		while(length > 0) {
+			int read = in.read(buf, 0, Math.min(length, buf.length));
+			if(read == -1) {
+				eof = true;
+				throw new FormatException();
+			}
+			length -= read;
+		}
+	}
+
+	public boolean hasInt32() throws IOException {
+		if(!hasLookahead) readLookahead();
+		if(eof) return false;
+		return next == Tag.INT32;
+	}
+
+	public int readInt32() throws IOException {
+		if(!hasInt32()) throw new FormatException();
+		consumeLookahead();
+		return readInt32Bits();
+	}
+
+	private int readInt32Bits() throws IOException {
+		readIntoBuffer(4);
+		return ((buf[0] & 0xFF) << 24) | ((buf[1] & 0xFF) << 16) |
+				((buf[2] & 0xFF) << 8) | (buf[3] & 0xFF);
+	}
+
+	public void skipInt32() throws IOException {
+		if(!hasInt32()) throw new FormatException();
+		hasLookahead = false;
+		skip(4);
+	}
+
 	public boolean hasInt64() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -206,6 +228,12 @@ class ReaderImpl implements Reader {
 				((buf[6] & 0xFFL) << 8) | (buf[7] & 0xFFL);
 	}
 
+	public void skipInt64() throws IOException {
+		if(!hasInt64()) throw new FormatException();
+		hasLookahead = false;
+		skip(8);
+	}
+
 	public boolean hasIntAny() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -223,6 +251,16 @@ class ReaderImpl implements Reader {
 		throw new IllegalStateException();
 	}
 
+	public void skipIntAny() throws IOException {
+		if(!hasIntAny()) throw new FormatException();
+		if(next >= 0) skipUint7();
+		else if(next == Tag.INT8) skipInt8();
+		else if(next == Tag.INT16) skipInt16();
+		else if(next == Tag.INT32) skipInt32();
+		else if(next == Tag.INT64) skipInt64();
+		else throw new IllegalStateException();
+	}
+
 	public boolean hasFloat32() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -235,6 +273,12 @@ class ReaderImpl implements Reader {
 		return Float.intBitsToFloat(readInt32Bits());
 	}
 
+	public void skipFloat32() throws IOException {
+		if(!hasFloat32()) throw new FormatException();
+		hasLookahead = false;
+		skip(4);
+	}
+
 	public boolean hasFloat64() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -247,6 +291,12 @@ class ReaderImpl implements Reader {
 		return Double.longBitsToDouble(readInt64Bits());
 	}
 
+	public void skipFloat64() throws IOException {
+		if(!hasFloat64()) throw new FormatException();
+		hasLookahead = false;
+		skip(8);
+	}
+
 	public boolean hasString() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -282,6 +332,15 @@ class ReaderImpl implements Reader {
 				|| next == Tag.INT32;
 	}
 
+	public void skipString(int maxLength) throws IOException {
+		if(!hasString()) throw new FormatException();
+		hasLookahead = false;
+		int length = readLength();
+		if(length > maxLength) throw new FormatException();
+		hasLookahead = false;
+		skip(length);
+	}
+
 	public boolean hasBytes() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
@@ -299,19 +358,28 @@ class ReaderImpl implements Reader {
 		return b;
 	}
 
+	public void skipBytes(int maxLength) throws IOException {
+		if(!hasBytes()) throw new FormatException();
+		hasLookahead = false;
+		int length = readLength();
+		if(length > maxLength) throw new FormatException();
+		hasLookahead = false;
+		skip(length);
+	}
+
 	public boolean hasList() throws IOException {
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
 		return next == Tag.LIST;
 	}
 
-	public <E> List<E> readList(Class<E> e) throws IOException {
+	public void readListStart() throws IOException {
 		if(!hasList()) throw new FormatException();
 		consumeLookahead();
-		List<E> list = new ArrayList<E>();
-		while(!hasEnd()) list.add(readObject(e));
-		readEnd();
-		return Collections.unmodifiableList(list);
+	}
+
+	public boolean hasListEnd() throws IOException {
+		return hasEnd();
 	}
 
 	private boolean hasEnd() throws IOException {
@@ -320,69 +388,34 @@ class ReaderImpl implements Reader {
 		return next == Tag.END;
 	}
 
+	public void readListEnd() throws IOException {
+		readEnd();
+	}
+
 	private void readEnd() throws IOException {
 		if(!hasEnd()) throw new FormatException();
 		consumeLookahead();
 	}
 
-	private Object readObject() throws IOException {
-		if(hasBoolean()) return Boolean.valueOf(readBoolean());
-		if(hasUint7()) return Byte.valueOf(readUint7());
-		if(hasInt8()) return Byte.valueOf(readInt8());
-		if(hasInt16()) return Short.valueOf(readInt16());
-		if(hasInt32()) return Integer.valueOf(readInt32());
-		if(hasInt64()) return Long.valueOf(readInt64());
-		if(hasFloat32()) return Float.valueOf(readFloat32());
-		if(hasFloat64()) return Double.valueOf(readFloat64());
-		if(hasString()) return readString(maxStringLength);
-		if(hasBytes()) return new Bytes(readBytes(maxBytesLength));
-		if(hasList()) return readList(Object.class);
-		if(hasMap()) return readMap(Object.class, Object.class);
-		if(hasNull()) {
-			readNull();
-			return null;
-		}
-		throw new FormatException();
-	}
-
-	private <T> T readObject(Class<T> t) throws IOException {
-		try {
-			Object o = readObject();
-			// If this is a small integer type and we're expecting a larger
-			// integer type, promote before casting
-			if(o instanceof Byte) {
-				if(Short.class.isAssignableFrom(t))
-					return t.cast(Short.valueOf((Byte) o));
-				if(Integer.class.isAssignableFrom(t))
-					return t.cast(Integer.valueOf((Byte) o));
-				if(Long.class.isAssignableFrom(t))
-					return t.cast(Long.valueOf((Byte) o));
-			} else if(o instanceof Short) {
-				if(Integer.class.isAssignableFrom(t))
-					return t.cast(Integer.valueOf((Short) o));
-				if(Long.class.isAssignableFrom(t))
-					return t.cast(Long.valueOf((Short) o));
-			} else if(o instanceof Integer) {
-				if(Long.class.isAssignableFrom(t))
-					return t.cast(Long.valueOf((Integer) o));
-			}
-			return t.cast(o);
-		} catch(ClassCastException e) {
-			throw new FormatException();
-		}
-	}
-
-	public void readListStart() throws IOException {
+	public void skipList() throws IOException {
 		if(!hasList()) throw new FormatException();
-		consumeLookahead();
-	}
-
-	public boolean hasListEnd() throws IOException {
-		return hasEnd();
+		hasLookahead = false;
+		while(!hasListEnd()) skipObject();
+		hasLookahead = false;
 	}
 
-	public void readListEnd() throws IOException {
-		readEnd();
+	private void skipObject() throws IOException {
+		if(hasBoolean()) skipBoolean();
+		else if(hasIntAny()) skipIntAny();
+		else if(hasFloat32()) skipFloat32();
+		else if(hasFloat64()) skipFloat64();
+		else if(hasString()) skipString(Integer.MAX_VALUE);
+		else if(hasBytes()) skipBytes(Integer.MAX_VALUE);
+		else if(hasList()) skipList();
+		else if(hasMap()) skipMap();
+		else if(hasStruct()) skipStruct();
+		else if(hasNull()) skipNull();
+		else throw new FormatException();
 	}
 
 	public boolean hasMap() throws IOException {
@@ -391,18 +424,6 @@ class ReaderImpl implements Reader {
 		return next == Tag.MAP;
 	}
 
-	public <K, V> Map<K, V> readMap(Class<K> k, Class<V> v)	throws IOException {
-		if(!hasMap()) throw new FormatException();
-		consumeLookahead();
-		Map<K, V> m = new HashMap<K, V>();
-		while(!hasEnd()) {
-			if(m.put(readObject(k), readObject(v)) != null)
-				throw new FormatException(); // Duplicate key
-		}
-		readEnd();
-		return Collections.unmodifiableMap(m);
-	}
-
 	public void readMapStart() throws IOException {
 		if(!hasMap()) throw new FormatException();
 		consumeLookahead();
@@ -416,11 +437,27 @@ class ReaderImpl implements Reader {
 		readEnd();
 	}
 
+	public void skipMap() throws IOException {
+		if(!hasMap()) throw new FormatException();
+		hasLookahead = false;
+		while(!hasMapEnd()) {
+			skipObject();
+			skipObject();
+		}
+		hasLookahead = false;
+	}
+
+	public boolean hasStruct() throws IOException {
+		if(!hasLookahead) readLookahead();
+		if(eof) return false;
+		return next == Tag.STRUCT;
+	}
+
 	public boolean hasStruct(int id) throws IOException {
 		if(id < 0 || id > 255) throw new IllegalArgumentException();
 		if(!hasLookahead) readLookahead();
 		if(eof) return false;
-		return (nextStructId & 0xFF) == id;
+		return next == Tag.STRUCT && (nextStructId & 0xFF) == id;
 	}
 
 	public void readStructStart(int id) throws IOException {
@@ -436,6 +473,13 @@ class ReaderImpl implements Reader {
 		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;
@@ -446,4 +490,9 @@ class ReaderImpl implements Reader {
 		if(!hasNull()) throw new FormatException();
 		consumeLookahead();
 	}
+
+	public void skipNull() throws IOException {
+		if(!hasNull()) throw new FormatException();
+		hasLookahead = false;
+	}
 }
diff --git a/briar-tests/src/net/sf/briar/serial/ReaderImplTest.java b/briar-tests/src/net/sf/briar/serial/ReaderImplTest.java
index d2ef31cddb..d0100ad75f 100644
--- a/briar-tests/src/net/sf/briar/serial/ReaderImplTest.java
+++ b/briar-tests/src/net/sf/briar/serial/ReaderImplTest.java
@@ -3,13 +3,8 @@ package net.sf.briar.serial;
 import static org.junit.Assert.assertArrayEquals;
 
 import java.io.ByteArrayInputStream;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
 
 import net.sf.briar.BriarTestCase;
-import net.sf.briar.api.Bytes;
 import net.sf.briar.api.FormatException;
 import net.sf.briar.util.StringUtils;
 
@@ -22,12 +17,20 @@ public class ReaderImplTest extends BriarTestCase {
 
 	@Test
 	public void testReadBoolean() throws Exception {
-		setContents("FFFE");
+		setContents("FF" + "FE");
 		assertFalse(r.readBoolean());
 		assertTrue(r.readBoolean());
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipBoolean() throws Exception {
+		setContents("FF" + "FE");
+		r.skipBoolean();
+		r.skipBoolean();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadInt8() throws Exception {
 		setContents("FD00" + "FDFF" + "FD7F" + "FD80");
@@ -38,6 +41,13 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipInt8() throws Exception {
+		setContents("FD00");
+		r.skipInt8();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadInt16() throws Exception {
 		setContents("FC0000" + "FCFFFF" + "FC7FFF" + "FC8000");
@@ -48,6 +58,13 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipInt16() throws Exception {
+		setContents("FC0000");
+		r.skipInt16();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadInt32() throws Exception {
 		setContents("FB00000000" + "FBFFFFFFFF" + "FB7FFFFFFF" + "FB80000000");
@@ -58,6 +75,13 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipInt32() throws Exception {
+		setContents("FB00000000");
+		r.skipInt32();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadInt64() throws Exception {
 		setContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF"
@@ -69,6 +93,13 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipInt64() throws Exception {
+		setContents("FA0000000000000000");
+		r.skipInt64();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadIntAny() throws Exception {
 		setContents("00" + "7F" + "FD80" + "FDFF" + "FC0080" + "FC7FFF"
@@ -85,6 +116,18 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipIntAny() throws Exception {
+		setContents("00" + "FD00" + "FC0000" + "FB00000000"
+				+ "FA0000000000000000");
+		r.skipIntAny();
+		r.skipIntAny();
+		r.skipIntAny();
+		r.skipIntAny();
+		r.skipIntAny();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadFloat32() throws Exception {
 		// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
@@ -102,6 +145,13 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipFloat32() throws Exception {
+		setContents("F900000000");
+		r.skipFloat32();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadFloat64() throws Exception {
 		setContents("F80000000000000000" + "F83FF0000000000000"
@@ -119,11 +169,18 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testSkipFloat64() throws Exception {
+		setContents("F80000000000000000");
+		r.skipFloat64();
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadString() throws Exception {
 		setContents("F703666F6F" + "F700");
-		assertEquals("foo", r.readString(1000));
-		assertEquals("", r.readString(1000));
+		assertEquals("foo", r.readString(Integer.MAX_VALUE));
+		assertEquals("", r.readString(Integer.MAX_VALUE));
 		assertTrue(r.eof());
 	}
 
@@ -138,104 +195,62 @@ public class ReaderImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testReadBytes() throws Exception {
-		setContents("F603010203" + "F600");
-		assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(1000));
-		assertArrayEquals(new byte[] {}, r.readBytes(1000));
+	public void testSkipString() throws Exception {
+		setContents("F703666F6F" + "F700");
+		r.skipString(Integer.MAX_VALUE);
+		r.skipString(Integer.MAX_VALUE);
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testReadBytesMaxLength() throws Exception {
-		setContents("F603010203" + "F603010203");
-		assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3));
+	public void testSkipStringMaxLength() throws Exception {
+		setContents("F703666F6F" + "F703666F6F");
+		r.skipString(3);
 		try {
-			r.readBytes(2);
+			r.skipString(2);
 			fail();
 		} catch(FormatException expected) {}
 	}
 
 	@Test
-	public void testReadList() throws Exception {
-		setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
-		List<Object> l = r.readList(Object.class);
-		assertNotNull(l);
-		assertEquals(3, l.size());
-		assertEquals((byte) 1, l.get(0));
-		assertEquals("foo", l.get(1));
-		assertEquals((short) 128, l.get(2));
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testReadListTypeSafe() throws Exception {
-		setContents("F5" + "01" + "02" + "03" + "F2");
-		List<Byte> l = r.readList(Byte.class);
-		assertNotNull(l);
-		assertEquals(3, l.size());
-		assertEquals(Byte.valueOf((byte) 1), l.get(0));
-		assertEquals(Byte.valueOf((byte) 2), l.get(1));
-		assertEquals(Byte.valueOf((byte) 3), l.get(2));
+	public void testReadBytes() throws Exception {
+		setContents("F603010203" + "F600");
+		assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(Integer.MAX_VALUE));
+		assertArrayEquals(new byte[] {}, r.readBytes(Integer.MAX_VALUE));
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testReadListTypeSafeThrowsFormatException() throws Exception {
-		setContents("F5" + "01" + "F703666F6F" + "03" + "F2");
-		// Trying to read a mixed list as a list of bytes should throw a
-		// FormatException
+	public void testReadBytesMaxLength() throws Exception {
+		setContents("F603010203" + "F603010203");
+		assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3));
 		try {
-			r.readList(Byte.class);
+			r.readBytes(2);
 			fail();
 		} catch(FormatException expected) {}
 	}
 
 	@Test
-	public void testReadMap() throws Exception {
-		setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
-		Map<Object, Object> m = r.readMap(Object.class, Object.class);
-		assertNotNull(m);
-		assertEquals(2, m.size());
-		assertEquals((byte) 123, m.get("foo"));
-		Bytes b = new Bytes(new byte[] {});
-		assertTrue(m.containsKey(b));
-		assertNull(m.get(b));
-		assertTrue(r.eof());
-	}
-
-	@Test
-	public void testReadMapTypeSafe() throws Exception {
-		setContents("F4" + "F703666F6F" + "7B" + "F700" + "F1" + "F2");
-		Map<String, Byte> m = r.readMap(String.class, Byte.class);
-		assertNotNull(m);
-		assertEquals(2, m.size());
-		assertEquals(Byte.valueOf((byte) 123), m.get("foo"));
-		assertTrue(m.containsKey(""));
-		assertNull(m.get(""));
+	public void testSkipBytes() throws Exception {
+		setContents("F603010203" + "F600");
+		r.skipBytes(Integer.MAX_VALUE);
+		r.skipBytes(Integer.MAX_VALUE);
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testMapKeysMustBeUnique() throws Exception {
-		setContents("F4" + "F703666F6F" + "01" + "F703626172" + "02" + "F2"
-				+ "F4" + "F703666F6F" + "01" + "F703666F6F" + "02" + "F2");
-		// The first map has unique keys
-		Map<String, Byte> m = r.readMap(String.class, Byte.class);
-		assertNotNull(m);
-		assertEquals(2, m.size());
-		assertEquals(Byte.valueOf((byte) 1), m.get("foo"));
-		assertEquals(Byte.valueOf((byte) 2), m.get("bar"));
-		// The second map has a duplicate key
+	public void testSkipBytesMaxLength() throws Exception {
+		setContents("F603010203" + "F603010203");
+		r.skipBytes(3);
 		try {
-			r.readMap(String.class, Byte.class);
+			r.skipBytes(2);
 			fail();
 		} catch(FormatException expected) {}
 	}
 
 	@Test
-	public void testReadDelimitedListElements() throws Exception {
+	public void testReadList() throws Exception {
 		setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
-		assertTrue(r.hasList());
 		r.readListStart();
 		assertFalse(r.hasListEnd());
 		assertEquals((byte) 1, r.readIntAny());
@@ -249,21 +264,15 @@ public class ReaderImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testReadDelimitedListTypeSafe() throws Exception {
-		setContents("F5" + "01" + "02" + "03" + "F2");
-		List<Byte> l = r.readList(Byte.class);
-		assertNotNull(l);
-		assertEquals(3, l.size());
-		assertEquals(Byte.valueOf((byte) 1), l.get(0));
-		assertEquals(Byte.valueOf((byte) 2), l.get(1));
-		assertEquals(Byte.valueOf((byte) 3), l.get(2));
+	public void testSkipList() throws Exception {
+		setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
+		r.skipList();
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testReadDelimitedMapEntries() throws Exception {
+	public void testReadMap() throws Exception {
 		setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
-		assertTrue(r.hasMap());
 		r.readMapStart();
 		assertFalse(r.hasMapEnd());
 		assertEquals("foo", r.readString(1000));
@@ -280,84 +289,51 @@ public class ReaderImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testReadDelimitedMapTypeSafe() throws Exception {
-		setContents("F4" + "F703666F6F" + "7B" + "F700" + "F1" + "F2");
-		Map<String, Byte> m = r.readMap(String.class, Byte.class);
-		assertNotNull(m);
-		assertEquals(2, m.size());
-		assertEquals(Byte.valueOf((byte) 123), m.get("foo"));
-		assertTrue(m.containsKey(""));
-		assertNull(m.get(""));
+	public void testSkipMap() throws Exception {
+		setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
+		r.skipMap();
 		assertTrue(r.eof());
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
-	public void testReadNestedMapsAndLists() throws Exception {
-		setContents("F4" + "F4" + "F703666F6F" + "7B" + "F2"
-				+ "F5" + "01" + "F2" + "F2");
-		Map<Object, Object> m = r.readMap(Object.class, Object.class);
-		assertNotNull(m);
-		assertEquals(1, m.size());
-		Entry<Object, Object> e = m.entrySet().iterator().next();
-		Map<Object, Object> m1 = (Map<Object, Object>) e.getKey();
-		assertNotNull(m1);
-		assertEquals(1, m1.size());
-		assertEquals((byte) 123, m1.get("foo"));
-		List<Object> l = (List<Object>) e.getValue();
-		assertNotNull(l);
-		assertEquals(1, l.size());
-		assertEquals((byte) 1, l.get(0));
+	public void testReadStruct() throws Exception {
+		// Two empty structs with IDs 0 and 255
+		setContents("F300" + "F2" + "F3FF" + "F2");
+		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("F300" + "F2" + "F3FF" + "F2");
+		r.skipStruct();
+		r.skipStruct();
+		assertTrue(r.eof());
+	}
 
 	@Test
-	public void testMaxStringLengthAppliesInsideMap() throws Exception {
-		setContents("F4" + "F703666F6F" + "F603010203" + "F2");
-		r.setMaxStringLength(3);
-		Map<String, Bytes> m = r.readMap(String.class, Bytes.class);
+	public void testSkipNestedStructMapAndList() throws Exception {
+		// A struct containing a map containing two empty lists
+		setContents("F300" + "F4" + "F5" + "F2" + "F5" + "F2" + "F2" + "F2");
+		r.skipStruct();
 		assertTrue(r.eof());
-		String key = "foo";
-		Bytes value = new Bytes(new byte[] {1, 2, 3});
-		assertEquals(Collections.singletonMap(key, value), m);
-		// The max string length should be applied inside the map
-		setContents("F4" + "F703666F6F" + "F603010203" + "F2");
-		r.setMaxStringLength(2);
-		try {
-			r.readMap(String.class, Bytes.class);
-			fail();
-		} catch(FormatException expected) {}
 	}
 
 	@Test
-	public void testMaxBytesLengthAppliesInsideMap() throws Exception {
-		setContents("F4" + "F703666F6F" + "F603010203" + "F2");
-		r.setMaxBytesLength(3);
-		Map<String, Bytes> m = r.readMap(String.class, Bytes.class);
+	public void testReadNull() throws Exception {
+		setContents("F1");
+		r.readNull();
 		assertTrue(r.eof());
-		String key = "foo";
-		Bytes value = new Bytes(new byte[] {1, 2, 3});
-		assertEquals(Collections.singletonMap(key, value), m);
-		// The max bytes length should be applied inside the map
-		setContents("F4" + "F703666F6F" + "F603010203" + "F2");
-		r.setMaxBytesLength(2);
-		try {
-			r.readMap(String.class, Bytes.class);
-			fail();
-		} catch(FormatException expected) {}
 	}
 
 	@Test
-	public void testReadStruct() throws Exception {
-		// Two structs with IDs 0 and 255, each containing 1 as a uint7
-		setContents("F300" + "01" + "F2" + "F3FF" + "01" + "F2");
-		r.readStructStart(0);
-		assertEquals(1, r.readUint7());
-		r.readStructEnd();
-		r.readStructStart(255);
-		assertEquals(1, r.readUint7());
-		r.readStructEnd();
+	public void testSkipNull() throws Exception {
+		setContents("F1");
+		r.skipNull();
 		assertTrue(r.eof());
 	}
 
-- 
GitLab