diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index 534369d2d512c59bd214f5b3f39334836a80106a..d2b2d872e8ebd38954e273ae2a8579438638ba72 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -1,15 +1,12 @@
 package org.briarproject.forum;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfReader;
-import org.briarproject.api.data.BdfReaderFactory;
-import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumManager;
@@ -24,8 +21,6 @@ import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.util.StringUtils;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -34,18 +29,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
 import static org.briarproject.api.identity.Author.Status.UNKNOWN;
 import static org.briarproject.api.identity.Author.Status.VERIFIED;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
 
 class ForumManagerImpl implements ForumManager {
 
@@ -53,21 +42,13 @@ class ForumManagerImpl implements ForumManager {
 			"859a7be50dca035b64bd6902fb797097"
 					+ "795af837abbf8c16d750b3c2ccc186ea"));
 
-	private static final Logger LOG =
-			Logger.getLogger(ForumManagerImpl.class.getName());
-
 	private final DatabaseComponent db;
-	private final BdfReaderFactory bdfReaderFactory;
-	private final MetadataEncoder metadataEncoder;
-	private final MetadataParser metadataParser;
+	private final ClientHelper clientHelper;
 
 	@Inject
-	ForumManagerImpl(DatabaseComponent db, BdfReaderFactory bdfReaderFactory,
-			MetadataEncoder metadataEncoder, MetadataParser metadataParser) {
+	ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper) {
 		this.db = db;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.metadataEncoder = metadataEncoder;
-		this.metadataParser = metadataParser;
+		this.clientHelper = clientHelper;
 	}
 
 	@Override
@@ -78,29 +59,21 @@ class ForumManagerImpl implements ForumManager {
 	@Override
 	public void addLocalPost(ForumPost p) throws DbException {
 		try {
-			BdfDictionary d = new BdfDictionary();
-			d.put("timestamp", p.getMessage().getTimestamp());
-			if (p.getParent() != null)
-				d.put("parent", p.getParent().getBytes());
+			BdfDictionary meta = new BdfDictionary();
+			meta.put("timestamp", p.getMessage().getTimestamp());
+			if (p.getParent() != null) meta.put("parent", p.getParent());
 			if (p.getAuthor() != null) {
 				Author a = p.getAuthor();
-				BdfDictionary d1 = new BdfDictionary();
-				d1.put("id", a.getId().getBytes());
-				d1.put("name", a.getName());
-				d1.put("publicKey", a.getPublicKey());
-				d.put("author", d1);
-			}
-			d.put("contentType", p.getContentType());
-			d.put("local", true);
-			d.put("read", true);
-			Metadata meta = metadataEncoder.encode(d);
-			Transaction txn = db.startTransaction();
-			try {
-				db.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, true);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
+				BdfDictionary authorMeta = new BdfDictionary();
+				authorMeta.put("id", a.getId());
+				authorMeta.put("name", a.getName());
+				authorMeta.put("publicKey", a.getPublicKey());
+				meta.put("author", authorMeta);
 			}
+			meta.put("contentType", p.getContentType());
+			meta.put("local", true);
+			meta.put("read", true);
+			clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
 		} catch (FormatException e) {
 			throw new RuntimeException(e);
 		}
@@ -145,34 +118,11 @@ class ForumManagerImpl implements ForumManager {
 	@Override
 	public byte[] getPostBody(MessageId m) throws DbException {
 		try {
-			byte[] raw;
-			Transaction txn = db.startTransaction();
-			try {
-				raw = db.getRawMessage(txn, m);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
-			ByteArrayInputStream in = new ByteArrayInputStream(raw,
-					MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-			BdfReader r = bdfReaderFactory.createReader(in);
-			r.readListStart();
-			if (r.hasRaw()) r.skipRaw(); // Parent ID
-			else r.skipNull(); // No parent
-			if (r.hasList()) r.skipList(); // Author
-			else r.skipNull(); // No author
-			r.skipString(); // Content type
-			byte[] postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
-			if (r.hasRaw()) r.skipRaw(); // Signature
-			else r.skipNull();
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			return postBody;
+			// Parent ID, author, content type, forum post body, signature
+			BdfList message = clientHelper.getMessageAsList(m);
+			return message.getRaw(3);
 		} catch (FormatException e) {
 			throw new DbException(e);
-		} catch (IOException e) {
-			// Shouldn't happen with ByteArrayInputStream
-			throw new RuntimeException(e);
 		}
 	}
 
@@ -181,7 +131,7 @@ class ForumManagerImpl implements ForumManager {
 			throws DbException {
 		Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
 		Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
-		Map<MessageId, Metadata> metadata;
+		Map<MessageId, BdfDictionary> metadata;
 		Transaction txn = db.startTransaction();
 		try {
 			// Load the IDs of the user's identities
@@ -191,20 +141,22 @@ class ForumManagerImpl implements ForumManager {
 			for (Contact c : db.getContacts(txn))
 				contactAuthorIds.add(c.getAuthor().getId());
 			// Load the metadata
-			metadata = db.getMessageMetadata(txn, g);
+			metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
 			txn.setComplete();
+		} catch (FormatException e) {
+			throw new DbException(e);
 		} finally {
 			db.endTransaction(txn);
 		}
 		// Parse the metadata
 		Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
-		for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
+		for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
 			try {
-				BdfDictionary d = metadataParser.parse(e.getValue());
-				long timestamp = d.getLong("timestamp");
+				BdfDictionary meta = entry.getValue();
+				long timestamp = meta.getLong("timestamp");
 				Author author = null;
 				Author.Status authorStatus = ANONYMOUS;
-				BdfDictionary d1 = d.getDictionary("author", null);
+				BdfDictionary d1 = meta.getDictionary("author", null);
 				if (d1 != null) {
 					AuthorId authorId = new AuthorId(d1.getRaw("id"));
 					String name = d1.getString("name");
@@ -216,13 +168,12 @@ class ForumManagerImpl implements ForumManager {
 						authorStatus = VERIFIED;
 					else authorStatus = UNKNOWN;
 				}
-				String contentType = d.getString("contentType");
-				boolean read = d.getBoolean("read");
-				headers.add(new ForumPostHeader(e.getKey(), timestamp, author,
-						authorStatus, contentType, read));
-			} catch (FormatException ex) {
-				if (LOG.isLoggable(WARNING))
-					LOG.log(WARNING, ex.toString(), ex);
+				String contentType = meta.getString("contentType");
+				boolean read = meta.getBoolean("read");
+				headers.add(new ForumPostHeader(entry.getKey(), timestamp,
+						author, authorStatus, contentType, read));
+			} catch (FormatException e) {
+				throw new DbException(e);
 			}
 		}
 		return headers;
@@ -231,36 +182,18 @@ class ForumManagerImpl implements ForumManager {
 	@Override
 	public void setReadFlag(MessageId m, boolean read) throws DbException {
 		try {
-			BdfDictionary d = new BdfDictionary();
-			d.put("read", read);
-			Metadata meta = metadataEncoder.encode(d);
-			Transaction txn = db.startTransaction();
-			try {
-				db.mergeMessageMetadata(txn, m, meta);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
+			BdfDictionary meta = new BdfDictionary();
+			meta.put("read", read);
+			clientHelper.mergeMessageMetadata(m, meta);
 		} catch (FormatException e) {
 			throw new RuntimeException(e);
 		}
 	}
 
 	private Forum parseForum(Group g) throws FormatException {
-		ByteArrayInputStream in = new ByteArrayInputStream(g.getDescriptor());
-		BdfReader r = bdfReaderFactory.createReader(in);
-		try {
-			r.readListStart();
-			String name = r.readString(MAX_FORUM_NAME_LENGTH);
-			byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			return new Forum(g, name, salt);
-		} catch (FormatException e) {
-			throw e;
-		} catch (IOException e) {
-			// Shouldn't happen with ByteArrayInputStream
-			throw new RuntimeException(e);
-		}
+		byte[] descriptor = g.getDescriptor();
+		// Name, salt
+		BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
+		return new Forum(g, forum.getString(0), forum.getRaw(1));
 	}
 }
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index 8442edeb7d7e6ba5615e1a05b887592a1c3e37fe..4a51872e7abc05aed8f2cdbfb59a6ecda10eca7d 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -35,10 +35,8 @@ public class ForumModule {
 	@Provides
 	@Singleton
 	ForumManager provideForumManager(DatabaseComponent db,
-			ContactManager contactManager, BdfReaderFactory bdfReaderFactory,
-			MetadataEncoder metadataEncoder, MetadataParser metadataParser) {
-		return new ForumManagerImpl(db, bdfReaderFactory, metadataEncoder,
-				metadataParser);
+			ClientHelper clientHelper) {
+		return new ForumManagerImpl(db, clientHelper);
 	}
 
 	@Provides
diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
index 657126053d82c6d490733cb12d4fa9410493ea4d..c1a0eb797cad84b3130b76785e1d397bb682dc32 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -1,19 +1,16 @@
 package org.briarproject.messaging;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.PrivateGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager.AddContactHook;
 import org.briarproject.api.contact.ContactManager.RemoveContactHook;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfReader;
-import org.briarproject.api.data.BdfReaderFactory;
-import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateMessage;
@@ -25,19 +22,12 @@ import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.MessageStatus;
 import org.briarproject.util.StringUtils;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Map;
-import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-
 class MessagingManagerImpl implements MessagingManager, AddContactHook,
 		RemoveContactHook {
 
@@ -45,25 +35,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 			"6bcdc006c0910b0f44e40644c3b31f1a"
 					+ "8bf9a6d6021d40d219c86b731b903070"));
 
-	private static final Logger LOG =
-			Logger.getLogger(MessagingManagerImpl.class.getName());
-
 	private final DatabaseComponent db;
+	private final ClientHelper clientHelper;
 	private final PrivateGroupFactory privateGroupFactory;
-	private final BdfReaderFactory bdfReaderFactory;
-	private final MetadataEncoder metadataEncoder;
-	private final MetadataParser metadataParser;
 
 	@Inject
-	MessagingManagerImpl(DatabaseComponent db,
-			PrivateGroupFactory privateGroupFactory,
-			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
-			MetadataParser metadataParser) {
+	MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
+			PrivateGroupFactory privateGroupFactory) {
 		this.db = db;
+		this.clientHelper = clientHelper;
 		this.privateGroupFactory = privateGroupFactory;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.metadataEncoder = metadataEncoder;
-		this.metadataParser = metadataParser;
 	}
 
 	@Override
@@ -77,7 +58,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 			// Attach the contact ID to the group
 			BdfDictionary d = new BdfDictionary();
 			d.put("contactId", c.getId().getInt());
-			db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
+			clientHelper.mergeGroupMetadata(txn, g.getId(), d);
 		} catch (FormatException e) {
 			throw new RuntimeException(e);
 		}
@@ -100,21 +81,13 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 	@Override
 	public void addLocalMessage(PrivateMessage m) throws DbException {
 		try {
-			BdfDictionary d = new BdfDictionary();
-			d.put("timestamp", m.getMessage().getTimestamp());
-			if (m.getParent() != null)
-				d.put("parent", m.getParent().getBytes());
-			d.put("contentType", m.getContentType());
-			d.put("local", true);
-			d.put("read", true);
-			Metadata meta = metadataEncoder.encode(d);
-			Transaction txn = db.startTransaction();
-			try {
-				db.addLocalMessage(txn, m.getMessage(), CLIENT_ID, meta, true);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
+			BdfDictionary meta = new BdfDictionary();
+			meta.put("timestamp", m.getMessage().getTimestamp());
+			if (m.getParent() != null) meta.put("parent", m.getParent());
+			meta.put("contentType", m.getContentType());
+			meta.put("local", true);
+			meta.put("read", true);
+			clientHelper.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true);
 		} catch (FormatException e) {
 			throw new RuntimeException(e);
 		}
@@ -123,16 +96,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 	@Override
 	public ContactId getContactId(GroupId g) throws DbException {
 		try {
-			Metadata meta;
-			Transaction txn = db.startTransaction();
-			try {
-				meta = db.getGroupMetadata(txn, g);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
-			BdfDictionary d = metadataParser.parse(meta);
-			return new ContactId(d.getLong("contactId").intValue());
+			BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
+			return new ContactId(meta.getLong("contactId").intValue());
 		} catch (FormatException e) {
 			throw new DbException(e);
 		}
@@ -154,14 +119,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 	@Override
 	public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
 			throws DbException {
-		Map<MessageId, Metadata> metadata;
+		Map<MessageId, BdfDictionary> metadata;
 		Collection<MessageStatus> statuses;
 		Transaction txn = db.startTransaction();
 		try {
 			GroupId g = getContactGroup(db.getContact(txn, c)).getId();
-			metadata = db.getMessageMetadata(txn, g);
+			metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
 			statuses = db.getMessageStatus(txn, c, g);
 			txn.setComplete();
+		} catch (FormatException e) {
+			throw new DbException(e);
 		} finally {
 			db.endTransaction(txn);
 		}
@@ -169,18 +136,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 				new ArrayList<PrivateMessageHeader>();
 		for (MessageStatus s : statuses) {
 			MessageId id = s.getMessageId();
-			Metadata m = metadata.get(id);
-			if (m == null) continue;
+			BdfDictionary meta = metadata.get(id);
+			if (meta == null) continue;
 			try {
-				BdfDictionary d = metadataParser.parse(m);
-				long timestamp = d.getLong("timestamp");
-				String contentType = d.getString("contentType");
-				boolean local = d.getBoolean("local");
-				boolean read = d.getBoolean("read");
+				long timestamp = meta.getLong("timestamp");
+				String contentType = meta.getString("contentType");
+				boolean local = meta.getBoolean("local");
+				boolean read = meta.getBoolean("read");
 				headers.add(new PrivateMessageHeader(id, timestamp, contentType,
 						local, read, s.isSent(), s.isSeen()));
 			} catch (FormatException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				throw new DbException(e);
 			}
 		}
 		return headers;
@@ -188,47 +154,21 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 
 	@Override
 	public byte[] getMessageBody(MessageId m) throws DbException {
-		byte[] raw;
-		Transaction txn = db.startTransaction();
 		try {
-			raw = db.getRawMessage(txn, m);
-			txn.setComplete();
-		} finally {
-			db.endTransaction(txn);
-		}
-		ByteArrayInputStream in = new ByteArrayInputStream(raw,
-				MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-		BdfReader r = bdfReaderFactory.createReader(in);
-		try {
-			r.readListStart();
-			if (r.hasRaw()) r.skipRaw(); // Parent ID
-			else r.skipNull(); // No parent
-			r.skipString(); // Content type
-			byte[] messageBody = r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			return messageBody;
+			// Parent ID, content type, private message body
+			BdfList message = clientHelper.getMessageAsList(m);
+			return message.getRaw(2);
 		} catch (FormatException e) {
 			throw new DbException(e);
-		} catch (IOException e) {
-			// Shouldn't happen with ByteArrayInputStream
-			throw new RuntimeException(e);
 		}
 	}
 
 	@Override
 	public void setReadFlag(MessageId m, boolean read) throws DbException {
 		try {
-			BdfDictionary d = new BdfDictionary();
-			d.put("read", read);
-			Metadata meta = metadataEncoder.encode(d);
-			Transaction txn = db.startTransaction();
-			try {
-				db.mergeMessageMetadata(txn, m, meta);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
+			BdfDictionary meta = new BdfDictionary();
+			meta.put("read", read);
+			clientHelper.mergeMessageMetadata(m, meta);
 		} catch (FormatException e) {
 			throw new RuntimeException(e);
 		}
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
index 6bf9b5281f660432cd5c4af00fd251de9030d43a..2c90166526ae97810aca79d0e1d8d6cddb1e4e1e 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -3,21 +3,16 @@ package org.briarproject.properties;
 import org.briarproject.api.DeviceId;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.PrivateGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager.AddContactHook;
 import org.briarproject.api.contact.ContactManager.RemoveContactHook;
 import org.briarproject.api.data.BdfDictionary;
-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.data.MetadataEncoder;
-import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.NoSuchGroupException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.properties.TransportProperties;
@@ -27,14 +22,10 @@ import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
-import org.briarproject.api.sync.MessageFactory;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.system.Clock;
 import org.briarproject.util.StringUtils;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -42,9 +33,6 @@ import java.util.Map.Entry;
 
 import javax.inject.Inject;
 
-import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-
 class TransportPropertyManagerImpl implements TransportPropertyManager,
 		AddContactHook, RemoveContactHook {
 
@@ -55,28 +43,18 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
 
 	private final DatabaseComponent db;
+	private final ClientHelper clientHelper;
 	private final PrivateGroupFactory privateGroupFactory;
-	private final MessageFactory messageFactory;
-	private final BdfReaderFactory bdfReaderFactory;
-	private final BdfWriterFactory bdfWriterFactory;
-	private final MetadataEncoder metadataEncoder;
-	private final MetadataParser metadataParser;
 	private final Clock clock;
 	private final Group localGroup;
 
 	@Inject
 	TransportPropertyManagerImpl(DatabaseComponent db,
-			GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
-			MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
-			MetadataParser metadataParser, Clock clock) {
+			ClientHelper clientHelper, GroupFactory groupFactory,
+			PrivateGroupFactory privateGroupFactory, Clock clock) {
 		this.db = db;
+		this.clientHelper = clientHelper;
 		this.privateGroupFactory = privateGroupFactory;
-		this.messageFactory = messageFactory;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.bdfWriterFactory = bdfWriterFactory;
-		this.metadataEncoder = metadataEncoder;
-		this.metadataParser = metadataParser;
 		this.clock = clock;
 		localGroup = groupFactory.createGroup(CLIENT_ID,
 				LOCAL_GROUP_DESCRIPTOR);
@@ -145,8 +123,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 						true);
 				if (latest != null) {
 					// Retrieve and parse the latest local properties
-					byte[] raw = db.getRawMessage(txn, latest.messageId);
-					p = parseProperties(raw);
+					BdfList message = clientHelper.getMessageAsList(txn,
+							latest.messageId);
+					p = parseProperties(message);
 				}
 				txn.setComplete();
 			} finally {
@@ -175,8 +154,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 					LatestUpdate latest = findLatest(txn, g.getId(), t, false);
 					if (latest != null) {
 						// Retrieve and parse the latest remote properties
-						byte[] raw = db.getRawMessage(txn, latest.messageId);
-						remote.put(c.getId(), parseProperties(raw));
+						BdfList message = clientHelper.getMessageAsList(txn,
+								latest.messageId);
+						remote.put(c.getId(), parseProperties(message));
 					}
 				}
 				txn.setComplete();
@@ -206,8 +186,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 					merged = p;
 					changed = true;
 				} else {
-					byte[] raw = db.getRawMessage(txn, latest.messageId);
-					TransportProperties old = parseProperties(raw);
+					BdfList message = clientHelper.getMessageAsList(txn,
+							latest.messageId);
+					TransportProperties old = parseProperties(message);
 					merged = new TransportProperties(old);
 					merged.putAll(p);
 					changed = !merged.equals(old);
@@ -250,8 +231,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 					localGroup.getId(), true);
 			// Retrieve and parse the latest local properties
 			for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
-				byte[] raw = db.getRawMessage(txn, e.getValue().messageId);
-				local.put(e.getKey(), parseProperties(raw));
+				BdfList message = clientHelper.getMessageAsList(txn,
+						e.getValue().messageId);
+				local.put(e.getKey(), parseProperties(message));
 			}
 			return local;
 		} catch (NoSuchGroupException e) {
@@ -266,48 +248,35 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 			TransportId t, TransportProperties p, long version, boolean local,
 			boolean shared) throws DbException {
 		try {
-			byte[] body = encodeProperties(dev, t, p, version);
+			BdfList body = encodeProperties(dev, t, p, version);
 			long now = clock.currentTimeMillis();
-			Message m = messageFactory.createMessage(g, now, body);
-			BdfDictionary d = new BdfDictionary();
-			d.put("transportId", t.getString());
-			d.put("version", version);
-			d.put("local", local);
-			Metadata meta = metadataEncoder.encode(d);
-			db.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
+			Message m = clientHelper.createMessage(g, now, body);
+			BdfDictionary meta = new BdfDictionary();
+			meta.put("transportId", t.getString());
+			meta.put("version", version);
+			meta.put("local", local);
+			clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
 		} catch (FormatException e) {
 			throw new RuntimeException(e);
 		}
 	}
 
-	private byte[] encodeProperties(DeviceId dev, TransportId t,
+	private BdfList encodeProperties(DeviceId dev, TransportId t,
 			TransportProperties p, long version) {
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		BdfWriter w = bdfWriterFactory.createWriter(out);
-		try {
-			w.writeListStart();
-			w.writeRaw(dev.getBytes());
-			w.writeString(t.getString());
-			w.writeLong(version);
-			w.writeDictionary(p);
-			w.writeListEnd();
-		} catch (IOException e) {
-			// Shouldn't happen with ByteArrayOutputStream
-			throw new RuntimeException(e);
-		}
-		return out.toByteArray();
+		return BdfList.of(dev, t.getString(), version, p);
 	}
 
 	private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
 			GroupId g, boolean local) throws DbException, FormatException {
 		Map<TransportId, LatestUpdate> latestUpdates =
 				new HashMap<TransportId, LatestUpdate>();
-		Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
-		for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
-			BdfDictionary d = metadataParser.parse(e.getValue());
-			if (d.getBoolean("local") == local) {
-				TransportId t = new TransportId(d.getString("transportId"));
-				long version = d.getLong("version");
+		Map<MessageId, BdfDictionary> metadata =
+				clientHelper.getMessageMetadataAsDictionary(txn, g);
+		for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
+			BdfDictionary meta = e.getValue();
+			if (meta.getBoolean("local") == local) {
+				TransportId t = new TransportId(meta.getString("transportId"));
+				long version = meta.getLong("version");
 				LatestUpdate latest = latestUpdates.get(t);
 				if (latest == null || version > latest.version)
 					latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
@@ -319,12 +288,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
 			boolean local) throws DbException, FormatException {
 		LatestUpdate latest = null;
-		Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
-		for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
-			BdfDictionary d = metadataParser.parse(e.getValue());
-			if (d.getString("transportId").equals(t.getString())
-					&& d.getBoolean("local") == local) {
-				long version = d.getLong("version");
+		Map<MessageId, BdfDictionary> metadata =
+				clientHelper.getMessageMetadataAsDictionary(txn, g);
+		for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
+			BdfDictionary meta = e.getValue();
+			if (meta.getString("transportId").equals(t.getString())
+					&& meta.getBoolean("local") == local) {
+				long version = meta.getLong("version");
 				if (latest == null || version > latest.version)
 					latest = new LatestUpdate(e.getKey(), version);
 			}
@@ -332,33 +302,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 		return latest;
 	}
 
-	private TransportProperties parseProperties(byte[] raw)
+	private TransportProperties parseProperties(BdfList message)
 			throws FormatException {
+		// Device ID, transport ID, version, properties
+		BdfDictionary dictionary = message.getDictionary(3);
 		TransportProperties p = new TransportProperties();
-		ByteArrayInputStream in = new ByteArrayInputStream(raw,
-				MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-		BdfReader r = bdfReaderFactory.createReader(in);
-		try {
-			r.readListStart();
-			r.skipRaw(); // Device ID
-			r.skipString(); // Transport ID
-			r.skipLong(); // Version
-			r.readDictionaryStart();
-			while (!r.hasDictionaryEnd()) {
-				String key = r.readString(MAX_PROPERTY_LENGTH);
-				String value = r.readString(MAX_PROPERTY_LENGTH);
-				p.put(key, value);
-			}
-			r.readDictionaryEnd();
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			return p;
-		} catch (FormatException e) {
-			throw e;
-		} catch (IOException e) {
-			// Shouldn't happen with ByteArrayInputStream
-			throw new RuntimeException(e);
-		}
+		for (String key : dictionary.keySet())
+			p.put(key, dictionary.getString(key));
+		return p;
 	}
 
 	private static class LatestUpdate {