diff --git a/api/net/sf/briar/api/protocol/BundleWriter.java b/api/net/sf/briar/api/protocol/BundleWriter.java
index ca841885d82b0c6a158d7cfeced0c65f60044d7c..fff3e66d923cb526bb9ab97325f9ba1c809b78b0 100644
--- a/api/net/sf/briar/api/protocol/BundleWriter.java
+++ b/api/net/sf/briar/api/protocol/BundleWriter.java
@@ -4,6 +4,8 @@ import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.util.Map;
 
+import net.sf.briar.api.serial.Raw;
+
 /**
  * An interface for writing a bundle of acknowledgements, subscriptions,
  * transport details and batches.
@@ -18,8 +20,8 @@ public interface BundleWriter {
 			Map<String, String> transports) throws IOException,
 			GeneralSecurityException;
 
-	/** Adds a batch to the bundle and returns its identifier. */
-	BatchId addBatch(Iterable<Message> messages) throws IOException,
+	/** Adds a batch of messages to the bundle and returns its identifier. */
+	BatchId addBatch(Iterable<Raw> messages) throws IOException,
 	GeneralSecurityException;
 
 	/** Finishes writing the bundle. */
diff --git a/api/net/sf/briar/api/protocol/Header.java b/api/net/sf/briar/api/protocol/Header.java
index 3d360175307ca6962fed8a6983fe6ef80af5e78d..96a99d9f3fdb1f7f8e88502029f897a875d066f2 100644
--- a/api/net/sf/briar/api/protocol/Header.java
+++ b/api/net/sf/briar/api/protocol/Header.java
@@ -16,4 +16,7 @@ public interface Header {
 
 	/** Returns the transport details contained in the header. */
 	Map<String, String> getTransports();
+
+	/** Returns the header's timestamp. */
+	long getTimestamp();
 }
diff --git a/api/net/sf/briar/api/protocol/MessageFactory.java b/api/net/sf/briar/api/protocol/MessageFactory.java
deleted file mode 100644
index ca3af74a996df2cb2009251cd72ec5df3f6d48de..0000000000000000000000000000000000000000
--- a/api/net/sf/briar/api/protocol/MessageFactory.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.sf.briar.api.protocol;
-
-public interface MessageFactory {
-
-	Message createMessage(MessageId id, MessageId parent, GroupId group,
-			AuthorId author, long timestamp, byte[] raw);
-}
diff --git a/components/net/sf/briar/serial/RawImpl.java b/api/net/sf/briar/api/serial/RawByteArray.java
similarity index 68%
rename from components/net/sf/briar/serial/RawImpl.java
rename to api/net/sf/briar/api/serial/RawByteArray.java
index 56bd80f2fec7f08d1a3be8f65f461b63a8a36a79..50ed9e02b8d405f211212c66e25a217b81f6da4f 100644
--- a/components/net/sf/briar/serial/RawImpl.java
+++ b/api/net/sf/briar/api/serial/RawByteArray.java
@@ -1,14 +1,14 @@
-package net.sf.briar.serial;
+package net.sf.briar.api.serial;
 
 import java.util.Arrays;
 
-import net.sf.briar.api.serial.Raw;
 
-class RawImpl implements Raw {
+/** A byte array wrapped in the Raw interface. */
+public class RawByteArray implements Raw {
 
 	private final byte[] bytes;
 
-	RawImpl(byte[] bytes) {
+	public RawByteArray(byte[] bytes) {
 		this.bytes = bytes;
 	}
 
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index 46e944a70226381bad9a24e2a8dc373d6a3c5835..d3bd03cf65240873397cc9c683b32444dffb8786 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -158,11 +158,11 @@ interface Database<T> {
 	Set<BatchId> getLostBatches(T txn, ContactId c) throws DbException;
 
 	/**
-	 * Returns the message identified by the given ID.
+	 * Returns the message identified by the given ID, in raw format.
 	 * <p>
 	 * Locking: messages read.
 	 */
-	Message getMessage(T txn, MessageId m) throws DbException;
+	byte[] getMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the IDs of all messages signed by the given author.
diff --git a/components/net/sf/briar/db/DatabaseCleanerImpl.java b/components/net/sf/briar/db/DatabaseCleanerImpl.java
index c3f52cd38267b38ffb45a95bb0ef05c3b995f972..bd9dfc7f591c1ce81e2b478530be72ea86e3289a 100644
--- a/components/net/sf/briar/db/DatabaseCleanerImpl.java
+++ b/components/net/sf/briar/db/DatabaseCleanerImpl.java
@@ -4,7 +4,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import com.google.inject.Inject;
 
-public class DatabaseCleanerImpl implements DatabaseCleaner, Runnable {
+class DatabaseCleanerImpl implements DatabaseCleaner, Runnable {
 
 	private final Callback db;
 	private final int msBetweenSweeps;
diff --git a/components/net/sf/briar/db/H2Database.java b/components/net/sf/briar/db/H2Database.java
index 6737fb5b8c7893dd9eb98b8d52ae19813b344ab8..34e6647a2088b5ece6c7058455abae7f40759895 100644
--- a/components/net/sf/briar/db/H2Database.java
+++ b/components/net/sf/briar/db/H2Database.java
@@ -12,7 +12,6 @@ import java.util.logging.Logger;
 import net.sf.briar.api.crypto.Password;
 import net.sf.briar.api.db.DatabasePassword;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.protocol.MessageFactory;
 
 import com.google.inject.Inject;
 
@@ -28,9 +27,8 @@ class H2Database extends JdbcDatabase {
 	private final long maxSize;
 
 	@Inject
-	H2Database(File dir, MessageFactory messageFactory,
-			@DatabasePassword Password password, long maxSize) {
-		super(messageFactory, "BINARY(32)", "BIGINT");
+	H2Database(File dir, @DatabasePassword Password password, long maxSize) {
+		super("BINARY(32)", "BIGINT");
 		home = new File(dir, "db");
 		this.password = password;
 		url = "jdbc:h2:split:" + home.getPath()
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index adbc6a6c3101b8433888455ebe390fe14d8657b6..8e2fbe69ee441a1e47c7421f25a467aa57a05ee8 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -27,7 +27,6 @@ import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
-import net.sf.briar.api.protocol.MessageFactory;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.util.FileUtils;
 
@@ -157,7 +156,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final Logger LOG =
 		Logger.getLogger(JdbcDatabase.class.getName());
 
-	private final MessageFactory messageFactory;
 	// Different database libraries use different names for certain types
 	private final String hashType, bigIntType;
 	private final LinkedList<Connection> connections =
@@ -168,9 +166,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	protected abstract Connection createConnection() throws SQLException;
 
-	JdbcDatabase(MessageFactory messageFactory, String hashType,
-			String bigIntType) {
-		this.messageFactory = messageFactory;
+	JdbcDatabase(String hashType, String bigIntType) {
 		this.hashType = hashType;
 		this.bigIntType = bigIntType;
 	}
@@ -683,32 +679,25 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Message getMessage(Connection txn, MessageId m) throws DbException {
+	public byte[] getMessage(Connection txn, MessageId m) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql =
-				"SELECT parentId, groupId, authorId, timestamp, size, raw"
-				+ " FROM messages WHERE messageId = ?";
+			String sql = "SELECT size, raw FROM messages WHERE messageId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			rs = ps.executeQuery();
 			boolean found = rs.next();
 			assert found;
-			MessageId parent = new MessageId(rs.getBytes(1));
-			GroupId group = new GroupId(rs.getBytes(2));
-			AuthorId author = new AuthorId(rs.getBytes(3));
-			long timestamp = rs.getLong(4);
-			int size = rs.getInt(5);
-			Blob b = rs.getBlob(6);
+			int size = rs.getInt(1);
+			Blob b = rs.getBlob(2);
 			byte[] raw = b.getBytes(1, size);
 			assert raw.length == size;
 			boolean more = rs.next();
 			assert !more;
 			rs.close();
 			ps.close();
-			return messageFactory.createMessage(m, parent, group, author,
-					timestamp, raw);
+			return raw;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index a31f5c5a8ed2bb21a361882d92af7649d8181840..aac17a06a768d95ebffabe59271b21259683add7 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -25,6 +25,8 @@ import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Header;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.serial.Raw;
+import net.sf.briar.api.serial.RawByteArray;
 
 import com.google.inject.Inject;
 
@@ -282,8 +284,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				try {
 					Txn txn = db.startTransaction();
 					try {
-						long capacity = Math.min(b.getRemainingCapacity(),
-								Batch.MAX_SIZE);
+						long capacity =
+							Math.min(b.getRemainingCapacity(), Batch.MAX_SIZE);
 						Iterator<MessageId> it =
 							db.getSendableMessages(txn, c, capacity).iterator();
 						if(!it.hasNext()) {
@@ -291,12 +293,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							return false; // No more messages to send
 						}
 						sent = new HashSet<MessageId>();
-						List<Message> messages = new ArrayList<Message>();
+						List<Raw> messages = new ArrayList<Raw>();
 						while(it.hasNext()) {
 							MessageId m = it.next();
-							Message message = db.getMessage(txn, m);
-							bytesSent += message.getSize();
-							messages.add(message);
+							byte[] message = db.getMessage(txn, m);
+							bytesSent += message.length;
+							messages.add(new RawByteArray(message));
 							sent.add(m);
 						}
 						batchId = b.addBatch(messages);
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index 5e115e9d95b8e56cb8231d1244999ab34f846bb0..4e0b05289e9fad92740f9017f37645ecef6e8892 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -25,6 +25,8 @@ import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Header;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.serial.Raw;
+import net.sf.briar.api.serial.RawByteArray;
 
 import com.google.inject.Inject;
 
@@ -212,8 +214,8 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				synchronized(messageStatusLock) {
 					Txn txn = db.startTransaction();
 					try {
-						long capacity = Math.min(b.getRemainingCapacity(),
-								Batch.MAX_SIZE);
+						long capacity =
+							Math.min(b.getRemainingCapacity(), Batch.MAX_SIZE);
 						Iterator<MessageId> it =
 							db.getSendableMessages(txn, c, capacity).iterator();
 						if(!it.hasNext()) {
@@ -221,13 +223,13 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							return false; // No more messages to send
 						}
 						Set<MessageId> sent = new HashSet<MessageId>();
-						List<Message> messages = new ArrayList<Message>();
+						List<Raw> messages = new ArrayList<Raw>();
 						int bytesSent = 0;
 						while(it.hasNext()) {
 							MessageId m = it.next();
-							Message message = db.getMessage(txn, m);
-							bytesSent += message.getSize();
-							messages.add(message);
+							byte[] message = db.getMessage(txn, m);
+							bytesSent += message.length;
+							messages.add(new RawByteArray(message));
 							sent.add(m);
 						}
 						BatchId batchId = b.addBatch(messages);
diff --git a/components/net/sf/briar/i18n/FontManagerImpl.java b/components/net/sf/briar/i18n/FontManagerImpl.java
index e4877d59607f83b1a30dfd6848529bf5325c953d..8787b62f103edc3e144898f1be9c95bd27ec5141 100644
--- a/components/net/sf/briar/i18n/FontManagerImpl.java
+++ b/components/net/sf/briar/i18n/FontManagerImpl.java
@@ -20,6 +20,7 @@ import javax.swing.UIManager;
 import net.sf.briar.api.i18n.FontManager;
 import net.sf.briar.util.FileUtils;
 
+// Needs to be public for installer
 public class FontManagerImpl implements FontManager {
 
 	private static final Logger LOG =
diff --git a/components/net/sf/briar/i18n/I18nImpl.java b/components/net/sf/briar/i18n/I18nImpl.java
index cf258512a0d6bc4edc5126b8d47451a0e4c9308b..c1c9aafcedd176560450d19aee3e5142d216f343 100644
--- a/components/net/sf/briar/i18n/I18nImpl.java
+++ b/components/net/sf/briar/i18n/I18nImpl.java
@@ -21,6 +21,7 @@ import net.sf.briar.api.i18n.FontManager;
 import net.sf.briar.api.i18n.I18n;
 import net.sf.briar.util.FileUtils;
 
+// Needs to be public for installer
 public class I18nImpl implements I18n {
 
 	/**
diff --git a/components/net/sf/briar/protocol/BatchFactoryImpl.java b/components/net/sf/briar/protocol/BatchFactoryImpl.java
index 5254190d760984391c217a49a0c252bc7335c4d7..ee39fa919fcfa125d3e2ff21fa49a4fa5326d43f 100644
--- a/components/net/sf/briar/protocol/BatchFactoryImpl.java
+++ b/components/net/sf/briar/protocol/BatchFactoryImpl.java
@@ -6,7 +6,7 @@ import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.Message;
 
-public class BatchFactoryImpl implements BatchFactory {
+class BatchFactoryImpl implements BatchFactory {
 
 	public Batch createBatch(BatchId id, List<Message> messages) {
 		return new BatchImpl(id, messages);
diff --git a/components/net/sf/briar/protocol/BundleReaderImpl.java b/components/net/sf/briar/protocol/BundleReaderImpl.java
index cf99c965146b5a69a650f6b698268b415ad45afe..30dd99faa5172431d0ec3a626acfdd657f7fecbe 100644
--- a/components/net/sf/briar/protocol/BundleReaderImpl.java
+++ b/components/net/sf/briar/protocol/BundleReaderImpl.java
@@ -81,12 +81,13 @@ class BundleReaderImpl implements BundleReader {
 		}
 		Map<String, String> transports =
 			r.readMap(String.class, String.class);
+		long timestamp = r.readInt64();
 		in.setSigning(false);
 		// Read and verify the signature
 		byte[] sig = r.readRaw();
 		if(!signature.verify(sig)) throw new SignatureException();
 		// Build and return the header
-		return headerFactory.createHeader(acks, subs, transports);
+		return headerFactory.createHeader(acks, subs, transports, timestamp);
 	}
 
 	public Batch getNextBatch() throws IOException, GeneralSecurityException {
diff --git a/components/net/sf/briar/protocol/BundleWriterImpl.java b/components/net/sf/briar/protocol/BundleWriterImpl.java
index 9f9bfea8c4aaa4639e32b053cadcdcff8a30d56d..09e7d72b62160c9803c4397605481ddda0313db3 100644
--- a/components/net/sf/briar/protocol/BundleWriterImpl.java
+++ b/components/net/sf/briar/protocol/BundleWriterImpl.java
@@ -11,7 +11,7 @@ import java.util.Map;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.BundleWriter;
 import net.sf.briar.api.protocol.GroupId;
-import net.sf.briar.api.protocol.Message;
+import net.sf.briar.api.serial.Raw;
 import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
 
@@ -59,6 +59,7 @@ class BundleWriterImpl implements BundleWriter {
 		for(GroupId sub : subs) w.writeRaw(sub);
 		w.writeListEnd();
 		w.writeMap(transports);
+		w.writeInt64(System.currentTimeMillis());
 		out.setSigning(false);
 		// Create and write the signature
 		byte[] sig = signature.sign();
@@ -67,7 +68,7 @@ class BundleWriterImpl implements BundleWriter {
 		state = State.FIRST_BATCH;
 	}
 
-	public BatchId addBatch(Iterable<Message> messages) throws IOException,
+	public BatchId addBatch(Iterable<Raw> messages) throws IOException,
 	GeneralSecurityException {
 		if(state == State.FIRST_BATCH) {
 			w.writeListStart();
@@ -81,7 +82,7 @@ class BundleWriterImpl implements BundleWriter {
 		out.setDigesting(true);
 		out.setSigning(true);
 		w.writeListStart();
-		for(Message m : messages) w.writeRaw(m);
+		for(Raw message : messages) w.writeRaw(message);
 		w.writeListEnd();
 		out.setSigning(false);
 		// Create and write the signature
diff --git a/components/net/sf/briar/protocol/HeaderFactory.java b/components/net/sf/briar/protocol/HeaderFactory.java
index 9147ff08ad3e89330922518e84f4db9c2ed9433e..a48a4f6747b10d6681a95c3a7b37aea47d55dca2 100644
--- a/components/net/sf/briar/protocol/HeaderFactory.java
+++ b/components/net/sf/briar/protocol/HeaderFactory.java
@@ -10,5 +10,5 @@ import net.sf.briar.api.protocol.Header;
 interface HeaderFactory {
 
 	Header createHeader(Set<BatchId> acks, Set<GroupId> subs,
-			Map<String, String> transports);
+			Map<String, String> transports, long timestamp);
 }
diff --git a/components/net/sf/briar/protocol/HeaderFactoryImpl.java b/components/net/sf/briar/protocol/HeaderFactoryImpl.java
index 4f8287534476837bd7b62079bcab817b7584f8f1..93bd6ba2c3d700aa3768dfcb1dd998be2cf009d5 100644
--- a/components/net/sf/briar/protocol/HeaderFactoryImpl.java
+++ b/components/net/sf/briar/protocol/HeaderFactoryImpl.java
@@ -10,7 +10,7 @@ import net.sf.briar.api.protocol.Header;
 class HeaderFactoryImpl implements HeaderFactory {
 
 	public Header createHeader(Set<BatchId> acks, Set<GroupId> subs,
-			Map<String, String> transports) {
-		return new HeaderImpl(acks, subs, transports);
+			Map<String, String> transports, long timestamp) {
+		return new HeaderImpl(acks, subs, transports, timestamp);
 	}
 }
diff --git a/components/net/sf/briar/protocol/HeaderImpl.java b/components/net/sf/briar/protocol/HeaderImpl.java
index de8356dfefb139a951c6cba7cea09f387e348014..a0bb2ed35c97bc022d10c5c294824fbd22937e96 100644
--- a/components/net/sf/briar/protocol/HeaderImpl.java
+++ b/components/net/sf/briar/protocol/HeaderImpl.java
@@ -13,12 +13,14 @@ class HeaderImpl implements Header {
 	private final Set<BatchId> acks;
 	private final Set<GroupId> subs;
 	private final Map<String, String> transports;
+	private final long timestamp;
 
 	HeaderImpl(Set<BatchId> acks, Set<GroupId> subs,
-			Map<String, String> transports) {
+			Map<String, String> transports, long timestamp) {
 		this.acks = acks;
 		this.subs = subs;
 		this.transports = transports;
+		this.timestamp = timestamp;
 	}
 
 	public Set<BatchId> getAcks() {
@@ -32,4 +34,8 @@ class HeaderImpl implements Header {
 	public Map<String, String> getTransports() {
 		return transports;
 	}
+
+	public long getTimestamp() {
+		return timestamp;
+	}
 }
diff --git a/components/net/sf/briar/protocol/MessageImpl.java b/components/net/sf/briar/protocol/MessageImpl.java
index 653062c4181b231d07ac330583325c0f65fd5a49..e8b02bb7b36f170eeefd0d29eccc4a9d09a616d0 100644
--- a/components/net/sf/briar/protocol/MessageImpl.java
+++ b/components/net/sf/briar/protocol/MessageImpl.java
@@ -6,7 +6,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 
 /** A simple in-memory implementation of a message. */
-public class MessageImpl implements Message {
+class MessageImpl implements Message {
 
 	private final MessageId id, parent;
 	private final GroupId group;
diff --git a/components/net/sf/briar/serial/ReaderFactoryImpl.java b/components/net/sf/briar/serial/ReaderFactoryImpl.java
index 2f52c03e2e1161b96a8746141b83317b56e86c11..dd2a1a4da620ea50e2d6319c66e174a434a51ade 100644
--- a/components/net/sf/briar/serial/ReaderFactoryImpl.java
+++ b/components/net/sf/briar/serial/ReaderFactoryImpl.java
@@ -5,7 +5,7 @@ import java.io.InputStream;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
 
-public class ReaderFactoryImpl implements ReaderFactory {
+class ReaderFactoryImpl implements ReaderFactory {
 
 	public Reader createReader(InputStream in) {
 		return new ReaderImpl(in);
diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java
index dd9e4acf9417ecbf634a2e1745e92fca4a751489..e487ddfdbc1aa6e8bfd80fa778398423dc5eab65 100644
--- a/components/net/sf/briar/serial/ReaderImpl.java
+++ b/components/net/sf/briar/serial/ReaderImpl.java
@@ -8,6 +8,7 @@ import java.util.List;
 import java.util.Map;
 
 import net.sf.briar.api.serial.FormatException;
+import net.sf.briar.api.serial.RawByteArray;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.Tag;
 
@@ -308,7 +309,7 @@ class ReaderImpl implements Reader {
 		if(hasFloat32()) return Float.valueOf(readFloat32());
 		if(hasFloat64()) return Double.valueOf(readFloat64());
 		if(hasUtf8()) return readUtf8();
-		if(hasRaw()) return new RawImpl(readRaw());
+		if(hasRaw()) return new RawByteArray(readRaw());
 		if(hasList()) return readList();
 		if(hasMap()) return readMap();
 		if(hasNull()) {
diff --git a/components/net/sf/briar/serial/WriterFactoryImpl.java b/components/net/sf/briar/serial/WriterFactoryImpl.java
index de01ca18f4e24c05be122356b7362b25304fda18..63563fdf34666b8811806640bb1eaec0930e678e 100644
--- a/components/net/sf/briar/serial/WriterFactoryImpl.java
+++ b/components/net/sf/briar/serial/WriterFactoryImpl.java
@@ -5,7 +5,7 @@ import java.io.OutputStream;
 import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
 
-public class WriterFactoryImpl implements WriterFactory {
+class WriterFactoryImpl implements WriterFactory {
 
 	public Writer createWriter(OutputStream out) {
 		return new WriterImpl(out);
diff --git a/components/net/sf/briar/setup/SetupWorkerFactoryImpl.java b/components/net/sf/briar/setup/SetupWorkerFactoryImpl.java
index 0df98b59ad0b39389db646b940f1ce11ca8b6b6d..a69204ec070c80029588d39f529fd79ab2cb7e19 100644
--- a/components/net/sf/briar/setup/SetupWorkerFactoryImpl.java
+++ b/components/net/sf/briar/setup/SetupWorkerFactoryImpl.java
@@ -9,6 +9,7 @@ import net.sf.briar.api.setup.SetupParameters;
 import net.sf.briar.api.setup.SetupWorkerFactory;
 import net.sf.briar.util.FileUtils;
 
+// Needs to be public for installer
 public class SetupWorkerFactoryImpl implements SetupWorkerFactory {
 
 	private final I18n i18n;
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 17f92cebb4f916890c55dbf6ded339143bd6a87e..15d8e5596fe2b4bfcac374899b4580353a1b389f 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -21,7 +21,8 @@ import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Header;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
-import net.sf.briar.protocol.MessageImpl;
+import net.sf.briar.api.serial.Raw;
+import net.sf.briar.api.serial.RawByteArray;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -37,7 +38,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 	protected final MessageId messageId, parentId;
 	private final long timestamp;
 	private final int size;
-	private final byte[] body;
+	private final byte[] raw;
 	private final Message message;
 	private final Set<ContactId> contacts;
 	private final Set<BatchId> acks;
@@ -55,9 +56,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 		parentId = new MessageId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
 		size = 1234;
-		body = new byte[size];
-		message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId,
-				timestamp, body);
+		raw = new byte[size];
+		message = new TestMessage(messageId, MessageId.NONE, groupId, authorId,
+				timestamp, raw);
 		contacts = Collections.singleton(contactId);
 		acks = Collections.singleton(batchId);
 		subs = Collections.singleton(groupId);
@@ -453,6 +454,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 	@Test
 	public void testGenerateBundle() throws Exception {
 		final long headerSize = 1234L;
+		final Raw messageRaw = new RawByteArray(raw);
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
@@ -482,9 +484,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 					Batch.MAX_SIZE - headerSize);
 			will(returnValue(messages));
 			oneOf(database).getMessage(txn, messageId);
-			will(returnValue(message));
+			will(returnValue(raw));
 			// Add the batch to the bundle
-			oneOf(bundleWriter).addBatch(Collections.singletonList(message));
+			oneOf(bundleWriter).addBatch(Collections.singletonList(messageRaw));
 			will(returnValue(batchId));
 			// Record the outstanding batch
 			oneOf(database).addOutstandingBatch(
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index c830a6a2b9116b876748390ee0b4aa50163917e7..337e542a5b85255f1e0030cab74957c4ab8a459e 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -23,9 +23,7 @@ import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
-import net.sf.briar.api.protocol.MessageFactory;
 import net.sf.briar.api.protocol.MessageId;
-import net.sf.briar.protocol.MessageImpl;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -49,7 +47,7 @@ public class H2DatabaseTest extends TestCase {
 	private final MessageId messageId;
 	private final long timestamp;
 	private final int size;
-	private final byte[] body;
+	private final byte[] raw;
 	private final Message message;
 
 	public H2DatabaseTest() {
@@ -61,10 +59,10 @@ public class H2DatabaseTest extends TestCase {
 		messageId = new MessageId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
 		size = 1234;
-		body = new byte[size];
-		random.nextBytes(body);
-		message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId,
-				timestamp, body);
+		raw = new byte[size];
+		random.nextBytes(raw);
+		message = new TestMessage(messageId, MessageId.NONE, groupId, authorId,
+				timestamp, raw);
 	}
 
 	@Before
@@ -74,10 +72,8 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testPersistence() throws DbException {
-		MessageFactory messageFactory = new TestMessageFactory();
+		Database<Connection> db = open(false);
 
-		// Create a new database
-		Database<Connection> db = open(false, messageFactory);
 		// Store some records
 		Connection txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
@@ -94,7 +90,7 @@ public class H2DatabaseTest extends TestCase {
 		db.close();
 
 		// Reopen the database
-		db = open(true, messageFactory);
+		db = open(true);
 		// Check that the records are still there
 		txn = db.startTransaction();
 		assertTrue(db.containsContact(txn, contactId));
@@ -102,14 +98,8 @@ public class H2DatabaseTest extends TestCase {
 		assertEquals(Collections.singletonMap("foo", "bar"), transports);
 		assertTrue(db.containsSubscription(txn, groupId));
 		assertTrue(db.containsMessage(txn, messageId));
-		Message m1 = db.getMessage(txn, messageId);
-		assertEquals(messageId, m1.getId());
-		assertEquals(MessageId.NONE, m1.getParent());
-		assertEquals(groupId, m1.getGroup());
-		assertEquals(authorId, m1.getAuthor());
-		assertEquals(timestamp, m1.getTimestamp());
-		assertEquals(size, m1.getSize());
-		assertTrue(Arrays.equals(body, m1.getBytes()));
+		byte[] raw1 = db.getMessage(txn, messageId);
+		assertTrue(Arrays.equals(raw, raw1));
 		// Delete the records
 		db.removeContact(txn, contactId);
 		db.removeMessage(txn, messageId);
@@ -118,7 +108,7 @@ public class H2DatabaseTest extends TestCase {
 		db.close();
 
 		// Repoen the database
-		db = open(true, messageFactory);
+		db = open(true);
 		// Check that the records are gone
 		txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
@@ -134,8 +124,7 @@ public class H2DatabaseTest extends TestCase {
 		ContactId contactId1 = new ContactId(2);
 		ContactId contactId2 = new ContactId(3);
 		ContactId contactId3 = new ContactId(4);
-		MessageFactory messageFactory = new TestMessageFactory();
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Create three contacts
 		Connection txn = db.startTransaction();
@@ -156,14 +145,14 @@ public class H2DatabaseTest extends TestCase {
 		assertEquals(contactId3, db.addContact(txn, null));
 		assertTrue(db.containsContact(txn, contactId3));
 		db.commitTransaction(txn);
+
 		db.close();
 	}
 
 	@Test
 	public void testRatings() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
+
 		Connection txn = db.startTransaction();
 		// Unknown authors should be unrated
 		assertEquals(Rating.UNRATED, db.getRating(txn, authorId));
@@ -174,15 +163,13 @@ public class H2DatabaseTest extends TestCase {
 		txn = db.startTransaction();
 		assertEquals(Rating.GOOD, db.getRating(txn, authorId));
 		db.commitTransaction(txn);
+		
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testUnsubscribingRemovesMessage() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -198,14 +185,11 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testSendableMessagesMustBeSendable() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -240,14 +224,11 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testSendableMessagesMustBeNew() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -288,14 +269,11 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testSendableMessagesMustBeSubscribed() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -329,14 +307,11 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testSendableMessagesMustFitCapacity() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -363,15 +338,12 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testBatchesToAck() throws DbException {
 		BatchId batchId1 = new BatchId(TestUtils.getRandomId());
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact and some batches to ack
 		Connection txn = db.startTransaction();
@@ -395,14 +367,11 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testRemoveAckedBatch() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -438,14 +407,11 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testRemoveLostBatch() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
@@ -483,7 +449,6 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
@@ -493,9 +458,7 @@ public class H2DatabaseTest extends TestCase {
 			ids[i] = new BatchId(TestUtils.getRandomId());
 		}
 		Set<MessageId> empty = Collections.emptySet();
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact
 		Connection txn = db.startTransaction();
@@ -525,7 +488,6 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
@@ -535,9 +497,7 @@ public class H2DatabaseTest extends TestCase {
 			ids[i] = new BatchId(TestUtils.getRandomId());
 		}
 		Set<MessageId> empty = Collections.emptySet();
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact
 		Connection txn = db.startTransaction();
@@ -559,18 +519,15 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testGetMessagesByAuthor() throws DbException {
 		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
-		Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
-				authorId1, timestamp, body);
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Message message1 = new TestMessage(messageId1, MessageId.NONE, groupId,
+				authorId1, timestamp, raw);
+		Database<Connection> db = open(false);
 
 		// Subscribe to a group and store two messages
 		Connection txn = db.startTransaction();
@@ -593,7 +550,6 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
@@ -602,16 +558,14 @@ public class H2DatabaseTest extends TestCase {
 		MessageId childId2 = new MessageId(TestUtils.getRandomId());
 		MessageId childId3 = new MessageId(TestUtils.getRandomId());
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Message child1 = new MessageImpl(childId1, messageId, groupId,
-				authorId, timestamp, body);
-		Message child2 = new MessageImpl(childId2, messageId, groupId,
-				authorId, timestamp, body);
+		Message child1 = new TestMessage(childId1, messageId, groupId,
+				authorId, timestamp, raw);
+		Message child2 = new TestMessage(childId2, messageId, groupId,
+				authorId, timestamp, raw);
 		// The third child is in a different group
-		Message child3 = new MessageImpl(childId3, messageId, groupId1,
-				authorId, timestamp, body);
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Message child3 = new TestMessage(childId3, messageId, groupId1,
+				authorId, timestamp, raw);
+		Database<Connection> db = open(false);
 
 		// Subscribe to the groups and store the messages
 		Connection txn = db.startTransaction();
@@ -637,17 +591,14 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testGetOldMessages() throws DbException {
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
-		Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
-				authorId, timestamp + 1000, body);
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Message message1 = new TestMessage(messageId1, MessageId.NONE, groupId,
+				authorId, timestamp + 1000, raw);
+		Database<Connection> db = open(false);
 
 		// Subscribe to a group and store two messages
 		Connection txn = db.startTransaction();
@@ -674,18 +625,15 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testGetFreeSpace() throws DbException {
 		byte[] largeBody = new byte[ONE_MEGABYTE];
 		for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i;
-		Message message1 = new MessageImpl(messageId, MessageId.NONE, groupId,
+		Message message1 = new TestMessage(messageId, MessageId.NONE, groupId,
 				authorId, timestamp, largeBody);
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Sanity check: there should be enough space on disk for this test
 		assertTrue(testDir.getFreeSpace() > MAX_SIZE);
@@ -701,17 +649,14 @@ public class H2DatabaseTest extends TestCase {
 		assertTrue(db.getFreeSpace() < free);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testCloseWaitsForCommit() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
 		final AtomicBoolean transactionFinished = new AtomicBoolean(false);
 		final AtomicBoolean closed = new AtomicBoolean(false);
 		final AtomicBoolean error = new AtomicBoolean(false);
-		final Database<Connection> db = open(false, messageFactory);
+		final Database<Connection> db = open(false);
 
 		// Start a transaction
 		Connection txn = db.startTransaction();
@@ -746,12 +691,10 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testCloseWaitsForAbort() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
 		final AtomicBoolean transactionFinished = new AtomicBoolean(false);
 		final AtomicBoolean closed = new AtomicBoolean(false);
 		final AtomicBoolean error = new AtomicBoolean(false);
-		final Database<Connection> db = open(false, messageFactory);
+		final Database<Connection> db = open(false);
 
 		// Start a transaction
 		Connection txn = db.startTransaction();
@@ -786,9 +729,7 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testUpdateTransports() throws DbException {
-		Mockery context = new Mockery();
-		MessageFactory messageFactory = context.mock(MessageFactory.class);
-		Database<Connection> db = open(false, messageFactory);
+		Database<Connection> db = open(false);
 
 		// Add a contact with some transport details
 		Connection txn = db.startTransaction();
@@ -813,11 +754,9 @@ public class H2DatabaseTest extends TestCase {
 		db.commitTransaction(txn);
 
 		db.close();
-		context.assertIsSatisfied();
 	}
 
-	private Database<Connection> open(boolean resume,
-			MessageFactory messageFactory) throws DbException {
+	private Database<Connection> open(boolean resume) throws DbException {
 		final char[] passwordArray = passwordString.toCharArray();
 		Mockery context = new Mockery();
 		final Password password = context.mock(Password.class);
@@ -825,8 +764,7 @@ public class H2DatabaseTest extends TestCase {
 			oneOf(password).getPassword();
 			will(returnValue(passwordArray));
 		}});
-		Database<Connection> db =
-			new H2Database(testDir, messageFactory, password, MAX_SIZE);
+		Database<Connection> db = new H2Database(testDir, password, MAX_SIZE);
 		db.open(resume);
 		context.assertIsSatisfied();
 		// The password array should be cleared after use
@@ -839,12 +777,4 @@ public class H2DatabaseTest extends TestCase {
 	public void tearDown() {
 		TestUtils.deleteTestDirectory(testDir);
 	}
-
-	private static class TestMessageFactory implements MessageFactory {
-
-		public Message createMessage(MessageId id, MessageId parent,
-				GroupId group, AuthorId author, long timestamp, byte[] raw) {
-			return new MessageImpl(id, parent, group, author, timestamp, raw);
-		}
-	}
 }
diff --git a/test/net/sf/briar/db/TestMessage.java b/test/net/sf/briar/db/TestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b0b0178151552bf27a83df95263004f246c65c7
--- /dev/null
+++ b/test/net/sf/briar/db/TestMessage.java
@@ -0,0 +1,63 @@
+package net.sf.briar.db;
+
+import net.sf.briar.api.protocol.AuthorId;
+import net.sf.briar.api.protocol.GroupId;
+import net.sf.briar.api.protocol.Message;
+import net.sf.briar.api.protocol.MessageId;
+
+class TestMessage implements Message {
+
+	private final MessageId id, parent;
+	private final GroupId group;
+	private final AuthorId author;
+	private final long timestamp;
+	private final byte[] raw;
+
+	public TestMessage(MessageId id, MessageId parent, GroupId group,
+			AuthorId author, long timestamp, byte[] raw) {
+		this.id = id;
+		this.parent = parent;
+		this.group = group;
+		this.author = author;
+		this.timestamp = timestamp;
+		this.raw = raw;
+	}
+
+	public MessageId getId() {
+		return id;
+	}
+
+	public MessageId getParent() {
+		return parent;
+	}
+
+	public GroupId getGroup() {
+		return group;
+	}
+
+	public AuthorId getAuthor() {
+		return author;
+	}
+
+	public long getTimestamp() {
+		return timestamp;
+	}
+
+	public int getSize() {
+		return raw.length;
+	}
+
+	public byte[] getBytes() {
+		return raw;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return o instanceof Message && id.equals(((Message)o).getId());
+	}
+
+	@Override
+	public int hashCode() {
+		return id.hashCode();
+	}
+}
diff --git a/test/net/sf/briar/protocol/BundleReadWriteTest.java b/test/net/sf/briar/protocol/BundleReadWriteTest.java
index 5460942084622306a6ddfb9852e5a2dd11a52e21..881a88b6a7645977dbf823e54b821b2cb83c59ea 100644
--- a/test/net/sf/briar/protocol/BundleReadWriteTest.java
+++ b/test/net/sf/briar/protocol/BundleReadWriteTest.java
@@ -34,15 +34,19 @@ import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.MessageParser;
 import net.sf.briar.api.protocol.UniqueId;
+import net.sf.briar.api.serial.Raw;
+import net.sf.briar.api.serial.RawByteArray;
 import net.sf.briar.api.serial.ReaderFactory;
 import net.sf.briar.api.serial.WriterFactory;
-import net.sf.briar.serial.ReaderFactoryImpl;
-import net.sf.briar.serial.WriterFactoryImpl;
+import net.sf.briar.serial.SerialModule;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
 public class BundleReadWriteTest extends TestCase {
 
 	private static final String SIGNATURE_ALGO = "SHA256withRSA";
@@ -62,9 +66,8 @@ public class BundleReadWriteTest extends TestCase {
 	private final String nick = "Foo Bar";
 	private final String messageBody = "This is the message body! Wooooooo!";
 
-	// FIXME: This test should not depend on impls in another component
-	private final ReaderFactory rf = new ReaderFactoryImpl();
-	private final WriterFactory wf = new WriterFactoryImpl();
+	private final ReaderFactory rf;
+	private final WriterFactory wf;
 
 	private final KeyPair keyPair;
 	private final Signature sig;
@@ -74,6 +77,9 @@ public class BundleReadWriteTest extends TestCase {
 
 	public BundleReadWriteTest() throws Exception {
 		super();
+		Injector i = Guice.createInjector(new SerialModule());
+		rf = i.getInstance(ReaderFactory.class);
+		wf = i.getInstance(WriterFactory.class);
 		keyPair = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair();
 		sig = Signature.getInstance(SIGNATURE_ALGO);
 		dig = MessageDigest.getInstance(DIGEST_ALGO);
@@ -101,9 +107,10 @@ public class BundleReadWriteTest extends TestCase {
 		FileOutputStream out = new FileOutputStream(bundle);
 		BundleWriter w = new BundleWriterImpl(out, wf, keyPair.getPrivate(),
 				sig, dig, capacity);
+		Raw messageRaw = new RawByteArray(message.getBytes());
 
 		w.addHeader(acks, subs, transports);
-		w.addBatch(Collections.singleton(message));
+		w.addBatch(Collections.singleton(messageRaw));
 		w.finish();
 
 		assertTrue(bundle.exists());
diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java
index c87c00e401769f5e65a155dbf6011e4285a61e1d..3c4528a7d5a5fc459c568b26756891f3da3a2056 100644
--- a/test/net/sf/briar/serial/ReaderImplTest.java
+++ b/test/net/sf/briar/serial/ReaderImplTest.java
@@ -11,6 +11,7 @@ import java.util.Map.Entry;
 import junit.framework.TestCase;
 import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.Raw;
+import net.sf.briar.api.serial.RawByteArray;
 import net.sf.briar.util.StringUtils;
 
 import org.junit.Test;
@@ -222,7 +223,7 @@ public class ReaderImplTest extends TestCase {
 		assertNotNull(m);
 		assertEquals(2, m.size());
 		assertEquals((byte) 123, m.get("foo"));
-		Raw raw = new RawImpl(new byte[] {});
+		Raw raw = new RawByteArray(new byte[] {});
 		assertTrue(m.containsKey(raw));
 		assertNull(m.get(raw));
 		assertTrue(r.eof());
@@ -287,7 +288,7 @@ public class ReaderImplTest extends TestCase {
 		assertNotNull(m);
 		assertEquals(2, m.size());
 		assertEquals((byte) 123, m.get("foo"));
-		Raw raw = new RawImpl(new byte[] {});
+		Raw raw = new RawByteArray(new byte[] {});
 		assertTrue(m.containsKey(raw));
 		assertNull(m.get(raw));
 		assertTrue(r.eof());
diff --git a/test/net/sf/briar/serial/WriterImplTest.java b/test/net/sf/briar/serial/WriterImplTest.java
index 5d7eeec4686d7ce157ad318873179e84593c914a..111ac08d99b9634d2ba15615dda6a77d760f1210 100644
--- a/test/net/sf/briar/serial/WriterImplTest.java
+++ b/test/net/sf/briar/serial/WriterImplTest.java
@@ -9,6 +9,7 @@ import java.util.List;
 import java.util.Map;
 
 import junit.framework.TestCase;
+import net.sf.briar.api.serial.RawByteArray;
 import net.sf.briar.util.StringUtils;
 
 import org.junit.Before;
@@ -141,7 +142,7 @@ public class WriterImplTest extends TestCase {
 
 	@Test
 	public void testWriteRawObject() throws IOException {
-		w.writeRaw(new RawImpl(new byte[] {0, 1, -1, 127, -128}));
+		w.writeRaw(new RawByteArray(new byte[] {0, 1, -1, 127, -128}));
 		checkContents("F6" + "05" + "0001FF7F80");
 	}
 
@@ -160,7 +161,7 @@ public class WriterImplTest extends TestCase {
 		// Use LinkedHashMap to get predictable iteration order
 		Map<Object, Object> m = new LinkedHashMap<Object, Object>();
 		m.put("foo", Integer.valueOf(123)); // Written as a uint7
-		m.put(new RawImpl(new byte[] {}), null); // Empty array != null
+		m.put(new RawByteArray(new byte[] {}), null); // Empty array != null
 		w.writeMap(m);
 		checkContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0");
 	}
diff --git a/ui/net/sf/briar/ui/invitation/PasswordPanel.java b/ui/net/sf/briar/ui/invitation/PasswordPanel.java
index 273768c0978b87db1042a10b7c1f3c325818c777..352b0cfdaf983b5d32d766a53caa72bbb263f3ce 100644
--- a/ui/net/sf/briar/ui/invitation/PasswordPanel.java
+++ b/ui/net/sf/briar/ui/invitation/PasswordPanel.java
@@ -17,7 +17,7 @@ import net.sf.briar.api.i18n.Stri18ng;
 import net.sf.briar.ui.wizard.Wizard;
 import net.sf.briar.ui.wizard.WizardPanel;
 
-public class PasswordPanel extends WizardPanel {
+class PasswordPanel extends WizardPanel {
 
 	private static final long serialVersionUID = -1012132977732308293L;
 
diff --git a/ui/net/sf/briar/ui/setup/LocationPanel.java b/ui/net/sf/briar/ui/setup/LocationPanel.java
index e73170a5ed8c73b2348e8fc9ef3dc1269e71e824..f5912eecb0a3daaa2ecac304755c1464c925976e 100644
--- a/ui/net/sf/briar/ui/setup/LocationPanel.java
+++ b/ui/net/sf/briar/ui/setup/LocationPanel.java
@@ -4,7 +4,7 @@ import net.sf.briar.api.i18n.I18n;
 import net.sf.briar.api.i18n.Stri18ng;
 import net.sf.briar.ui.wizard.DirectoryChooserPanel;
 
-public class LocationPanel extends DirectoryChooserPanel {
+class LocationPanel extends DirectoryChooserPanel {
 
 	private static final long serialVersionUID = -8831098591612528860L;
 
diff --git a/ui/net/sf/briar/ui/setup/SetupWizard.java b/ui/net/sf/briar/ui/setup/SetupWizard.java
index 015315780cdc83068e3cc085c1d1a169f08faae5..7c8ee2c13c0cdb11a331cadc7bde41e2d078bbfb 100644
--- a/ui/net/sf/briar/ui/setup/SetupWizard.java
+++ b/ui/net/sf/briar/ui/setup/SetupWizard.java
@@ -4,7 +4,7 @@ import net.sf.briar.api.i18n.I18n;
 import net.sf.briar.api.i18n.Stri18ng;
 import net.sf.briar.ui.wizard.Wizard;
 
-public class SetupWizard extends Wizard {
+class SetupWizard extends Wizard {
 
 	private static int WIDTH = 400, HEIGHT = 300;