diff --git a/briar-api/src/org/briarproject/api/data/BdfDictionary.java b/briar-api/src/org/briarproject/api/data/BdfDictionary.java
new file mode 100644
index 0000000000000000000000000000000000000000..eecea996c123ed0dfb4ca4ef42987531362b3c22
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/BdfDictionary.java
@@ -0,0 +1,49 @@
+package org.briarproject.api.data;
+
+import java.util.HashMap;
+
+// This class is not thread-safe
+public class BdfDictionary extends HashMap<String, Object> {
+
+	public Boolean getBoolean(String key, Boolean defaultValue) {
+		Object o = get(key);
+		if (o instanceof Boolean) return (Boolean) o;
+		return defaultValue;
+	}
+
+	public Long getInteger(String key, Long defaultValue) {
+		Object o = get(key);
+		if (o instanceof Long) return (Long) o;
+		return defaultValue;
+	}
+
+	public Double getFloat(String key, Double defaultValue) {
+		Object o = get(key);
+		if (o instanceof Double) return (Double) o;
+		return defaultValue;
+	}
+
+	public String getString(String key, String defaultValue) {
+		Object o = get(key);
+		if (o instanceof String) return (String) o;
+		return defaultValue;
+	}
+
+	public byte[] getRaw(String key, byte[] defaultValue) {
+		Object o = get(key);
+		if (o instanceof byte[]) return (byte[]) o;
+		return defaultValue;
+	}
+
+	public BdfList getList(String key, BdfList defaultValue) {
+		Object o = get(key);
+		if (o instanceof BdfList) return (BdfList) o;
+		return defaultValue;
+	}
+
+	public BdfDictionary getDictionary(String key, BdfDictionary defaultValue) {
+		Object o = get(key);
+		if (o instanceof BdfDictionary) return (BdfDictionary) o;
+		return defaultValue;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/data/BdfList.java b/briar-api/src/org/briarproject/api/data/BdfList.java
new file mode 100644
index 0000000000000000000000000000000000000000..949d414676c10f27d6824df29841cbeb573beb49
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/BdfList.java
@@ -0,0 +1,49 @@
+package org.briarproject.api.data;
+
+import java.util.ArrayList;
+
+// This class is not thread-safe
+public class BdfList extends ArrayList<Object> {
+
+	public Boolean getBoolean(int index, Boolean defaultValue) {
+		Object o = get(index);
+		if (o instanceof Boolean) return (Boolean) o;
+		return defaultValue;
+	}
+
+	public Long getInteger(int index, Long defaultValue) {
+		Object o = get(index);
+		if (o instanceof Long) return (Long) o;
+		return defaultValue;
+	}
+
+	public Double getFloat(int index, Double defaultValue) {
+		Object o = get(index);
+		if (o instanceof Double) return (Double) o;
+		return defaultValue;
+	}
+
+	public String getString(int index, String defaultValue) {
+		Object o = get(index);
+		if (o instanceof String) return (String) o;
+		return defaultValue;
+	}
+
+	public byte[] getRaw(int index, byte[] defaultValue) {
+		Object o = get(index);
+		if (o instanceof byte[]) return (byte[]) o;
+		return defaultValue;
+	}
+
+	public BdfList getList(int index, BdfList defaultValue) {
+		Object o = get(index);
+		if (o instanceof BdfList) return (BdfList) o;
+		return defaultValue;
+	}
+
+	public BdfDictionary getDictionary(int index, BdfDictionary defaultValue) {
+		Object o = get(index);
+		if (o instanceof BdfDictionary) return (BdfDictionary) o;
+		return defaultValue;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/data/Reader.java b/briar-api/src/org/briarproject/api/data/BdfReader.java
similarity index 81%
rename from briar-api/src/org/briarproject/api/data/Reader.java
rename to briar-api/src/org/briarproject/api/data/BdfReader.java
index f8cc528d1c8e495a8114139524a3ba9af907abe9..d279fe7e11b4b3b4356c9ef64b01ef9d9c145bb7 100644
--- a/briar-api/src/org/briarproject/api/data/Reader.java
+++ b/briar-api/src/org/briarproject/api/data/BdfReader.java
@@ -2,7 +2,7 @@ package org.briarproject.api.data;
 
 import java.io.IOException;
 
-public interface Reader {
+public interface BdfReader {
 
 	boolean eof() throws IOException;
 	void close() throws IOException;
@@ -40,9 +40,9 @@ public interface Reader {
 	void readListEnd() throws IOException;
 	void skipList() throws IOException;
 
-	boolean hasMap() throws IOException;
-	void readMapStart() throws IOException;
-	boolean hasMapEnd() throws IOException;
-	void readMapEnd() throws IOException;
-	void skipMap() throws IOException;
+	boolean hasDictionary() throws IOException;
+	void readDictionaryStart() throws IOException;
+	boolean hasDictionaryEnd() throws IOException;
+	void readDictionaryEnd() throws IOException;
+	void skipDictionary() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/data/BdfReaderFactory.java b/briar-api/src/org/briarproject/api/data/BdfReaderFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e717f7476ab8bb5ca5210ee5350f6f869f22c7c6
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/BdfReaderFactory.java
@@ -0,0 +1,8 @@
+package org.briarproject.api.data;
+
+import java.io.InputStream;
+
+public interface BdfReaderFactory {
+
+	BdfReader createReader(InputStream in);
+}
diff --git a/briar-api/src/org/briarproject/api/data/Writer.java b/briar-api/src/org/briarproject/api/data/BdfWriter.java
similarity index 78%
rename from briar-api/src/org/briarproject/api/data/Writer.java
rename to briar-api/src/org/briarproject/api/data/BdfWriter.java
index 083313572faa2b1f074b6b8df26b557a52850428..e46e38a6de32fe312bbbfe75ba403521aa4cefe5 100644
--- a/briar-api/src/org/briarproject/api/data/Writer.java
+++ b/briar-api/src/org/briarproject/api/data/BdfWriter.java
@@ -4,7 +4,7 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Map;
 
-public interface Writer {
+public interface BdfWriter {
 
 	void flush() throws IOException;
 	void close() throws IOException;
@@ -23,7 +23,7 @@ public interface Writer {
 	void writeListStart() throws IOException;
 	void writeListEnd() throws IOException;
 
-	void writeMap(Map<?, ?> m) throws IOException;
-	void writeMapStart() throws IOException;
-	void writeMapEnd() throws IOException;
+	void writeDictionary(Map<?, ?> m) throws IOException;
+	void writeDictionaryStart() throws IOException;
+	void writeDictionaryEnd() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/data/BdfWriterFactory.java b/briar-api/src/org/briarproject/api/data/BdfWriterFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7ede1890837278dd928547264434cde3bdc224a
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/BdfWriterFactory.java
@@ -0,0 +1,8 @@
+package org.briarproject.api.data;
+
+import java.io.OutputStream;
+
+public interface BdfWriterFactory {
+
+	BdfWriter createWriter(OutputStream out);
+}
diff --git a/briar-api/src/org/briarproject/api/data/MetadataEncoder.java b/briar-api/src/org/briarproject/api/data/MetadataEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b2e3ab8e41f59ff35a41e682661f155a49b5130
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/MetadataEncoder.java
@@ -0,0 +1,9 @@
+package org.briarproject.api.data;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.db.Metadata;
+
+public interface MetadataEncoder {
+
+	Metadata encode(BdfDictionary d) throws FormatException;
+}
diff --git a/briar-api/src/org/briarproject/api/data/MetadataParser.java b/briar-api/src/org/briarproject/api/data/MetadataParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..798ad5df9c4a21067214e5d2d71fb98340be7455
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/MetadataParser.java
@@ -0,0 +1,9 @@
+package org.briarproject.api.data;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.db.Metadata;
+
+public interface MetadataParser {
+
+	BdfDictionary parse(Metadata m) throws FormatException;
+}
diff --git a/briar-api/src/org/briarproject/api/data/ObjectReader.java b/briar-api/src/org/briarproject/api/data/ObjectReader.java
index 3c83d41f3ed5b779f6b7f6b97b42963adee1ae6f..81f02902c1a6e9f4377be30e5a690014d069053d 100644
--- a/briar-api/src/org/briarproject/api/data/ObjectReader.java
+++ b/briar-api/src/org/briarproject/api/data/ObjectReader.java
@@ -4,5 +4,5 @@ import java.io.IOException;
 
 public interface ObjectReader<T> {
 
-	T readObject(Reader r) throws IOException;
+	T readObject(BdfReader r) throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/data/ReaderFactory.java b/briar-api/src/org/briarproject/api/data/ReaderFactory.java
deleted file mode 100644
index a349fd4bafe612c5485f86bdc99b4a2db546da21..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/data/ReaderFactory.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.briarproject.api.data;
-
-import java.io.InputStream;
-
-public interface ReaderFactory {
-
-	Reader createReader(InputStream in);
-}
diff --git a/briar-api/src/org/briarproject/api/data/WriterFactory.java b/briar-api/src/org/briarproject/api/data/WriterFactory.java
deleted file mode 100644
index 2fc1fd37d395c9b4e595e74c772b8e5033c874e3..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/data/WriterFactory.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.briarproject.api.data;
-
-import java.io.OutputStream;
-
-public interface WriterFactory {
-
-	Writer createWriter(OutputStream out);
-}
diff --git a/briar-api/src/org/briarproject/api/db/Metadata.java b/briar-api/src/org/briarproject/api/db/Metadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..b70df227f9d4573a69af8315dfcadbdc71da0fea
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/db/Metadata.java
@@ -0,0 +1,11 @@
+package org.briarproject.api.db;
+
+import java.util.Hashtable;
+
+public class Metadata extends Hashtable<String, byte[]> {
+
+	/**
+	 * Special value to indicate that a key is being removed.
+	 */
+	public static final byte[] REMOVE = new byte[0];
+}
diff --git a/briar-core/src/org/briarproject/data/BdfReaderFactoryImpl.java b/briar-core/src/org/briarproject/data/BdfReaderFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb2ca9e67186ffd15a4cd5cbaae0bf1f81e1c320
--- /dev/null
+++ b/briar-core/src/org/briarproject/data/BdfReaderFactoryImpl.java
@@ -0,0 +1,13 @@
+package org.briarproject.data;
+
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
+
+import java.io.InputStream;
+
+class BdfReaderFactoryImpl implements BdfReaderFactory {
+
+	public BdfReader createReader(InputStream in) {
+		return new BdfReaderImpl(in);
+	}
+}
diff --git a/briar-core/src/org/briarproject/data/ReaderImpl.java b/briar-core/src/org/briarproject/data/BdfReaderImpl.java
similarity index 89%
rename from briar-core/src/org/briarproject/data/ReaderImpl.java
rename to briar-core/src/org/briarproject/data/BdfReaderImpl.java
index db902ef8317b2682b7efed056e138153e3b2a073..08ad3a3e342470c5d1550e4e66910d8ea9cbb0ed 100644
--- a/briar-core/src/org/briarproject/data/ReaderImpl.java
+++ b/briar-core/src/org/briarproject/data/BdfReaderImpl.java
@@ -1,5 +1,15 @@
 package org.briarproject.data;
 
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.Consumer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static org.briarproject.data.Types.DICTIONARY;
 import static org.briarproject.data.Types.END;
 import static org.briarproject.data.Types.FALSE;
 import static org.briarproject.data.Types.FLOAT_64;
@@ -8,7 +18,6 @@ import static org.briarproject.data.Types.INT_32;
 import static org.briarproject.data.Types.INT_64;
 import static org.briarproject.data.Types.INT_8;
 import static org.briarproject.data.Types.LIST;
-import static org.briarproject.data.Types.MAP;
 import static org.briarproject.data.Types.NULL;
 import static org.briarproject.data.Types.RAW_16;
 import static org.briarproject.data.Types.RAW_32;
@@ -18,17 +27,8 @@ import static org.briarproject.data.Types.STRING_32;
 import static org.briarproject.data.Types.STRING_8;
 import static org.briarproject.data.Types.TRUE;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.briarproject.api.FormatException;
-import org.briarproject.api.data.Consumer;
-import org.briarproject.api.data.Reader;
-
 // This class is not thread-safe
-class ReaderImpl implements Reader {
+class BdfReaderImpl implements BdfReader {
 
 	private static final byte[] EMPTY_BUFFER = new byte[] {};
 
@@ -39,7 +39,7 @@ class ReaderImpl implements Reader {
 	private byte next;
 	private byte[] buf = new byte[8];
 
-	ReaderImpl(InputStream in) {
+	BdfReaderImpl(InputStream in) {
 		this.in = in;
 	}
 
@@ -88,14 +88,14 @@ class ReaderImpl implements Reader {
 	}
 
 	private void skipObject() throws IOException {
-		if (hasBoolean()) skipBoolean();
+		if (hasNull()) skipNull();
+		else if (hasBoolean()) skipBoolean();
 		else if (hasInteger()) skipInteger();
 		else if (hasFloat()) skipFloat();
 		else if (hasString()) skipString();
 		else if (hasRaw()) skipRaw();
 		else if (hasList()) skipList();
-		else if (hasMap()) skipMap();
-		else if (hasNull()) skipNull();
+		else if (hasDictionary()) skipDictionary();
 		else throw new FormatException();
 	}
 
@@ -173,18 +173,13 @@ class ReaderImpl implements Reader {
 
 	private short readInt16(boolean consume) throws IOException {
 		readIntoBuffer(2, consume);
-		short value = (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
-		if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)
-			throw new FormatException();
-		return value;
+		return (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
 	}
 
 	private int readInt32(boolean consume) throws IOException {
 		readIntoBuffer(4, consume);
 		int value = 0;
 		for (int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8);
-		if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
-			throw new FormatException();
 		return value;
 	}
 
@@ -192,8 +187,6 @@ class ReaderImpl implements Reader {
 		readIntoBuffer(8, consume);
 		long value = 0;
 		for (int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
-		if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE)
-			throw new FormatException();
 		return value;
 	}
 
@@ -327,30 +320,30 @@ class ReaderImpl implements Reader {
 		hasLookahead = false;
 	}
 
-	public boolean hasMap() throws IOException {
+	public boolean hasDictionary() throws IOException {
 		if (!hasLookahead) readLookahead();
 		if (eof) return false;
-		return next == MAP;
+		return next == DICTIONARY;
 	}
 
-	public void readMapStart() throws IOException {
-		if (!hasMap()) throw new FormatException();
+	public void readDictionaryStart() throws IOException {
+		if (!hasDictionary()) throw new FormatException();
 		consumeLookahead();
 	}
 
-	public boolean hasMapEnd() throws IOException {
+	public boolean hasDictionaryEnd() throws IOException {
 		return hasEnd();
 	}
 
-	public void readMapEnd() throws IOException {
+	public void readDictionaryEnd() throws IOException {
 		readEnd();
 	}
 
-	public void skipMap() throws IOException {
-		if (!hasMap()) throw new FormatException();
+	public void skipDictionary() throws IOException {
+		if (!hasDictionary()) throw new FormatException();
 		hasLookahead = false;
-		while (!hasMapEnd()) {
-			skipObject();
+		while (!hasDictionaryEnd()) {
+			skipString();
 			skipObject();
 		}
 		hasLookahead = false;
diff --git a/briar-core/src/org/briarproject/data/BdfWriterFactoryImpl.java b/briar-core/src/org/briarproject/data/BdfWriterFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..f98f9c871d6dbf197c47d32da45122ea4978e3f8
--- /dev/null
+++ b/briar-core/src/org/briarproject/data/BdfWriterFactoryImpl.java
@@ -0,0 +1,13 @@
+package org.briarproject.data;
+
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
+
+import java.io.OutputStream;
+
+class BdfWriterFactoryImpl implements BdfWriterFactory {
+
+	public BdfWriter createWriter(OutputStream out) {
+		return new BdfWriterImpl(out);
+	}
+}
diff --git a/briar-core/src/org/briarproject/data/WriterImpl.java b/briar-core/src/org/briarproject/data/BdfWriterImpl.java
similarity index 85%
rename from briar-core/src/org/briarproject/data/WriterImpl.java
rename to briar-core/src/org/briarproject/data/BdfWriterImpl.java
index 3f51be2769cf8512b3a0802afe4f77ecd81c3ba1..9a7c04c8175d4e4c2dc9ac67b7166b71f27f8e81 100644
--- a/briar-core/src/org/briarproject/data/WriterImpl.java
+++ b/briar-core/src/org/briarproject/data/BdfWriterImpl.java
@@ -1,5 +1,19 @@
 package org.briarproject.data;
 
+import org.briarproject.api.Bytes;
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.Consumer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static org.briarproject.data.Types.DICTIONARY;
 import static org.briarproject.data.Types.END;
 import static org.briarproject.data.Types.FALSE;
 import static org.briarproject.data.Types.FLOAT_64;
@@ -8,7 +22,6 @@ import static org.briarproject.data.Types.INT_32;
 import static org.briarproject.data.Types.INT_64;
 import static org.briarproject.data.Types.INT_8;
 import static org.briarproject.data.Types.LIST;
-import static org.briarproject.data.Types.MAP;
 import static org.briarproject.data.Types.NULL;
 import static org.briarproject.data.Types.RAW_16;
 import static org.briarproject.data.Types.RAW_32;
@@ -18,25 +31,13 @@ import static org.briarproject.data.Types.STRING_32;
 import static org.briarproject.data.Types.STRING_8;
 import static org.briarproject.data.Types.TRUE;
 
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.briarproject.api.Bytes;
-import org.briarproject.api.data.Consumer;
-import org.briarproject.api.data.Writer;
-
 // This class is not thread-safe
-class WriterImpl implements Writer {
+class BdfWriterImpl implements BdfWriter {
 
 	private final OutputStream out;
 	private final Collection<Consumer> consumers = new ArrayList<Consumer>(0);
 
-	WriterImpl(OutputStream out) {
+	BdfWriterImpl(OutputStream out) {
 		this.out = out;
 	}
 
@@ -145,7 +146,8 @@ class WriterImpl implements Writer {
 	}
 
 	private void writeObject(Object o) throws IOException {
-		if (o instanceof Boolean) writeBoolean((Boolean) o);
+		if (o == null) writeNull();
+		else if (o instanceof Boolean) writeBoolean((Boolean) o);
 		else if (o instanceof Byte) writeInteger((Byte) o);
 		else if (o instanceof Short) writeInteger((Short) o);
 		else if (o instanceof Integer) writeInteger((Integer) o);
@@ -155,10 +157,9 @@ class WriterImpl implements Writer {
 		else if (o instanceof String) writeString((String) o);
 		else if (o instanceof byte[]) writeRaw((byte[]) o);
 		else if (o instanceof Bytes) writeRaw(((Bytes) o).getBytes());
-		else if (o instanceof List<?>) writeList((List<?>) o);
-		else if (o instanceof Map<?, ?>) writeMap((Map<?, ?>) o);
-		else if (o == null) writeNull();
-		else throw new IllegalStateException();
+		else if (o instanceof List) writeList((List) o);
+		else if (o instanceof Map) writeDictionary((Map) o);
+		else throw new FormatException();
 	}
 
 	public void writeListStart() throws IOException {
@@ -169,20 +170,21 @@ class WriterImpl implements Writer {
 		write(END);
 	}
 
-	public void writeMap(Map<?, ?> m) throws IOException {
-		write(MAP);
+	public void writeDictionary(Map<?, ?> m) throws IOException {
+		write(DICTIONARY);
 		for (Entry<?, ?> e : m.entrySet()) {
-			writeObject(e.getKey());
+			if (!(e.getKey() instanceof String)) throw new FormatException();
+			writeString((String) e.getKey());
 			writeObject(e.getValue());
 		}
 		write(END);
 	}
 
-	public void writeMapStart() throws IOException {
-		write(MAP);
+	public void writeDictionaryStart() throws IOException {
+		write(DICTIONARY);
 	}
 
-	public void writeMapEnd() throws IOException {
+	public void writeDictionaryEnd() throws IOException {
 		write(END);
 	}
 
diff --git a/briar-core/src/org/briarproject/data/DataModule.java b/briar-core/src/org/briarproject/data/DataModule.java
index be692d7fb7c052fb8d944de2812d80e90569c995..c3fe841aee351adf938a17fd13dae8a095da52b1 100644
--- a/briar-core/src/org/briarproject/data/DataModule.java
+++ b/briar-core/src/org/briarproject/data/DataModule.java
@@ -1,15 +1,19 @@
 package org.briarproject.data;
 
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.WriterFactory;
-
 import com.google.inject.AbstractModule;
 
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriterFactory;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.data.MetadataParser;
+
 public class DataModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
-		bind(ReaderFactory.class).to(ReaderFactoryImpl.class);
-		bind(WriterFactory.class).to(WriterFactoryImpl.class);
+		bind(BdfReaderFactory.class).to(BdfReaderFactoryImpl.class);
+		bind(BdfWriterFactory.class).to(BdfWriterFactoryImpl.class);
+		bind(MetadataParser.class).to(MetadataParserImpl.class);
+		bind(MetadataEncoder.class).to(MetadataEncoderImpl.class);
 	}
 }
diff --git a/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java b/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc7dc3f9dc5a2808f71a993aa5984c2f1bf4eb99
--- /dev/null
+++ b/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java
@@ -0,0 +1,175 @@
+package org.briarproject.data;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.db.Metadata;
+import org.briarproject.util.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static org.briarproject.api.db.Metadata.REMOVE;
+import static org.briarproject.data.Types.DICTIONARY;
+import static org.briarproject.data.Types.END;
+import static org.briarproject.data.Types.FALSE;
+import static org.briarproject.data.Types.FLOAT_64;
+import static org.briarproject.data.Types.INT_16;
+import static org.briarproject.data.Types.INT_32;
+import static org.briarproject.data.Types.INT_64;
+import static org.briarproject.data.Types.INT_8;
+import static org.briarproject.data.Types.LIST;
+import static org.briarproject.data.Types.NULL;
+import static org.briarproject.data.Types.RAW_16;
+import static org.briarproject.data.Types.RAW_32;
+import static org.briarproject.data.Types.RAW_8;
+import static org.briarproject.data.Types.STRING_16;
+import static org.briarproject.data.Types.STRING_32;
+import static org.briarproject.data.Types.STRING_8;
+import static org.briarproject.data.Types.TRUE;
+
+class MetadataEncoderImpl implements MetadataEncoder {
+
+	@Override
+	public Metadata encode(BdfDictionary d) throws FormatException {
+		Metadata m = new Metadata();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		for (Entry<String, Object> e : d.entrySet()) {
+			if (e.getValue() == null) {
+				// Special case: if the value is null, the key is being removed
+				m.put(e.getKey(), REMOVE);
+			} else {
+				encodeObject(out, e.getValue());
+				m.put(e.getKey(), out.toByteArray());
+				out.reset();
+			}
+		}
+		return m;
+	}
+
+	private void encodeObject(ByteArrayOutputStream out, Object o)
+			throws FormatException {
+		if (o == null) out.write(NULL);
+		else if (o instanceof Boolean) out.write((Boolean) o ? TRUE : FALSE);
+		else if (o instanceof Byte) encodeInteger(out, (Byte) o);
+		else if (o instanceof Short) encodeInteger(out, (Short) o);
+		else if (o instanceof Integer) encodeInteger(out, (Integer) o);
+		else if (o instanceof Long) encodeInteger(out, (Long) o);
+		else if (o instanceof Float) encodeFloat(out, (Float) o);
+		else if (o instanceof Double) encodeFloat(out, (Double) o);
+		else if (o instanceof String) encodeString(out, (String) o);
+		else if (o instanceof byte[]) encodeRaw(out, (byte[]) o);
+		else if (o instanceof List) encodeList(out, (List) o);
+		else if (o instanceof Map) encodeDictionary(out, (Map) o);
+		else throw new FormatException();
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, byte i) {
+		out.write(INT_8);
+		out.write(i);
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, short i) {
+		if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
+			encodeInteger(out, (byte) i);
+		} else {
+			out.write(INT_16);
+			out.write((byte) (i >> 8));
+			out.write((byte) ((i << 8) >> 8));
+		}
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, int i) {
+		if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
+			encodeInteger(out, (short) i);
+		} else {
+			out.write(INT_32);
+			out.write((byte) (i >> 24));
+			out.write((byte) ((i << 8) >> 24));
+			out.write((byte) ((i << 16) >> 24));
+			out.write((byte) ((i << 24) >> 24));
+		}
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, long i) {
+		if (i >= Integer.MIN_VALUE && i <= Integer.MAX_VALUE) {
+			encodeInteger(out, (int) i);
+		} else {
+			out.write(INT_64);
+			out.write((byte) (i >> 56));
+			out.write((byte) ((i << 8) >> 56));
+			out.write((byte) ((i << 16) >> 56));
+			out.write((byte) ((i << 24) >> 56));
+			out.write((byte) ((i << 32) >> 56));
+			out.write((byte) ((i << 40) >> 56));
+			out.write((byte) ((i << 48) >> 56));
+			out.write((byte) ((i << 56) >> 56));
+		}
+	}
+
+	private void encodeFloat(ByteArrayOutputStream out, float f) {
+		encodeFloat(out, (double) f);
+	}
+
+	private void encodeFloat(ByteArrayOutputStream out, double d) {
+		long i = Double.doubleToLongBits(d);
+		out.write(FLOAT_64);
+		out.write((byte) (i >> 56));
+		out.write((byte) ((i << 8) >> 56));
+		out.write((byte) ((i << 16) >> 56));
+		out.write((byte) ((i << 24) >> 56));
+		out.write((byte) ((i << 32) >> 56));
+		out.write((byte) ((i << 40) >> 56));
+		out.write((byte) ((i << 48) >> 56));
+		out.write((byte) ((i << 56) >> 56));
+	}
+
+	private void encodeString(ByteArrayOutputStream out, String s) {
+		byte[] b = StringUtils.toUtf8(s);
+		if (b.length <= Byte.MAX_VALUE) {
+			out.write(STRING_8);
+			encodeInteger(out, (byte) b.length);
+		} else if (b.length <= Short.MAX_VALUE) {
+			out.write(STRING_16);
+			encodeInteger(out, (short) b.length);
+		} else {
+			out.write(STRING_32);
+			encodeInteger(out, b.length);
+		}
+		out.write(b, 0, b.length);
+	}
+
+	private void encodeRaw(ByteArrayOutputStream out, byte[] b) {
+		if (b.length <= Byte.MAX_VALUE) {
+			out.write(RAW_8);
+			encodeInteger(out, (byte) b.length);
+		} else if (b.length <= Short.MAX_VALUE) {
+			out.write(RAW_16);
+			encodeInteger(out, (short) b.length);
+		} else {
+			out.write(RAW_32);
+			encodeInteger(out, b.length);
+		}
+		out.write(b, 0, b.length);
+	}
+
+	private void encodeList(ByteArrayOutputStream out, List list)
+			throws FormatException {
+		out.write(LIST);
+		for (Object o : list) encodeObject(out, o);
+		out.write(END);
+	}
+
+	private void encodeDictionary(ByteArrayOutputStream out, Map<?, ?> map)
+			throws FormatException {
+		out.write(DICTIONARY);
+		for (Entry<?, ?> e : map.entrySet()) {
+			if (!(e.getKey() instanceof String)) throw new FormatException();
+			encodeString(out, (String) e.getKey());
+			encodeObject(out, e.getValue());
+		}
+		out.write(END);
+	}
+}
diff --git a/briar-core/src/org/briarproject/data/MetadataParserImpl.java b/briar-core/src/org/briarproject/data/MetadataParserImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4624dc6037881562a6ea7dff91a0c12ad9e076d
--- /dev/null
+++ b/briar-core/src/org/briarproject/data/MetadataParserImpl.java
@@ -0,0 +1,165 @@
+package org.briarproject.data;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.db.Metadata;
+import org.briarproject.util.StringUtils;
+
+import java.io.ByteArrayInputStream;
+import java.util.Map.Entry;
+
+import static org.briarproject.api.db.Metadata.REMOVE;
+import static org.briarproject.data.Types.DICTIONARY;
+import static org.briarproject.data.Types.END;
+import static org.briarproject.data.Types.FALSE;
+import static org.briarproject.data.Types.FLOAT_64;
+import static org.briarproject.data.Types.INT_16;
+import static org.briarproject.data.Types.INT_32;
+import static org.briarproject.data.Types.INT_64;
+import static org.briarproject.data.Types.INT_8;
+import static org.briarproject.data.Types.LIST;
+import static org.briarproject.data.Types.NULL;
+import static org.briarproject.data.Types.RAW_16;
+import static org.briarproject.data.Types.RAW_32;
+import static org.briarproject.data.Types.RAW_8;
+import static org.briarproject.data.Types.STRING_16;
+import static org.briarproject.data.Types.STRING_32;
+import static org.briarproject.data.Types.STRING_8;
+import static org.briarproject.data.Types.TRUE;
+
+class MetadataParserImpl implements MetadataParser {
+
+	@Override
+	public BdfDictionary parse(Metadata m) throws FormatException {
+		BdfDictionary dict = new BdfDictionary();
+		for (Entry<String, byte[]> e : m.entrySet())
+			dict.put(e.getKey(), parseObject(e.getValue()));
+		return dict;
+	}
+
+	private Object parseObject(byte[] b) throws FormatException {
+		if (b == REMOVE) return null;
+		ByteArrayInputStream in = new ByteArrayInputStream(b);
+		Object o = parseObject(in);
+		if (in.available() > 0) throw new FormatException();
+		return o;
+	}
+
+	private Object parseObject(ByteArrayInputStream in) throws FormatException {
+		switch(in.read()) {
+			case NULL:
+				return null;
+			case TRUE:
+				return Boolean.TRUE;
+			case FALSE:
+				return Boolean.FALSE;
+			case INT_8:
+				return (long) parseInt8(in);
+			case INT_16:
+				return (long) parseInt16(in);
+			case INT_32:
+				return (long) parseInt32(in);
+			case INT_64:
+				return parseInt64(in);
+			case FLOAT_64:
+				return Double.longBitsToDouble(parseInt64(in));
+			case STRING_8:
+				return parseString(in, parseInt8(in));
+			case STRING_16:
+				return parseString(in, parseInt16(in));
+			case STRING_32:
+				return parseString(in, parseInt32(in));
+			case RAW_8:
+				return parseRaw(in, parseInt8(in));
+			case RAW_16:
+				return parseRaw(in, parseInt16(in));
+			case RAW_32:
+				return parseRaw(in, parseInt32(in));
+			case LIST:
+				return parseList(in);
+			case DICTIONARY:
+				return parseDictionary(in);
+			default:
+				throw new FormatException();
+		}
+	}
+
+	private String parseString(ByteArrayInputStream in) throws FormatException {
+		switch(in.read()) {
+			case STRING_8:
+				return parseString(in, parseInt8(in));
+			case STRING_16:
+				return parseString(in, parseInt16(in));
+			case STRING_32:
+				return parseString(in, parseInt32(in));
+			default:
+				throw new FormatException();
+		}
+	}
+
+	private byte parseInt8(ByteArrayInputStream in) throws FormatException {
+		if (in.available() < 1) throw new FormatException();
+		return (byte) in.read();
+	}
+
+	private short parseInt16(ByteArrayInputStream in) throws FormatException {
+		if (in.available() < 2) throw new FormatException();
+		return (short) (((in.read() & 0xFF) << 8) + (in.read() & 0xFF));
+	}
+
+	private int parseInt32(ByteArrayInputStream in) throws FormatException {
+		if (in.available() < 4) throw new FormatException();
+		int value = 0;
+		for (int i = 0; i < 4; i++)
+			value |= (in.read() & 0xFF) << (24 - i * 8);
+		return value;
+	}
+
+	private long parseInt64(ByteArrayInputStream in) throws FormatException {
+		if (in.available() < 8) throw new FormatException();
+		long value = 0;
+		for (int i = 0; i < 8; i++)
+			value |= (in.read() & 0xFFL) << (56 - i * 8);
+		return value;
+	}
+
+	private String parseString(ByteArrayInputStream in, int len)
+			throws FormatException {
+		if (len < 0) throw new FormatException();
+		byte[] b = new byte[len];
+		if (in.read(b, 0, len) != len) throw new FormatException();
+		return StringUtils.fromUtf8(b, 0, len);
+	}
+
+	private byte[] parseRaw(ByteArrayInputStream in, int len)
+			throws FormatException {
+		if (len < 0) throw new FormatException();
+		byte[] b = new byte[len];
+		if (in.read(b, 0, len) != len) throw new FormatException();
+		return b;
+	}
+
+	private BdfList parseList(ByteArrayInputStream in) throws FormatException {
+		BdfList list = new BdfList();
+		while (peek(in) != END) list.add(parseObject(in));
+		if (in.read() != END) throw new FormatException();
+		return list;
+	}
+
+	private BdfDictionary parseDictionary(ByteArrayInputStream in)
+			throws FormatException {
+		BdfDictionary dict = new BdfDictionary();
+		while (peek(in) != END) dict.put(parseString(in), parseObject(in));
+		if (in.read() != END) throw new FormatException();
+		return dict;
+	}
+
+	private int peek(ByteArrayInputStream in) {
+		in.mark(1);
+		int next = in.read();
+		in.reset();
+		return next;
+	}
+}
diff --git a/briar-core/src/org/briarproject/data/ReaderFactoryImpl.java b/briar-core/src/org/briarproject/data/ReaderFactoryImpl.java
deleted file mode 100644
index 15b4376faac71c217d4643dcc9ed2167ac01619f..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/data/ReaderFactoryImpl.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.briarproject.data;
-
-import java.io.InputStream;
-
-import org.briarproject.api.data.Reader;
-import org.briarproject.api.data.ReaderFactory;
-
-class ReaderFactoryImpl implements ReaderFactory {
-
-	public Reader createReader(InputStream in) {
-		return new ReaderImpl(in);
-	}
-}
diff --git a/briar-core/src/org/briarproject/data/Types.java b/briar-core/src/org/briarproject/data/Types.java
index 5f5dbc5c9ffd09dfe403090fd50297a85a590d08..6cc701d28fc13ca78bd99041b127fa5e6bdb957a 100644
--- a/briar-core/src/org/briarproject/data/Types.java
+++ b/briar-core/src/org/briarproject/data/Types.java
@@ -17,6 +17,6 @@ interface Types {
 	byte RAW_16 = 0x52;
 	byte RAW_32 = 0x54;
 	byte LIST = 0x60;
-	byte MAP = 0x70;
+	byte DICTIONARY = 0x70;
 	byte END = (byte) 0x80;
 }
diff --git a/briar-core/src/org/briarproject/data/WriterFactoryImpl.java b/briar-core/src/org/briarproject/data/WriterFactoryImpl.java
deleted file mode 100644
index d8e651d78f9564aeb52b21fdb3901137f1def768..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/data/WriterFactoryImpl.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.briarproject.data;
-
-import java.io.OutputStream;
-
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
-
-class WriterFactoryImpl implements WriterFactory {
-
-	public Writer createWriter(OutputStream out) {
-		return new WriterImpl(out);
-	}
-}
diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java
index 84de4fc5472296e847e66cedee8d2e868c90f4f3..794f542ec7eaa2df0ac52448ff9a668d41aeeb16 100644
--- a/briar-core/src/org/briarproject/invitation/AliceConnector.java
+++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java
@@ -6,10 +6,10 @@ import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
-import org.briarproject.api.data.Reader;
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
@@ -42,7 +42,8 @@ class AliceConnector extends Connector {
 			Logger.getLogger(AliceConnector.class.getName());
 
 	AliceConnector(CryptoComponent crypto,
-			ReaderFactory readerFactory, WriterFactory writerFactory,
+			BdfReaderFactory bdfReaderFactory,
+			BdfWriterFactory bdfWriterFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
@@ -53,7 +54,7 @@ class AliceConnector extends Connector {
 			LocalAuthor localAuthor,
 			Map<TransportId, TransportProperties> localProps,
 			PseudoRandom random) {
-		super(crypto, readerFactory, writerFactory, streamReaderFactory,
+		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
 				streamWriterFactory, authorFactory, groupFactory,
 				keyManager, connectionManager, contactManager,
 				messagingManager, transportPropertyManager, clock,
@@ -76,14 +77,14 @@ class AliceConnector extends Connector {
 		// Carry out the key agreement protocol
 		InputStream in;
 		OutputStream out;
-		Reader r;
-		Writer w;
+		BdfReader r;
+		BdfWriter w;
 		SecretKey master;
 		try {
 			in = conn.getReader().getInputStream();
 			out = conn.getWriter().getOutputStream();
-			r = readerFactory.createReader(in);
-			w = writerFactory.createWriter(out);
+			r = bdfReaderFactory.createReader(in);
+			w = bdfWriterFactory.createWriter(out);
 			// Alice goes first
 			sendPublicKeyHash(w);
 			byte[] hash = receivePublicKeyHash(r);
@@ -144,12 +145,12 @@ class AliceConnector extends Connector {
 		InputStream streamReader =
 				streamReaderFactory.createInvitationStreamReader(in,
 						bobHeaderKey);
-		r = readerFactory.createReader(streamReader);
+		r = bdfReaderFactory.createReader(streamReader);
 		// Create the writers
 		OutputStream streamWriter =
 				streamWriterFactory.createInvitationStreamWriter(out,
 						aliceHeaderKey);
-		w = writerFactory.createWriter(streamWriter);
+		w = bdfWriterFactory.createWriter(streamWriter);
 		// Derive the invitation nonces
 		byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
 		byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java
index f598e4a0034558ea79caaf5f8c7e3d2e4f67fdcb..270ecfbf197f06f4a546ba6185efe7cb18d137da 100644
--- a/briar-core/src/org/briarproject/invitation/BobConnector.java
+++ b/briar-core/src/org/briarproject/invitation/BobConnector.java
@@ -6,10 +6,10 @@ import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
-import org.briarproject.api.data.Reader;
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
@@ -42,7 +42,8 @@ class BobConnector extends Connector {
 			Logger.getLogger(BobConnector.class.getName());
 
 	BobConnector(CryptoComponent crypto,
-			ReaderFactory readerFactory, WriterFactory writerFactory,
+			BdfReaderFactory bdfReaderFactory,
+			BdfWriterFactory bdfWriterFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
@@ -53,7 +54,7 @@ class BobConnector extends Connector {
 			LocalAuthor localAuthor,
 			Map<TransportId, TransportProperties> localProps,
 			PseudoRandom random) {
-		super(crypto, readerFactory, writerFactory, streamReaderFactory,
+		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
 				streamWriterFactory, authorFactory, groupFactory,
 				keyManager, connectionManager, contactManager,
 				messagingManager, transportPropertyManager, clock,
@@ -70,14 +71,14 @@ class BobConnector extends Connector {
 		// Carry out the key agreement protocol
 		InputStream in;
 		OutputStream out;
-		Reader r;
-		Writer w;
+		BdfReader r;
+		BdfWriter w;
 		SecretKey master;
 		try {
 			in = conn.getReader().getInputStream();
 			out = conn.getWriter().getOutputStream();
-			r = readerFactory.createReader(in);
-			w = writerFactory.createWriter(out);
+			r = bdfReaderFactory.createReader(in);
+			w = bdfWriterFactory.createWriter(out);
 			// Alice goes first
 			byte[] hash = receivePublicKeyHash(r);
 			// Don't proceed with more than one connection
@@ -144,12 +145,12 @@ class BobConnector extends Connector {
 		InputStream streamReader =
 				streamReaderFactory.createInvitationStreamReader(in,
 						aliceHeaderKey);
-		r = readerFactory.createReader(streamReader);
+		r = bdfReaderFactory.createReader(streamReader);
 		// Create the writers
 		OutputStream streamWriter =
 				streamWriterFactory.createInvitationStreamWriter(out,
 						bobHeaderKey);
-		w = writerFactory.createWriter(streamWriter);
+		w = bdfWriterFactory.createWriter(streamWriter);
 		// Derive the nonces
 		byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
 		byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java
index 1e22f0492605399947c79d12fa7e81a87b311a84..bd2d8a32545b5e57cb6437953cc9013fbe4e86ce 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -12,10 +12,10 @@ import org.briarproject.api.crypto.MessageDigest;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.crypto.Signature;
-import org.briarproject.api.data.Reader;
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
@@ -56,8 +56,8 @@ abstract class Connector extends Thread {
 			Logger.getLogger(Connector.class.getName());
 
 	protected final CryptoComponent crypto;
-	protected final ReaderFactory readerFactory;
-	protected final WriterFactory writerFactory;
+	protected final BdfReaderFactory bdfReaderFactory;
+	protected final BdfWriterFactory bdfWriterFactory;
 	protected final StreamReaderFactory streamReaderFactory;
 	protected final StreamWriterFactory streamWriterFactory;
 	protected final AuthorFactory authorFactory;
@@ -83,7 +83,8 @@ abstract class Connector extends Thread {
 	private volatile ContactId contactId = null;
 
 	Connector(CryptoComponent crypto,
-			ReaderFactory readerFactory, WriterFactory writerFactory,
+			BdfReaderFactory bdfReaderFactory,
+			BdfWriterFactory bdfWriterFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
@@ -96,8 +97,8 @@ abstract class Connector extends Thread {
 			PseudoRandom random) {
 		super("Connector");
 		this.crypto = crypto;
-		this.readerFactory = readerFactory;
-		this.writerFactory = writerFactory;
+		this.bdfReaderFactory = bdfReaderFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
 		this.streamReaderFactory = streamReaderFactory;
 		this.streamWriterFactory = streamWriterFactory;
 		this.authorFactory = authorFactory;
@@ -126,13 +127,13 @@ abstract class Connector extends Thread {
 		return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT);
 	}
 
-	protected void sendPublicKeyHash(Writer w) throws IOException {
+	protected void sendPublicKeyHash(BdfWriter w) throws IOException {
 		w.writeRaw(messageDigest.digest(keyPair.getPublic().getEncoded()));
 		w.flush();
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
 	}
 
-	protected byte[] receivePublicKeyHash(Reader r) throws IOException {
+	protected byte[] receivePublicKeyHash(BdfReader r) throws IOException {
 		int hashLength = messageDigest.getDigestLength();
 		byte[] b = r.readRaw(hashLength);
 		if (b.length < hashLength) throw new FormatException();
@@ -140,15 +141,15 @@ abstract class Connector extends Thread {
 		return b;
 	}
 
-	protected void sendPublicKey(Writer w) throws IOException {
+	protected void sendPublicKey(BdfWriter w) throws IOException {
 		byte[] key = keyPair.getPublic().getEncoded();
 		w.writeRaw(key);
 		w.flush();
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
 	}
 
-	protected byte[] receivePublicKey(Reader r) throws GeneralSecurityException,
-	IOException {
+	protected byte[] receivePublicKey(BdfReader r)
+			throws GeneralSecurityException, IOException {
 		byte[] b = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
 		keyParser.parsePublicKey(b);
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
@@ -169,7 +170,7 @@ abstract class Connector extends Thread {
 		return crypto.deriveMasterSecret(key, keyPair, alice);
 	}
 
-	protected void sendConfirmation(Writer w, boolean confirmed)
+	protected void sendConfirmation(BdfWriter w, boolean confirmed)
 			throws IOException {
 		w.writeBoolean(confirmed);
 		w.flush();
@@ -177,14 +178,14 @@ abstract class Connector extends Thread {
 			LOG.info(pluginName + " sent confirmation: " + confirmed);
 	}
 
-	protected boolean receiveConfirmation(Reader r) throws IOException {
+	protected boolean receiveConfirmation(BdfReader r) throws IOException {
 		boolean confirmed = r.readBoolean();
 		if (LOG.isLoggable(INFO))
 			LOG.info(pluginName + " received confirmation: " + confirmed);
 		return confirmed;
 	}
 
-	protected void sendPseudonym(Writer w, byte[] nonce)
+	protected void sendPseudonym(BdfWriter w, byte[] nonce)
 			throws GeneralSecurityException, IOException {
 		// Sign the nonce
 		Signature signature = crypto.getSignature();
@@ -201,7 +202,7 @@ abstract class Connector extends Thread {
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent pseudonym");
 	}
 
-	protected Author receivePseudonym(Reader r, byte[] nonce)
+	protected Author receivePseudonym(BdfReader r, byte[] nonce)
 			throws GeneralSecurityException, IOException {
 		// Read the name, public key and signature
 		String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
@@ -221,24 +222,26 @@ abstract class Connector extends Thread {
 		return authorFactory.createAuthor(name, publicKey);
 	}
 
-	protected void sendTimestamp(Writer w, long timestamp) throws IOException {
+	protected void sendTimestamp(BdfWriter w, long timestamp)
+			throws IOException {
 		w.writeInteger(timestamp);
 		w.flush();
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent timestamp");
 	}
 
-	protected long receiveTimestamp(Reader r) throws IOException {
+	protected long receiveTimestamp(BdfReader r) throws IOException {
 		long timestamp = r.readInteger();
 		if (timestamp < 0) throw new FormatException();
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received timestamp");
 		return timestamp;
 	}
 
-	protected void sendTransportProperties(Writer w) throws IOException {
+	protected void sendTransportProperties(BdfWriter w) throws IOException {
 		w.writeListStart();
-		for (Entry<TransportId, TransportProperties> e : localProps.entrySet()) {
+		for (Entry<TransportId, TransportProperties> e :
+				localProps.entrySet()) {
 			w.writeString(e.getKey().getString());
-			w.writeMap(e.getValue());
+			w.writeDictionary(e.getValue());
 		}
 		w.writeListEnd();
 		w.flush();
@@ -247,7 +250,7 @@ abstract class Connector extends Thread {
 	}
 
 	protected Map<TransportId, TransportProperties> receiveTransportProperties(
-			Reader r) throws IOException {
+			BdfReader r) throws IOException {
 		Map<TransportId, TransportProperties> remoteProps =
 				new HashMap<TransportId, TransportProperties>();
 		r.readListStart();
@@ -256,15 +259,15 @@ abstract class Connector extends Thread {
 			if (idString.length() == 0) throw new FormatException();
 			TransportId id = new TransportId(idString);
 			Map<String, String> p = new HashMap<String, String>();
-			r.readMapStart();
-			for (int i = 0; !r.hasMapEnd(); i++) {
+			r.readDictionaryStart();
+			for (int i = 0; !r.hasDictionaryEnd(); 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();
+			r.readDictionaryEnd();
 			remoteProps.put(id, new TransportProperties(p));
 		}
 		r.readListEnd();
diff --git a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
index f4ed123527e3b3f479a0235f751d4780ed1ded83..378fc54a5b421dc01b62c6119f897cb6f698a4c8 100644
--- a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
+++ b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
@@ -5,8 +5,8 @@ import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
@@ -48,8 +48,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			Logger.getLogger(ConnectorGroup.class.getName());
 
 	private final CryptoComponent crypto;
-	private final ReaderFactory readerFactory;
-	private final WriterFactory writerFactory;
+	private final BdfReaderFactory bdfReaderFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 	private final StreamReaderFactory streamReaderFactory;
 	private final StreamWriterFactory streamWriterFactory;
 	private final AuthorFactory authorFactory;
@@ -78,7 +78,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	private String remoteName = null;
 
 	ConnectorGroup(CryptoComponent crypto,
-			ReaderFactory readerFactory, WriterFactory writerFactory,
+			BdfReaderFactory bdfReaderFactory,
+			BdfWriterFactory bdfWriterFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
@@ -91,8 +92,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			boolean reuseConnection) {
 		super("ConnectorGroup");
 		this.crypto = crypto;
-		this.readerFactory = readerFactory;
-		this.writerFactory = writerFactory;
+		this.bdfReaderFactory = bdfReaderFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
 		this.streamReaderFactory = streamReaderFactory;
 		this.streamWriterFactory = streamWriterFactory;
 		this.authorFactory = authorFactory;
@@ -197,7 +198,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			Map<TransportId, TransportProperties> localProps) {
 		PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
 				remoteInvitationCode);
-		return new AliceConnector(crypto, readerFactory, writerFactory,
+		return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, contactManager,
 				messagingManager, transportPropertyManager, clock,
@@ -209,7 +210,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			Map<TransportId, TransportProperties> localProps) {
 		PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
 				localInvitationCode);
-		return new BobConnector(crypto, readerFactory, writerFactory,
+		return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, contactManager,
 				messagingManager, transportPropertyManager, clock,
diff --git a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
index 899b8c558dcdd3004902abfb435bd4cc8a81f53a..9879f7f5518d33d570a16b3af9c36013abebfb8f 100644
--- a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
+++ b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
@@ -2,8 +2,8 @@ package org.briarproject.invitation;
 
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.IdentityManager;
@@ -24,8 +24,8 @@ import javax.inject.Inject;
 class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 
 	private final CryptoComponent crypto;
-	private final ReaderFactory readerFactory;
-	private final WriterFactory writerFactory;
+	private final BdfReaderFactory bdfReaderFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 	private final StreamReaderFactory streamReaderFactory;
 	private final StreamWriterFactory streamWriterFactory;
 	private final AuthorFactory authorFactory;
@@ -41,7 +41,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 
 	@Inject
 	InvitationTaskFactoryImpl(CryptoComponent crypto,
-			ReaderFactory readerFactory, WriterFactory writerFactory,
+			BdfReaderFactory bdfReaderFactory, BdfWriterFactory bdfWriterFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
@@ -51,8 +51,8 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 			TransportPropertyManager transportPropertyManager,
 			Clock clock, PluginManager pluginManager) {
 		this.crypto = crypto;
-		this.readerFactory = readerFactory;
-		this.writerFactory = writerFactory;
+		this.bdfReaderFactory = bdfReaderFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
 		this.streamReaderFactory = streamReaderFactory;
 		this.streamWriterFactory = streamWriterFactory;
 		this.authorFactory = authorFactory;
@@ -69,7 +69,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 
 	public InvitationTask createTask(AuthorId localAuthorId, int localCode,
 			int remoteCode, boolean reuseConnection) {
-		return new ConnectorGroup(crypto, readerFactory, writerFactory,
+		return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, identityManager,
 				contactManager, messagingManager, transportPropertyManager,
diff --git a/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java b/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
index 9f318112e3747ef1606ef5a90b448f47041a1600..aad2aceca273a90560631dc89e48b2c5c3140771 100644
--- a/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
@@ -2,8 +2,8 @@ package org.briarproject.sync;
 
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.MessageDigest;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
@@ -18,14 +18,14 @@ import javax.inject.Inject;
 class AuthorFactoryImpl implements AuthorFactory {
 
 	private final CryptoComponent crypto;
-	private final WriterFactory writerFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 	private final Clock clock;
 
 	@Inject
-	AuthorFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory,
+	AuthorFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory,
 			Clock clock) {
 		this.crypto = crypto;
-		this.writerFactory = writerFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
 		this.clock = clock;
 	}
 
@@ -41,7 +41,7 @@ class AuthorFactoryImpl implements AuthorFactory {
 
 	private AuthorId getId(String name, byte[] publicKey) {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		try {
 			w.writeListStart();
 			w.writeString(name);
diff --git a/briar-core/src/org/briarproject/sync/AuthorReader.java b/briar-core/src/org/briarproject/sync/AuthorReader.java
index eb15f833170fa1539fc4b2b96808065ddd198403..5715bdf9c7f197db8ccae0e563af6f396fb7ea9b 100644
--- a/briar-core/src/org/briarproject/sync/AuthorReader.java
+++ b/briar-core/src/org/briarproject/sync/AuthorReader.java
@@ -3,8 +3,8 @@ package org.briarproject.sync;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.MessageDigest;
+import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.data.Reader;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 
@@ -21,7 +21,7 @@ class AuthorReader implements ObjectReader<Author> {
 		messageDigest = crypto.getMessageDigest();
 	}
 
-	public Author readObject(Reader r) throws IOException {
+	public Author readObject(BdfReader r) throws IOException {
 		// Set up the reader
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		r.addConsumer(digesting);
diff --git a/briar-core/src/org/briarproject/sync/GroupFactoryImpl.java b/briar-core/src/org/briarproject/sync/GroupFactoryImpl.java
index 2b422b917c9947f2a74211fa89f2f20ecaa4aefc..6f36b0f30201eefca0ced9033ad4d706f52ee0f6 100644
--- a/briar-core/src/org/briarproject/sync/GroupFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/GroupFactoryImpl.java
@@ -2,8 +2,8 @@ package org.briarproject.sync;
 
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.MessageDigest;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.sync.GroupId;
@@ -18,12 +18,12 @@ import static org.briarproject.api.sync.MessagingConstants.GROUP_SALT_LENGTH;
 class GroupFactoryImpl implements GroupFactory {
 
 	private final CryptoComponent crypto;
-	private final WriterFactory writerFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 
 	@Inject
-	GroupFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory) {
+	GroupFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory) {
 		this.crypto = crypto;
-		this.writerFactory = writerFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
 	}
 
 	public Group createGroup(String name) {
@@ -34,7 +34,7 @@ class GroupFactoryImpl implements GroupFactory {
 
 	public Group createGroup(String name, byte[] salt) {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		try {
 			w.writeListStart();
 			w.writeString(name);
diff --git a/briar-core/src/org/briarproject/sync/GroupReader.java b/briar-core/src/org/briarproject/sync/GroupReader.java
index 9ee9fe1a0507e6aae30fc5fe080ca7c7968c7a40..529a8170a7cdd01be8228e5cbd5ed358bbdd44c2 100644
--- a/briar-core/src/org/briarproject/sync/GroupReader.java
+++ b/briar-core/src/org/briarproject/sync/GroupReader.java
@@ -3,8 +3,8 @@ package org.briarproject.sync;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.MessageDigest;
+import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.data.Reader;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 
@@ -21,7 +21,7 @@ class GroupReader implements ObjectReader<Group> {
 		messageDigest = crypto.getMessageDigest();
 	}
 
-	public Group readObject(Reader r) throws IOException {
+	public Group readObject(BdfReader r) throws IOException {
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		// Read and digest the data
 		r.addConsumer(digesting);
diff --git a/briar-core/src/org/briarproject/sync/MessageFactoryImpl.java b/briar-core/src/org/briarproject/sync/MessageFactoryImpl.java
index 209b71362a9c8ad28860c900ef7efbd41614e1ce..493713e3bd8366bda1547837cb3d6d053b8e4b67 100644
--- a/briar-core/src/org/briarproject/sync/MessageFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/MessageFactoryImpl.java
@@ -4,9 +4,9 @@ import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.MessageDigest;
 import org.briarproject.api.crypto.PrivateKey;
 import org.briarproject.api.crypto.Signature;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.data.Consumer;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.Message;
@@ -32,14 +32,14 @@ class MessageFactoryImpl implements MessageFactory {
 	private final Signature signature;
 	private final SecureRandom random;
 	private final MessageDigest messageDigest;
-	private final WriterFactory writerFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 
 	@Inject
-	MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory) {
+	MessageFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory) {
 		signature = crypto.getSignature();
 		random = crypto.getSecureRandom();
 		messageDigest = crypto.getMessageDigest();
-		this.writerFactory = writerFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
 	}
 
 	public Message createAnonymousMessage(MessageId parent, Group group,
@@ -69,7 +69,7 @@ class MessageFactoryImpl implements MessageFactory {
 			throw new IllegalArgumentException();
 		// Serialise the message to a buffer
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		// Initialise the consumers
 		CountingConsumer counting = new CountingConsumer(MAX_PAYLOAD_LENGTH);
 		w.addConsumer(counting);
@@ -113,14 +113,14 @@ class MessageFactoryImpl implements MessageFactory {
 				timestamp, out.toByteArray(), bodyStart, body.length);
 	}
 
-	private void writeGroup(Writer w, Group g) throws IOException {
+	private void writeGroup(BdfWriter w, Group g) throws IOException {
 		w.writeListStart();
 		w.writeString(g.getName());
 		w.writeRaw(g.getSalt());
 		w.writeListEnd();
 	}
 
-	private void writeAuthor(Writer w, Author a) throws IOException {
+	private void writeAuthor(BdfWriter w, Author a) throws IOException {
 		w.writeListStart();
 		w.writeString(a.getName());
 		w.writeRaw(a.getPublicKey());
diff --git a/briar-core/src/org/briarproject/sync/MessageReader.java b/briar-core/src/org/briarproject/sync/MessageReader.java
index f96c964f0b704c29f1014e791f05939d4d03b0f0..617d9fa68621ea07e90da88ea01b5e4fd2bd1979 100644
--- a/briar-core/src/org/briarproject/sync/MessageReader.java
+++ b/briar-core/src/org/briarproject/sync/MessageReader.java
@@ -2,8 +2,8 @@ package org.briarproject.sync;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.UniqueId;
+import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.data.Reader;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.MessageId;
@@ -28,7 +28,7 @@ class MessageReader implements ObjectReader<UnverifiedMessage> {
 		this.authorReader = authorReader;
 	}
 
-	public UnverifiedMessage readObject(Reader r) throws IOException {
+	public UnverifiedMessage readObject(BdfReader r) throws IOException {
 		CopyingConsumer copying = new CopyingConsumer();
 		CountingConsumer counting = new CountingConsumer(MAX_PAYLOAD_LENGTH);
 		r.addConsumer(copying);
diff --git a/briar-core/src/org/briarproject/sync/PacketReaderFactoryImpl.java b/briar-core/src/org/briarproject/sync/PacketReaderFactoryImpl.java
index 19766e31a2ea0c09deeb5d95c88466ff5dcff176..ed37e602c6fb2fdf3f1c7224507473f03b988510 100644
--- a/briar-core/src/org/briarproject/sync/PacketReaderFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/PacketReaderFactoryImpl.java
@@ -1,7 +1,7 @@
 package org.briarproject.sync;
 
+import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.sync.PacketReader;
 import org.briarproject.api.sync.PacketReaderFactory;
 import org.briarproject.api.sync.SubscriptionUpdate;
@@ -13,21 +13,21 @@ import javax.inject.Inject;
 
 class PacketReaderFactoryImpl implements PacketReaderFactory {
 
-	private final ReaderFactory readerFactory;
+	private final BdfReaderFactory bdfReaderFactory;
 	private final ObjectReader<UnverifiedMessage> messageReader;
 	private final ObjectReader<SubscriptionUpdate> subscriptionUpdateReader;
 
 	@Inject
-	PacketReaderFactoryImpl(ReaderFactory readerFactory,
+	PacketReaderFactoryImpl(BdfReaderFactory bdfReaderFactory,
 			ObjectReader<UnverifiedMessage> messageReader,
 			ObjectReader<SubscriptionUpdate> subscriptionUpdateReader) {
-		this.readerFactory = readerFactory;
+		this.bdfReaderFactory = bdfReaderFactory;
 		this.messageReader = messageReader;
 		this.subscriptionUpdateReader = subscriptionUpdateReader;
 	}
 
 	public PacketReader createPacketReader(InputStream in) {
-		return new PacketReaderImpl(readerFactory, messageReader,
+		return new PacketReaderImpl(bdfReaderFactory, messageReader,
 				subscriptionUpdateReader, in);
 	}
 }
diff --git a/briar-core/src/org/briarproject/sync/PacketReaderImpl.java b/briar-core/src/org/briarproject/sync/PacketReaderImpl.java
index 9527c6897b21d5bb1ab30a1ec5351189f2349d1a..08a6632abe60168656f3178f7776c977ab0b090c 100644
--- a/briar-core/src/org/briarproject/sync/PacketReaderImpl.java
+++ b/briar-core/src/org/briarproject/sync/PacketReaderImpl.java
@@ -4,9 +4,9 @@ import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.UniqueId;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.data.Reader;
-import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.Offer;
@@ -48,7 +48,7 @@ class PacketReaderImpl implements PacketReader {
 
 	private enum State { BUFFER_EMPTY, BUFFER_FULL, EOF }
 
-	private final ReaderFactory readerFactory;
+	private final BdfReaderFactory bdfReaderFactory;
 	private final ObjectReader<UnverifiedMessage> messageReader;
 	private final ObjectReader<SubscriptionUpdate> subscriptionUpdateReader;
 	private final InputStream in;
@@ -57,11 +57,11 @@ class PacketReaderImpl implements PacketReader {
 	private State state = State.BUFFER_EMPTY;
 	private int payloadLength = 0;
 
-	PacketReaderImpl(ReaderFactory readerFactory,
+	PacketReaderImpl(BdfReaderFactory bdfReaderFactory,
 			ObjectReader<UnverifiedMessage> messageReader,
 			ObjectReader<SubscriptionUpdate> subscriptionUpdateReader,
 			InputStream in) {
-		this.readerFactory = readerFactory;
+		this.bdfReaderFactory = bdfReaderFactory;
 		this.messageReader = messageReader;
 		this.subscriptionUpdateReader = subscriptionUpdateReader;
 		this.in = in;
@@ -111,7 +111,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasAck()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read the start of the payload
 		r.readListStart();
 		// Read the message IDs
@@ -141,7 +141,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasMessage()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read and build the message
 		UnverifiedMessage m = messageReader.readObject(r);
 		if (!r.eof()) throw new FormatException();
@@ -157,7 +157,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasOffer()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read the start of the payload
 		r.readListStart();
 		// Read the message IDs
@@ -187,7 +187,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasRequest()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read the start of the payload
 		r.readListStart();
 		// Read the message IDs
@@ -217,7 +217,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasSubscriptionAck()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read the start of the payload
 		r.readListStart();
 		// Read the version
@@ -239,7 +239,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasSubscriptionUpdate()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read and build the subscription update
 		SubscriptionUpdate u = subscriptionUpdateReader.readObject(r);
 		if (!r.eof()) throw new FormatException();
@@ -255,7 +255,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasTransportAck()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read the start of the payload
 		r.readListStart();
 		// Read the transport ID and version
@@ -280,7 +280,7 @@ class PacketReaderImpl implements PacketReader {
 		if (!hasTransportUpdate()) throw new FormatException();
 		// Set up the reader
 		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		Reader r = readerFactory.createReader(bais);
+		BdfReader r = bdfReaderFactory.createReader(bais);
 		// Read the start of the payload
 		r.readListStart();
 		// Read the transport ID
@@ -289,15 +289,15 @@ class PacketReaderImpl implements PacketReader {
 		TransportId id = new TransportId(idString);
 		// Read the transport properties
 		Map<String, String> p = new HashMap<String, String>();
-		r.readMapStart();
-		for (int i = 0; !r.hasMapEnd(); i++) {
+		r.readDictionaryStart();
+		for (int i = 0; !r.hasDictionaryEnd(); 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();
+		r.readDictionaryEnd();
 		// Read the version number
 		long version = r.readInteger();
 		if (version < 0) throw new FormatException();
diff --git a/briar-core/src/org/briarproject/sync/PacketWriterFactoryImpl.java b/briar-core/src/org/briarproject/sync/PacketWriterFactoryImpl.java
index 4925e0eb2f5149cb418e7c9552043e459f139f3f..4c1b3fbb89660c943cc3a6f8dada5c62dcc37585 100644
--- a/briar-core/src/org/briarproject/sync/PacketWriterFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/PacketWriterFactoryImpl.java
@@ -1,6 +1,6 @@
 package org.briarproject.sync;
 
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.sync.PacketWriter;
 import org.briarproject.api.sync.PacketWriterFactory;
 
@@ -10,14 +10,14 @@ import javax.inject.Inject;
 
 class PacketWriterFactoryImpl implements PacketWriterFactory {
 
-	private final WriterFactory writerFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 
 	@Inject
-	PacketWriterFactoryImpl(WriterFactory writerFactory) {
-		this.writerFactory = writerFactory;
+	PacketWriterFactoryImpl(BdfWriterFactory bdfWriterFactory) {
+		this.bdfWriterFactory = bdfWriterFactory;
 	}
 
 	public PacketWriter createPacketWriter(OutputStream out) {
-		return new PacketWriterImpl(writerFactory, out);
+		return new PacketWriterImpl(bdfWriterFactory, out);
 	}
 }
diff --git a/briar-core/src/org/briarproject/sync/PacketWriterImpl.java b/briar-core/src/org/briarproject/sync/PacketWriterImpl.java
index b30f046155b754aa1387a602093454edfa0ceacf..9c734a8eb228ffd7616bbffcda7c4ea382ff6bc7 100644
--- a/briar-core/src/org/briarproject/sync/PacketWriterImpl.java
+++ b/briar-core/src/org/briarproject/sync/PacketWriterImpl.java
@@ -1,7 +1,7 @@
 package org.briarproject.sync;
 
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.MessageId;
@@ -36,13 +36,13 @@ import static org.briarproject.api.sync.PacketTypes.TRANSPORT_UPDATE;
 // This class is not thread-safe
 class PacketWriterImpl implements PacketWriter {
 
-	private final WriterFactory writerFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 	private final OutputStream out;
 	private final byte[] header;
 	private final ByteArrayOutputStream payload;
 
-	PacketWriterImpl(WriterFactory writerFactory, OutputStream out) {
-		this.writerFactory = writerFactory;
+	PacketWriterImpl(BdfWriterFactory bdfWriterFactory, OutputStream out) {
+		this.bdfWriterFactory = bdfWriterFactory;
 		this.out = out;
 		header = new byte[HEADER_LENGTH];
 		header[0] = PROTOCOL_VERSION;
@@ -78,7 +78,7 @@ class PacketWriterImpl implements PacketWriter {
 
 	public void writeAck(Ack a) throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeListStart();
 		for (MessageId m : a.getMessageIds()) w.writeRaw(m.getBytes());
@@ -96,7 +96,7 @@ class PacketWriterImpl implements PacketWriter {
 
 	public void writeOffer(Offer o) throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeListStart();
 		for (MessageId m : o.getMessageIds()) w.writeRaw(m.getBytes());
@@ -107,7 +107,7 @@ class PacketWriterImpl implements PacketWriter {
 
 	public void writeRequest(Request r) throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeListStart();
 		for (MessageId m : r.getMessageIds()) w.writeRaw(m.getBytes());
@@ -118,7 +118,7 @@ class PacketWriterImpl implements PacketWriter {
 
 	public void writeSubscriptionAck(SubscriptionAck a) throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeInteger(a.getVersion());
 		w.writeListEnd();
@@ -128,7 +128,7 @@ class PacketWriterImpl implements PacketWriter {
 	public void writeSubscriptionUpdate(SubscriptionUpdate u)
 			throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeListStart();
 		for (Group g : u.getGroups()) {
@@ -145,7 +145,7 @@ class PacketWriterImpl implements PacketWriter {
 
 	public void writeTransportAck(TransportAck a) throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeString(a.getId().getString());
 		w.writeInteger(a.getVersion());
@@ -155,10 +155,10 @@ class PacketWriterImpl implements PacketWriter {
 
 	public void writeTransportUpdate(TransportUpdate u) throws IOException {
 		assert payload.size() == 0;
-		Writer w = writerFactory.createWriter(payload);
+		BdfWriter w = bdfWriterFactory.createWriter(payload);
 		w.writeListStart();
 		w.writeString(u.getId().getString());
-		w.writeMap(u.getProperties());
+		w.writeDictionary(u.getProperties());
 		w.writeInteger(u.getVersion());
 		w.writeListEnd();
 		writePacket(TRANSPORT_UPDATE);
diff --git a/briar-core/src/org/briarproject/sync/SubscriptionUpdateReader.java b/briar-core/src/org/briarproject/sync/SubscriptionUpdateReader.java
index 3b6b0594d4ad9c8b85797b830336cd4b83f99627..8f4545ff8c761836814c63dfa7b2ee86287a25ee 100644
--- a/briar-core/src/org/briarproject/sync/SubscriptionUpdateReader.java
+++ b/briar-core/src/org/briarproject/sync/SubscriptionUpdateReader.java
@@ -1,9 +1,9 @@
 package org.briarproject.sync;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.Consumer;
 import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.data.Reader;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.SubscriptionUpdate;
@@ -26,7 +26,7 @@ class SubscriptionUpdateReader implements ObjectReader<SubscriptionUpdate> {
 		this.groupReader = groupReader;
 	}
 
-	public SubscriptionUpdate readObject(Reader r) throws IOException {
+	public SubscriptionUpdate readObject(BdfReader r) throws IOException {
 		// Set up the reader
 		Consumer counting = new CountingConsumer(MAX_PAYLOAD_LENGTH);
 		r.addConsumer(counting);
diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java
index 0f62c62a8f09470e823964c54c8168989d7af2c0..822831ac05b347daa88b041144a6bf100a93cd04 100644
--- a/briar-core/src/org/briarproject/util/StringUtils.java
+++ b/briar-core/src/org/briarproject/util/StringUtils.java
@@ -39,6 +39,14 @@ public class StringUtils {
 		}
 	}
 
+	public static String fromUtf8(byte[] bytes, int off, int len) {
+		try {
+			return new String(bytes, off, len, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
 	/** Converts the given byte array to a hex character array. */
 	public static char[] toHexChars(byte[] bytes) {
 		char[] hex = new char[bytes.length * 2];
diff --git a/briar-tests/build.xml b/briar-tests/build.xml
index 33fcfa0e30596d11b9e2eca0e1cb162967e69157..29cc7dce912093911fd26be55c73180ad9981ca2 100644
--- a/briar-tests/build.xml
+++ b/briar-tests/build.xml
@@ -105,8 +105,10 @@
 			<test name='org.briarproject.crypto.StreamDecrypterImplTest'/>
 			<test name='org.briarproject.crypto.StreamEncrypterImplTest'/>
 			<test name='org.briarproject.crypto.XSalsa20Poly1305AuthenticatedCipherTest'/>
-			<test name='org.briarproject.data.ReaderImplTest'/>
-			<test name='org.briarproject.data.WriterImplTest'/>
+			<test name='org.briarproject.data.BdfReaderImplTest'/>
+			<test name='org.briarproject.data.BdfWriterImplTest'/>
+			<test name='org.briarproject.data.MetadataEncoderImplTest'/>
+			<test name='org.briarproject.data.MetadataParserImplTest'/>
 			<test name='org.briarproject.db.BasicH2Test'/>
 			<test name='org.briarproject.db.DatabaseComponentImplTest'/>
 			<test name='org.briarproject.db.ExponentialBackoffTest'/>
diff --git a/briar-tests/src/org/briarproject/data/ReaderImplTest.java b/briar-tests/src/org/briarproject/data/BdfReaderImplTest.java
similarity index 76%
rename from briar-tests/src/org/briarproject/data/ReaderImplTest.java
rename to briar-tests/src/org/briarproject/data/BdfReaderImplTest.java
index e5ac76f7aa7601cf438a186a36bb9535d304272d..f09665588807278e40d955ceb1625bac995c6bf1 100644
--- a/briar-tests/src/org/briarproject/data/ReaderImplTest.java
+++ b/briar-tests/src/org/briarproject/data/BdfReaderImplTest.java
@@ -14,10 +14,9 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-public class ReaderImplTest extends BriarTestCase {
+public class BdfReaderImplTest extends BriarTestCase {
 
-	private ByteArrayInputStream in = null;
-	private ReaderImpl r = null;
+	private BdfReaderImpl r = null;
 
 	@Test
 	public void testReadEmptyInput() throws Exception {
@@ -127,49 +126,6 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
-	@Test
-	public void testIntegersMustHaveMinimalLength() throws Exception {
-		// INTEGER_16 could be encoded as INTEGER_8
-		setContents("21" + "7F" + "22" + "007F");
-		assertEquals(Byte.MAX_VALUE, r.readInteger());
-		try {
-			r.readInteger();
-			fail();
-		} catch (FormatException expected) {}
-		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("22" + "7FFF" + "24" + "00007FFF");
-		assertEquals(Short.MAX_VALUE, r.readInteger());
-		try {
-			r.readInteger();
-			fail();
-		} catch (FormatException expected) {}
-		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("24" + "7FFFFFFF" + "28" + "000000007FFFFFFF");
-		assertEquals(Integer.MAX_VALUE, r.readInteger());
-		try {
-			r.readInteger();
-			fail();
-		} catch (FormatException expected) {}
-		setContents("24" + "80000000" + "28" + "FFFFFFFF80000000");
-		assertEquals(Integer.MIN_VALUE, r.readInteger());
-		try {
-			r.readInteger();
-			fail();
-		} catch (FormatException expected) {}
-	}
-
 	@Test
 	public void testReadFloat() throws Exception {
 		// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
@@ -218,7 +174,9 @@ public class ReaderImplTest extends BriarTestCase {
 		try {
 			r.readString(2);
 			fail();
-		} catch (FormatException expected) {}
+		} catch (FormatException expected) {
+			// Expected
+		}
 	}
 
 	@Test
@@ -258,7 +216,9 @@ public class ReaderImplTest extends BriarTestCase {
 		try {
 			r.readString(Byte.MAX_VALUE);
 			fail();
-		} catch (FormatException expected) {}
+		} catch (FormatException expected) {
+			// Expected
+		}
 	}
 
 	@Test
@@ -296,7 +256,9 @@ public class ReaderImplTest extends BriarTestCase {
 		try {
 			r.readString(Short.MAX_VALUE);
 			fail();
-		} catch (FormatException expected) {}
+		} catch (FormatException expected) {
+			// Expected
+		}
 	}
 
 	@Test
@@ -311,28 +273,6 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
-	@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("41" + "7F" + long8Hex + "42" + "007F" + long8Hex);
-		assertEquals(longest8, r.readString(Integer.MAX_VALUE));
-		try {
-			r.readString(Integer.MAX_VALUE);
-			fail();
-		} catch (FormatException expected) {}
-		// STRING_32 could be encoded as STRING_16
-		String longest16 = TestUtils.createRandomString(Short.MAX_VALUE);
-		String long16Hex = StringUtils.toHexString(longest16.getBytes("UTF-8"));
-		setContents("42" + "7FFF" + long16Hex + "44" + "00007FFF" + long16Hex);
-		assertEquals(longest16, r.readString(Integer.MAX_VALUE));
-		try {
-			r.readString(Integer.MAX_VALUE);
-			fail();
-		} catch (FormatException expected) {}
-	}
-
 	@Test
 	public void testReadRaw8() throws Exception {
 		byte[] longest = new byte[Byte.MAX_VALUE];
@@ -355,7 +295,9 @@ public class ReaderImplTest extends BriarTestCase {
 		try {
 			r.readRaw(2);
 			fail();
-		} catch (FormatException expected) {}
+		} catch (FormatException expected) {
+			// Expected
+		}
 	}
 
 	@Test
@@ -395,7 +337,9 @@ public class ReaderImplTest extends BriarTestCase {
 		try {
 			r.readRaw(Byte.MAX_VALUE);
 			fail();
-		} catch (FormatException expected) {}
+		} catch (FormatException expected) {
+			// Expected
+		}
 	}
 
 	@Test
@@ -433,7 +377,9 @@ public class ReaderImplTest extends BriarTestCase {
 		try {
 			r.readRaw(Short.MAX_VALUE);
 			fail();
-		} catch (FormatException expected) {}
+		} catch (FormatException expected) {
+			// Expected
+		}
 	}
 
 	@Test
@@ -448,28 +394,6 @@ public class ReaderImplTest extends BriarTestCase {
 		assertTrue(r.eof());
 	}
 
-	@Test
-	public void testRawMustHaveMinimalLength() throws Exception {
-		// RAW_16 could be encoded as RAW_8
-		byte[] longest8 = new byte[Byte.MAX_VALUE];
-		String long8Hex = StringUtils.toHexString(longest8);
-		setContents("51" + "7F" + long8Hex + "52" + "007F" + long8Hex);
-		assertArrayEquals(longest8, r.readRaw(Integer.MAX_VALUE));
-		try {
-			r.readRaw(Integer.MAX_VALUE);
-			fail();
-		} catch (FormatException expected) {}
-		// RAW_32 could be encoded as RAW_16
-		byte[] longest16 = new byte[Short.MAX_VALUE];
-		String long16Hex = StringUtils.toHexString(longest16);
-		setContents("52" + "7FFF" + long16Hex + "54" + "00007FFF" + long16Hex);
-		assertArrayEquals(longest16, r.readRaw(Integer.MAX_VALUE));
-		try {
-			r.readRaw(Integer.MAX_VALUE);
-			fail();
-		} catch (FormatException expected) {}
-	}
-
 	@Test
 	public void testReadList() throws Exception {
 		// A list containing 1, "foo", and 128
@@ -499,44 +423,45 @@ public class ReaderImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testReadMap() throws Exception {
-		// A map containing "foo" -> 123 and "bar" -> null
+	public void testReadDictionary() throws Exception {
+		// A dictionary containing "foo" -> 123 and "bar" -> null
 		setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
 				"41" + "03" + "626172" + "00" + "80");
-		r.readMapStart();
-		assertFalse(r.hasMapEnd());
+		r.readDictionaryStart();
+		assertFalse(r.hasDictionaryEnd());
 		assertEquals("foo", r.readString(1000));
-		assertFalse(r.hasMapEnd());
+		assertFalse(r.hasDictionaryEnd());
 		assertEquals(123, r.readInteger());
-		assertFalse(r.hasMapEnd());
+		assertFalse(r.hasDictionaryEnd());
 		assertEquals("bar", r.readString(1000));
-		assertFalse(r.hasMapEnd());
+		assertFalse(r.hasDictionaryEnd());
 		assertTrue(r.hasNull());
 		r.readNull();
-		assertTrue(r.hasMapEnd());
-		r.readMapEnd();
+		assertTrue(r.hasDictionaryEnd());
+		r.readDictionaryEnd();
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testSkipMap() throws Exception {
+	public void testSkipDictionary() throws Exception {
 		// A map containing "foo" -> 123 and "bar" -> null
 		setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
 				"41" + "03" + "626172" + "00" + "80");
-		r.skipMap();
+		r.skipDictionary();
 		assertTrue(r.eof());
 	}
 
 	@Test
-	public void testSkipNestedListsAndMaps() throws Exception {
-		// A list containing a map containing two empty lists
-		setContents("60" + "70" + "60" + "80" + "60" + "80" + "80" + "80");
+	public void testSkipNestedListsAndDictionaries() throws Exception {
+		// A list containing a dictionary containing "" -> an empty list
+		setContents("60" + "70" + "4100" + "60" + "80" + "80" + "80");
 		r.skipList();
 		assertTrue(r.eof());
 	}
 
 	private void setContents(String hex) {
-		in = new ByteArrayInputStream(StringUtils.fromHexString(hex));
-		r = new ReaderImpl(in);
+		ByteArrayInputStream in = new ByteArrayInputStream(
+				StringUtils.fromHexString(hex));
+		r = new BdfReaderImpl(in);
 	}
 }
diff --git a/briar-tests/src/org/briarproject/data/WriterImplTest.java b/briar-tests/src/org/briarproject/data/BdfWriterImplTest.java
similarity index 90%
rename from briar-tests/src/org/briarproject/data/WriterImplTest.java
rename to briar-tests/src/org/briarproject/data/BdfWriterImplTest.java
index cfacd0c51c87cd9b02fb34d5955cd705bce5588d..23fa9edec0590ad3b56ab4c472f54a2cd2306856 100644
--- a/briar-tests/src/org/briarproject/data/WriterImplTest.java
+++ b/briar-tests/src/org/briarproject/data/BdfWriterImplTest.java
@@ -16,15 +16,15 @@ import java.util.Map;
 
 import static org.junit.Assert.assertTrue;
 
-public class WriterImplTest extends BriarTestCase {
+public class BdfWriterImplTest extends BriarTestCase {
 
 	private ByteArrayOutputStream out = null;
-	private WriterImpl w = null;
+	private BdfWriterImpl w = null;
 
 	@Before
 	public void setUp() {
 		out = new ByteArrayOutputStream();
-		w = new WriterImpl(out);
+		w = new BdfWriterImpl(out);
 	}
 
 	@Test
@@ -165,12 +165,12 @@ public class WriterImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testWriteMap() throws IOException {
+	public void testWriteDictionary() throws IOException {
 		// Use LinkedHashMap to get predictable iteration order
 		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, keys as strings and values as integers, END tag
+		w.writeDictionary(m);
+		// DICTIONARY 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" +
@@ -191,21 +191,21 @@ public class WriterImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testWriteDelimitedMap() throws IOException {
-		w.writeMapStart();
+	public void testWriteDelimitedDictionary() throws IOException {
+		w.writeDictionaryStart();
 		w.writeString("foo");
 		w.writeInteger(123);
 		w.writeString("bar");
 		w.writeNull();
-		w.writeMapEnd();
-		// MAP tag, "foo" as string, 123 as integer, "bar" as string,
+		w.writeDictionaryEnd();
+		// DICTIONARY 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 {
+	public void testWriteNestedDictionariesAndLists() throws IOException {
 		Map<String, Object> inner = new LinkedHashMap<String, Object>();
 		inner.put("bar", new byte[0]);
 		List<Object> list = new ArrayList<Object>();
@@ -213,9 +213,9 @@ public class WriterImplTest extends BriarTestCase {
 		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
+		w.writeDictionary(outer);
+		// DICTIONARY tag, "foo" as string, LIST tag, 1 as integer,
+		// DICTIONARY 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");
diff --git a/briar-tests/src/org/briarproject/data/MetadataEncoderImplTest.java b/briar-tests/src/org/briarproject/data/MetadataEncoderImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0ab879def193ba9de00e83b6e758ef65b64c34d
--- /dev/null
+++ b/briar-tests/src/org/briarproject/data/MetadataEncoderImplTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.data;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class MetadataEncoderImplTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/data/MetadataParserImplTest.java b/briar-tests/src/org/briarproject/data/MetadataParserImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e3518fb509cc41e266048c9eef7356a29cccd61
--- /dev/null
+++ b/briar-tests/src/org/briarproject/data/MetadataParserImplTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.data;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class MetadataParserImplTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/sync/ConsumersTest.java b/briar-tests/src/org/briarproject/sync/ConsumersTest.java
index a9d4ed67ba770354205ce4a22b1ef677de7a6fb3..f8e3bfe4d7af8d219f18edd0827e1e5592dd93fe 100644
--- a/briar-tests/src/org/briarproject/sync/ConsumersTest.java
+++ b/briar-tests/src/org/briarproject/sync/ConsumersTest.java
@@ -23,8 +23,7 @@ public class ConsumersTest extends BriarTestCase {
 		messageDigest.update(data);
 		byte[] dig = messageDigest.digest();
 		// Check that feeding a DigestingConsumer generates the same digest
-		org.briarproject.sync.DigestingConsumer
-				dc = new org.briarproject.sync.DigestingConsumer(messageDigest);
+		DigestingConsumer dc = new DigestingConsumer(messageDigest);
 		dc.write(data[0]);
 		dc.write(data, 1, data.length - 2);
 		dc.write(data[data.length - 1]);
@@ -35,8 +34,7 @@ public class ConsumersTest extends BriarTestCase {
 	@Test
 	public void testCountingConsumer() throws Exception {
 		byte[] data = new byte[1234];
-		org.briarproject.sync.CountingConsumer
-				cc = new org.briarproject.sync.CountingConsumer(data.length);
+		CountingConsumer cc = new CountingConsumer(data.length);
 		cc.write(data[0]);
 		cc.write(data, 1, data.length - 2);
 		cc.write(data[data.length - 1]);
@@ -54,8 +52,7 @@ public class ConsumersTest extends BriarTestCase {
 		byte[] data = new byte[1234];
 		new Random().nextBytes(data);
 		// Check that a CopyingConsumer creates a faithful copy
-		org.briarproject.sync.CopyingConsumer
-				cc = new org.briarproject.sync.CopyingConsumer();
+		CopyingConsumer cc = new CopyingConsumer();
 		cc.write(data[0]);
 		cc.write(data, 1, data.length - 2);
 		cc.write(data[data.length - 1]);
diff --git a/briar-tests/src/org/briarproject/sync/PacketReaderImplTest.java b/briar-tests/src/org/briarproject/sync/PacketReaderImplTest.java
index f75550fdf15b613b4c2fb75e234137fa220e8c9d..314bf4bd3b625eda2671eb317c3f8947bb298461 100644
--- a/briar-tests/src/org/briarproject/sync/PacketReaderImplTest.java
+++ b/briar-tests/src/org/briarproject/sync/PacketReaderImplTest.java
@@ -6,9 +6,9 @@ import com.google.inject.Injector;
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestUtils;
 import org.briarproject.api.FormatException;
-import org.briarproject.api.data.ReaderFactory;
-import org.briarproject.api.data.Writer;
-import org.briarproject.api.data.WriterFactory;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.data.DataModule;
 import org.briarproject.util.ByteUtils;
 import org.junit.Test;
@@ -30,21 +30,20 @@ public class PacketReaderImplTest extends BriarTestCase {
 
 	// FIXME: This is an integration test, not a unit test
 
-	private final ReaderFactory readerFactory;
-	private final WriterFactory writerFactory;
+	private final BdfReaderFactory bdfReaderFactory;
+	private final BdfWriterFactory bdfWriterFactory;
 
 	public PacketReaderImplTest() throws Exception {
 		Injector i = Guice.createInjector(new DataModule());
-		readerFactory = i.getInstance(ReaderFactory.class);
-		writerFactory = i.getInstance(WriterFactory.class);
+		bdfReaderFactory = i.getInstance(BdfReaderFactory.class);
+		bdfWriterFactory = i.getInstance(BdfWriterFactory.class);
 	}
 
 	@Test
 	public void testFormatExceptionIfAckIsTooLarge() throws Exception {
 		byte[] b = createAck(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		try {
 			reader.readAck();
@@ -58,8 +57,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
 		byte[] b = createAck(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		reader.readAck();
 	}
@@ -68,8 +66,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testEmptyAck() throws Exception {
 		byte[] b = createEmptyAck();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		try {
 			reader.readAck();
@@ -83,8 +80,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
 		byte[] b = createOffer(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		try {
 			reader.readOffer();
@@ -98,8 +94,9 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
 		byte[] b = createOffer(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl
+				reader = new PacketReaderImpl(
+				bdfReaderFactory, null,
 				null, in);
 		reader.readOffer();
 	}
@@ -108,8 +105,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testEmptyOffer() throws Exception {
 		byte[] b = createEmptyOffer();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		try {
 			reader.readOffer();
@@ -123,8 +119,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
 		byte[] b = createRequest(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		try {
 			reader.readRequest();
@@ -138,8 +133,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
 		byte[] b = createRequest(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		reader.readRequest();
 	}
@@ -148,8 +142,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	public void testEmptyRequest() throws Exception {
 		byte[] b = createEmptyRequest();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		org.briarproject.sync.PacketReaderImpl
-				reader = new org.briarproject.sync.PacketReaderImpl(readerFactory, null,
+		PacketReaderImpl reader = new PacketReaderImpl(bdfReaderFactory, null,
 				null, in);
 		try {
 			reader.readRequest();
@@ -162,7 +155,7 @@ 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);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		w.writeListStart();
 		w.writeListStart();
 		while (out.size() + UNIQUE_ID_LENGTH + LIST_END_LENGTH * 2
@@ -182,7 +175,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	private byte[] createEmptyAck() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		out.write(new byte[HEADER_LENGTH]);
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		w.writeListStart();
 		w.writeListStart();
 		w.writeListEnd();
@@ -196,7 +189,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	private byte[] createOffer(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		out.write(new byte[HEADER_LENGTH]);
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		w.writeListStart();
 		w.writeListStart();
 		while (out.size() + UNIQUE_ID_LENGTH + LIST_END_LENGTH * 2
@@ -216,7 +209,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	private byte[] createEmptyOffer() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		out.write(new byte[HEADER_LENGTH]);
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		w.writeListStart();
 		w.writeListStart();
 		w.writeListEnd();
@@ -230,7 +223,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	private byte[] createRequest(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		out.write(new byte[HEADER_LENGTH]);
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		w.writeListStart();
 		w.writeListStart();
 		while (out.size() + UNIQUE_ID_LENGTH + LIST_END_LENGTH * 2
@@ -250,7 +243,7 @@ public class PacketReaderImplTest extends BriarTestCase {
 	private byte[] createEmptyRequest() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		out.write(new byte[HEADER_LENGTH]);
-		Writer w = writerFactory.createWriter(out);
+		BdfWriter w = bdfWriterFactory.createWriter(out);
 		w.writeListStart();
 		w.writeListStart();
 		w.writeListEnd();
diff --git a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
index 1848bad7fcc1d9b7f32ee4516c05f8bdcfa98b0d..3cffb9feb6d3464170fe07b265c4be4b828c1d49 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
@@ -46,8 +46,7 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 
 	@Test
 	public void testNothingToSend() throws Exception {
-		final org.briarproject.sync.SimplexOutgoingSession
-				session = new org.briarproject.sync.SimplexOutgoingSession(db,
+		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
 				dbExecutor, eventBus, contactId, transportId, maxLatency,
 				packetWriter);
 		context.checking(new Expectations() {{
@@ -87,8 +86,7 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 	public void testSomethingToSend() throws Exception {
 		final Ack ack = new Ack(Collections.singletonList(messageId));
 		final byte[] raw = new byte[1234];
-		final org.briarproject.sync.SimplexOutgoingSession
-				session = new org.briarproject.sync.SimplexOutgoingSession(db,
+		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
 				dbExecutor, eventBus, contactId, transportId, maxLatency,
 				packetWriter);
 		context.checking(new Expectations() {{