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..b37cfc606737a8dd8f0ba94a7f743cb09edb6e94
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/BdfDictionary.java
@@ -0,0 +1,48 @@
+package org.briarproject.api.data;
+
+import java.util.Hashtable;
+
+public class BdfDictionary extends Hashtable<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..d39588d0d667e89f22018277a4a706930a27d181
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/BdfList.java
@@ -0,0 +1,48 @@
+package org.briarproject.api.data;
+
+import java.util.Vector;
+
+public class BdfList extends Vector<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/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/db/Metadata.java b/briar-api/src/org/briarproject/api/db/Metadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..c54d15cff74f2504e77bf52d64c9c02c934ea7e7
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/db/Metadata.java
@@ -0,0 +1,6 @@
+package org.briarproject.api.db;
+
+import java.util.Hashtable;
+
+public class Metadata extends Hashtable<String, byte[]> {
+}
diff --git a/briar-core/src/org/briarproject/data/DataModule.java b/briar-core/src/org/briarproject/data/DataModule.java
index c532b833b14d7a94338a90ecbef14ac16646a177..85d7b311a0cfc508a9b215a6b651d848202615f7 100644
--- a/briar-core/src/org/briarproject/data/DataModule.java
+++ b/briar-core/src/org/briarproject/data/DataModule.java
@@ -4,6 +4,7 @@ import com.google.inject.AbstractModule;
 
 import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.BdfWriterFactory;
+import org.briarproject.api.data.MetadataParser;
 
 public class DataModule extends AbstractModule {
 
@@ -11,5 +12,6 @@ public class DataModule extends AbstractModule {
 	protected void configure() {
 		bind(BdfReaderFactory.class).to(BdfReaderFactoryImpl.class);
 		bind(BdfWriterFactory.class).to(BdfWriterFactoryImpl.class);
+		bind(MetadataParser.class).to(MetadataParserImpl.class);
 	}
 }
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..954838072c98c90489f3eb6ee088a00df70031ab
--- /dev/null
+++ b/briar-core/src/org/briarproject/data/MetadataParserImpl.java
@@ -0,0 +1,168 @@
+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 java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+
+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 (Map.Entry<String, byte[]> e : m.entrySet())
+			dict.put(e.getKey(), parseObject(e.getValue()));
+		return dict;
+	}
+
+	private Object parseObject(byte[] b) throws FormatException {
+		if (b == null) 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();
+		try {
+			return new String(b, 0, len, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			throw new RuntimeException();
+		}
+	}
+
+	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;
+	}
+}