diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Ack.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Ack.java
index 8c56067d80b47ced69b9a26babeb12a2c521a857..1333e0f4b5c7c0aaceada1ebca45f4473ea3fe82 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Ack.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Ack.java
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
 import java.util.Collection;
 
 /**
- * A packet acknowledging receipt of one or more {@link Message Messages}.
+ * A record acknowledging receipt of one or more {@link Message Messages}.
  */
 public class Ack {
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java
index 009ef7dbd2a5f154bea4764888c182e3fc9ee372..f05a176cea642b961b0b75ab4092e9cbb57cfa37 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java
@@ -1,5 +1,7 @@
 package org.briarproject.bramble.api.sync;
 
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
+
 public class Group {
 
 	public enum Visibility {
@@ -13,6 +15,8 @@ public class Group {
 	private final byte[] descriptor;
 
 	public Group(GroupId id, ClientId clientId, byte[] descriptor) {
+		if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
+			throw new IllegalArgumentException();
 		this.id = id;
 		this.clientId = clientId;
 		this.descriptor = descriptor;
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Offer.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Offer.java
index b70df3dbaed3aa19b4ebb1f574ce17a6364120f6..4fff0608e10b6bfaadcec4ef3662c4f0444a011e 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Offer.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Offer.java
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
 import java.util.Collection;
 
 /**
- * A packet offering the recipient one or more {@link Message Messages}.
+ * A record offering the recipient one or more {@link Message Messages}.
  */
 public class Offer {
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketReader.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReader.java
similarity index 94%
rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketReader.java
rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReader.java
index 65708e0a0c030e8a9ba0980fdf4e6092c3de9558..25eadaaea13c82eba91610b1158a54ce8a7eb171 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketReader.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReader.java
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import java.io.IOException;
 
 @NotNullByDefault
-public interface PacketReader {
+public interface RecordReader {
 
 	boolean eof() throws IOException;
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketReaderFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReaderFactory.java
similarity index 64%
rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketReaderFactory.java
rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReaderFactory.java
index 9eb84fa5496603e5c89d04b91fd2563043cd3a2e..a1488491949bd0af8753c087bc121549d6c9da07 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketReaderFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordReaderFactory.java
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import java.io.InputStream;
 
 @NotNullByDefault
-public interface PacketReaderFactory {
+public interface RecordReaderFactory {
 
-	PacketReader createPacketReader(InputStream in);
+	RecordReader createRecordReader(InputStream in);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketTypes.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java
similarity index 63%
rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketTypes.java
rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java
index 537cb7e594bfa66c06f6fe28a895f83c5cd8977d..c8d08bdc4ecff744388e0f5b069b002f6d566d18 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketTypes.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordTypes.java
@@ -1,12 +1,13 @@
 package org.briarproject.bramble.api.sync;
 
 /**
- * Packet types for the sync protocol.
+ * Record types for the sync protocol.
  */
-public interface PacketTypes {
+public interface RecordTypes {
 
 	byte ACK = 0;
 	byte MESSAGE = 1;
 	byte OFFER = 2;
 	byte REQUEST = 3;
+
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriter.java
similarity index 92%
rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketWriter.java
rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriter.java
index 8741c55539a7bdb63a8614c60799a27d68ce7a53..2c6abfa789710539f30fd3b611a46c2382dd3aeb 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketWriter.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriter.java
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import java.io.IOException;
 
 @NotNullByDefault
-public interface PacketWriter {
+public interface RecordWriter {
 
 	void writeAck(Ack a) throws IOException;
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketWriterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriterFactory.java
similarity index 63%
rename from bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketWriterFactory.java
rename to bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriterFactory.java
index 9afab7d5ca8252345291f950151d9344e64ed5d9..de8606d42b8e4e5b07353cc45c7a9e92f4593f19 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/PacketWriterFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/RecordWriterFactory.java
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import java.io.OutputStream;
 
 @NotNullByDefault
-public interface PacketWriterFactory {
+public interface RecordWriterFactory {
 
-	PacketWriter createPacketWriter(OutputStream out);
+	RecordWriter createRecordWriter(OutputStream out);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Request.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Request.java
index 0f03fed252659c6bfb730e50b0d6ad1fb3b84286..9d20fdc191f7733123d9a8f9f633d012f8994aeb 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Request.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Request.java
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
 import java.util.Collection;
 
 /**
- * A packet requesting one or more {@link Message Messages} from the recipient.
+ * A record requesting one or more {@link Message Messages} from the recipient.
  */
 public class Request {
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java
index 23f22beb1ee571d81a612a15c1388b0432bc4601..e1df2a75317412c0710fb1623f63365932f6f72e 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java
@@ -10,19 +10,22 @@ public interface SyncConstants {
 	byte PROTOCOL_VERSION = 0;
 
 	/**
-	 * The length of the packet header in bytes.
+	 * The length of the record header in bytes.
 	 */
-	int PACKET_HEADER_LENGTH = 4;
+	int RECORD_HEADER_LENGTH = 4;
 
 	/**
-	 * The maximum length of the packet payload in bytes.
+	 * The maximum length of the record payload in bytes.
 	 */
-	int MAX_PACKET_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
+	int MAX_RECORD_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
+
+	/** The maximum length of a group descriptor in bytes. */
+	int MAX_GROUP_DESCRIPTOR_LENGTH = 32 * 1024; // 32 KiB
 
 	/**
 	 * The maximum length of a message in bytes.
 	 */
-	int MAX_MESSAGE_LENGTH = MAX_PACKET_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH;
+	int MAX_MESSAGE_LENGTH = MAX_RECORD_PAYLOAD_LENGTH - RECORD_HEADER_LENGTH;
 
 	/**
 	 * The length of the message header in bytes.
@@ -32,10 +35,10 @@ public interface SyncConstants {
 	/**
 	 * The maximum length of a message body in bytes.
 	 */
-	int MAX_MESSAGE_BODY_LENGTH = MAX_MESSAGE_LENGTH - MESSAGE_HEADER_LENGTH;
+	int MAX_MESSAGE_BODY_LENGTH = 32 * 1024; // 32 KiB
 
 	/**
-	 * The maximum number of message IDs in an ack, offer or request packet.
+	 * The maximum number of message IDs in an ack, offer or request record.
 	 */
-	int MAX_MESSAGE_IDS = MAX_PACKET_PAYLOAD_LENGTH / UniqueId.LENGTH;
+	int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH;
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSession.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSession.java
index 9a860d13fe3bdae42fd0f11787efb41a3e7422b4..4adafeebb8c8ad2e4dcb09cf8e454331568068e4 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSession.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSession.java
@@ -5,7 +5,7 @@ import java.io.IOException;
 public interface SyncSession {
 
 	/**
-	 * Runs the session. This method returns when there are no more packets to
+	 * Runs the session. This method returns when there are no more records to
 	 * send or receive, or when the {@link #interrupt()} method has been called.
 	 */
 	void run() throws IOException;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java
index 1c56c2d1493a68a1d2d49d48c1d4869002aea88e..3fb1c8b16e7cbad7e401d5c7ca703e58634dfcc4 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java
@@ -52,7 +52,7 @@ class KeyAgreementProtocol {
 
 		void connectionWaiting();
 
-		void initialPacketReceived();
+		void initialRecordReceived();
 	}
 
 	private final Callbacks callbacks;
@@ -117,7 +117,7 @@ class KeyAgreementProtocol {
 
 	private byte[] receiveKey() throws AbortException {
 		byte[] publicKey = transport.receiveKey();
-		callbacks.initialPacketReceived();
+		callbacks.initialRecordReceived();
 		byte[] expected = crypto.deriveKeyCommitment(publicKey);
 		if (!Arrays.equals(expected, theirPayload.getCommitment()))
 			throw new AbortException();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java
index af180b89e29ff628b7fbd401d5fe42cf90e79766..de6dcecf753b19c351413aa7dfeab9fbc11f8fe7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java
@@ -129,7 +129,7 @@ class KeyAgreementTaskImpl extends Thread implements
 	}
 
 	@Override
-	public void initialPacketReceived() {
+	public void initialRecordReceived() {
 		// We send this here instead of when we create the protocol, so that
 		// if device A makes a connection after getting device B's payload and
 		// starts its protocol, device A's UI doesn't change to prevent device B
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java
index 2044f2b65df2bc70ce138b959d56818a26334152..8fa8adcda84d3ac9b5ce95c1088d18b19ee4452d 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java
@@ -95,16 +95,34 @@ class KeyAgreementTransport {
 		out.flush();
 	}
 
-	private byte[] readRecord(byte type) throws AbortException {
+	private byte[] readRecord(byte expectedType) throws AbortException {
 		byte[] header = readHeader();
-		if (header[0] != PROTOCOL_VERSION)
-			throw new AbortException(); // TODO handle?
-		if (header[1] != type) {
-			// Unexpected packet
-			throw new AbortException(header[1] == ABORT);
-		}
 		int len = ByteUtils.readUint16(header,
 				RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
+		if (header[0] != PROTOCOL_VERSION) {
+			// ignore record with unknown protocol version and try next
+			try {
+				readData(len);
+			} catch (IOException e) {
+				throw new AbortException(e);
+			}
+			return readRecord(expectedType);
+		}
+		byte type = header[1];
+		if (type == ABORT) throw new AbortException(true);
+		if (type != expectedType) {
+			if (type != KEY && type != CONFIRM) {
+				// ignore unrecognised record and try next
+				try {
+					readData(len);
+				} catch (IOException e) {
+					throw new AbortException(e);
+				}
+				return readRecord(expectedType);
+			} else {
+				throw new AbortException(false);
+			}
+		}
 		try {
 			return readData(len);
 		} catch (IOException e) {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java
index c8b306fdaae6680ebc12bb0b3662ca4a21868f08..bd6fb0f1a7f1204b7925b529e194df017a177dbd 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java
@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.Offer;
-import org.briarproject.bramble.api.sync.PacketWriter;
+import org.briarproject.bramble.api.sync.RecordWriter;
 import org.briarproject.bramble.api.sync.Request;
 import org.briarproject.bramble.api.sync.SyncSession;
 import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
@@ -37,7 +37,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
-import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
 
 /**
  * An outgoing {@link SyncSession} suitable for duplex transports. The session
@@ -67,14 +67,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 	private final Clock clock;
 	private final ContactId contactId;
 	private final int maxLatency, maxIdleTime;
-	private final PacketWriter packetWriter;
+	private final RecordWriter recordWriter;
 	private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
 
 	private volatile boolean interrupted = false;
 
 	DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
 			EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
-			int maxIdleTime, PacketWriter packetWriter) {
+			int maxIdleTime, RecordWriter recordWriter) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
@@ -82,7 +82,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		this.contactId = contactId;
 		this.maxLatency = maxLatency;
 		this.maxIdleTime = maxIdleTime;
-		this.packetWriter = packetWriter;
+		this.recordWriter = recordWriter;
 		writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
 	}
 
@@ -109,7 +109,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 					if (wait < 0) wait = 0;
 					// Flush any unflushed data if we're going to wait
 					if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
-						packetWriter.flush();
+						recordWriter.flush();
 						dataToFlush = false;
 						nextKeepalive = now + maxIdleTime;
 					}
@@ -126,7 +126,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 						}
 						if (now >= nextKeepalive) {
 							// Flush the stream to keep it alive
-							packetWriter.flush();
+							recordWriter.flush();
 							dataToFlush = false;
 							nextKeepalive = now + maxIdleTime;
 						}
@@ -137,7 +137,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 						dataToFlush = true;
 					}
 				}
-				if (dataToFlush) packetWriter.flush();
+				if (dataToFlush) recordWriter.flush();
 			} catch (InterruptedException e) {
 				LOG.info("Interrupted while waiting for a packet to write");
 				Thread.currentThread().interrupt();
@@ -215,7 +215,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		@Override
 		public void run() throws IOException {
 			if (interrupted) return;
-			packetWriter.writeAck(ack);
+			recordWriter.writeAck(ack);
 			LOG.info("Sent ack");
 			dbExecutor.execute(new GenerateAck());
 		}
@@ -232,7 +232,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 				Transaction txn = db.startTransaction(false);
 				try {
 					b = db.generateRequestedBatch(txn, contactId,
-							MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
+							MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
 					db.commitTransaction(txn);
 				} finally {
 					db.endTransaction(txn);
@@ -259,7 +259,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		@Override
 		public void run() throws IOException {
 			if (interrupted) return;
-			for (byte[] raw : batch) packetWriter.writeMessage(raw);
+			for (byte[] raw : batch) recordWriter.writeMessage(raw);
 			LOG.info("Sent batch");
 			dbExecutor.execute(new GenerateBatch());
 		}
@@ -303,7 +303,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		@Override
 		public void run() throws IOException {
 			if (interrupted) return;
-			packetWriter.writeOffer(offer);
+			recordWriter.writeOffer(offer);
 			LOG.info("Sent offer");
 			dbExecutor.execute(new GenerateOffer());
 		}
@@ -346,7 +346,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		@Override
 		public void run() throws IOException {
 			if (interrupted) return;
-			packetWriter.writeRequest(request);
+			recordWriter.writeRequest(request);
 			LOG.info("Sent request");
 			dbExecutor.execute(new GenerateRequest());
 		}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java
index 4c64a17f50f702741058ec4e695b070d212cb9a5..e099664678308426d070eeddca9b8fb7b294fa3d 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java
@@ -1,6 +1,5 @@
 package org.briarproject.bramble.sync;
 
-import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
 import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -16,7 +15,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.Offer;
-import org.briarproject.bramble.api.sync.PacketReader;
+import org.briarproject.bramble.api.sync.RecordReader;
 import org.briarproject.bramble.api.sync.Request;
 import org.briarproject.bramble.api.sync.SyncSession;
 
@@ -42,18 +41,18 @@ class IncomingSession implements SyncSession, EventListener {
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
 	private final ContactId contactId;
-	private final PacketReader packetReader;
+	private final RecordReader recordReader;
 
 	private volatile boolean interrupted = false;
 
 	IncomingSession(DatabaseComponent db, Executor dbExecutor,
 			EventBus eventBus, ContactId contactId,
-			PacketReader packetReader) {
+			RecordReader recordReader) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.contactId = contactId;
-		this.packetReader = packetReader;
+		this.recordReader = recordReader;
 	}
 
 	@IoExecutor
@@ -62,22 +61,21 @@ class IncomingSession implements SyncSession, EventListener {
 		eventBus.addListener(this);
 		try {
 			// Read packets until interrupted or EOF
-			while (!interrupted && !packetReader.eof()) {
-				if (packetReader.hasAck()) {
-					Ack a = packetReader.readAck();
+			while (!interrupted && !recordReader.eof()) {
+				if (recordReader.hasAck()) {
+					Ack a = recordReader.readAck();
 					dbExecutor.execute(new ReceiveAck(a));
-				} else if (packetReader.hasMessage()) {
-					Message m = packetReader.readMessage();
+				} else if (recordReader.hasMessage()) {
+					Message m = recordReader.readMessage();
 					dbExecutor.execute(new ReceiveMessage(m));
-				} else if (packetReader.hasOffer()) {
-					Offer o = packetReader.readOffer();
+				} else if (recordReader.hasOffer()) {
+					Offer o = recordReader.readOffer();
 					dbExecutor.execute(new ReceiveOffer(o));
-				} else if (packetReader.hasRequest()) {
-					Request r = packetReader.readRequest();
+				} else if (recordReader.hasRequest()) {
+					Request r = recordReader.readRequest();
 					dbExecutor.execute(new ReceiveRequest(r));
-				} else {
-					throw new FormatException();
 				}
+				// unknown records are ignored
 			}
 		} finally {
 			eventBus.removeListener(this);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java
index d94eb7c4bec09ef08aaf6ae3f04701c5b1551121..7ca6824a3d2f6ecc0628dc1d392864c84bb341a1 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java
@@ -30,11 +30,15 @@ class MessageFactoryImpl implements MessageFactory {
 	public Message createMessage(GroupId g, long timestamp, byte[] body) {
 		if (body.length > MAX_MESSAGE_BODY_LENGTH)
 			throw new IllegalArgumentException();
+		byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
+		ByteUtils.writeUint64(timestamp, timeBytes, 0);
+		byte[] idHash =
+				crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
+		MessageId id = new MessageId(idHash);
 		byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
 		System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
 		ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
 		System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
-		MessageId id = new MessageId(crypto.hash(MessageId.LABEL, raw));
 		return new Message(id, g, timestamp, raw);
 	}
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketReaderFactoryImpl.java
deleted file mode 100644
index 914bbf1f51e7e3882def186f99d14604b43c3a7d..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketReaderFactoryImpl.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.briarproject.bramble.sync;
-
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.PacketReader;
-import org.briarproject.bramble.api.sync.PacketReaderFactory;
-
-import java.io.InputStream;
-
-import javax.annotation.concurrent.Immutable;
-import javax.inject.Inject;
-
-@Immutable
-@NotNullByDefault
-class PacketReaderFactoryImpl implements PacketReaderFactory {
-
-	private final CryptoComponent crypto;
-
-	@Inject
-	PacketReaderFactoryImpl(CryptoComponent crypto) {
-		this.crypto = crypto;
-	}
-
-	@Override
-	public PacketReader createPacketReader(InputStream in) {
-		return new PacketReaderImpl(crypto, in);
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketWriterFactoryImpl.java
deleted file mode 100644
index 343819390fd190b6cef20cb7f7ca65a75a2ed498..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketWriterFactoryImpl.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.briarproject.bramble.sync;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.PacketWriter;
-import org.briarproject.bramble.api.sync.PacketWriterFactory;
-
-import java.io.OutputStream;
-
-@NotNullByDefault
-class PacketWriterFactoryImpl implements PacketWriterFactory {
-
-	@Override
-	public PacketWriter createPacketWriter(OutputStream out) {
-		return new PacketWriterImpl(out);
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..43c1d262337e7603e3576e7a8d4e5add58bf6b2c
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderFactoryImpl.java
@@ -0,0 +1,32 @@
+package org.briarproject.bramble.sync;
+
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.MessageFactory;
+import org.briarproject.bramble.api.sync.RecordReader;
+import org.briarproject.bramble.api.sync.RecordReaderFactory;
+
+import java.io.InputStream;
+
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+
+@Immutable
+@NotNullByDefault
+class RecordReaderFactoryImpl implements RecordReaderFactory {
+
+	private final CryptoComponent crypto;
+	private final MessageFactory messageFactory;
+
+	@Inject
+	RecordReaderFactoryImpl(CryptoComponent crypto,
+			MessageFactory messageFactory) {
+		this.crypto = crypto;
+		this.messageFactory = messageFactory;
+	}
+
+	@Override
+	public RecordReader createRecordReader(InputStream in) {
+		return new RecordReaderImpl(messageFactory, in);
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketReaderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderImpl.java
similarity index 75%
rename from bramble-core/src/main/java/org/briarproject/bramble/sync/PacketReaderImpl.java
rename to bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderImpl.java
index 414f7e812038c5d2e8278f4533a25cc47482aa48..03e67e26d15e86ab37ff1b5e28e08f7b9a20d4d7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketReaderImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordReaderImpl.java
@@ -2,14 +2,14 @@ package org.briarproject.bramble.sync;
 
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.UniqueId;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageFactory;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.sync.Offer;
-import org.briarproject.bramble.api.sync.PacketReader;
+import org.briarproject.bramble.api.sync.RecordReader;
 import org.briarproject.bramble.api.sync.Request;
 import org.briarproject.bramble.util.ByteUtils;
 
@@ -20,41 +20,41 @@ import java.util.List;
 
 import javax.annotation.concurrent.NotThreadSafe;
 
-import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
-import static org.briarproject.bramble.api.sync.PacketTypes.MESSAGE;
-import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
-import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
-import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
+import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
+import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
+import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
+import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
+import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
 
 @NotThreadSafe
 @NotNullByDefault
-class PacketReaderImpl implements PacketReader {
+class RecordReaderImpl implements RecordReader {
 
 	private enum State { BUFFER_EMPTY, BUFFER_FULL, EOF }
 
-	private final CryptoComponent crypto;
+	private final MessageFactory messageFactory;
 	private final InputStream in;
 	private final byte[] header, payload;
 
 	private State state = State.BUFFER_EMPTY;
 	private int payloadLength = 0;
 
-	PacketReaderImpl(CryptoComponent crypto, InputStream in) {
-		this.crypto = crypto;
+	RecordReaderImpl(MessageFactory messageFactory, InputStream in) {
+		this.messageFactory = messageFactory;
 		this.in = in;
-		header = new byte[PACKET_HEADER_LENGTH];
-		payload = new byte[MAX_PACKET_PAYLOAD_LENGTH];
+		header = new byte[RECORD_HEADER_LENGTH];
+		payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
 	}
 
-	private void readPacket() throws IOException {
+	private void readRecord() throws IOException {
 		if (state != State.BUFFER_EMPTY) throw new IllegalStateException();
 		// Read the header
 		int offset = 0;
-		while (offset < PACKET_HEADER_LENGTH) {
-			int read = in.read(header, offset, PACKET_HEADER_LENGTH - offset);
+		while (offset < RECORD_HEADER_LENGTH) {
+			int read = in.read(header, offset, RECORD_HEADER_LENGTH - offset);
 			if (read == -1) {
 				if (offset > 0) throw new FormatException();
 				state = State.EOF;
@@ -66,7 +66,7 @@ class PacketReaderImpl implements PacketReader {
 		if (header[0] != PROTOCOL_VERSION) throw new FormatException();
 		// Read the payload length
 		payloadLength = ByteUtils.readUint16(header, 2);
-		if (payloadLength > MAX_PACKET_PAYLOAD_LENGTH) throw new FormatException();
+		if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH) throw new FormatException();
 		// Read the payload
 		offset = 0;
 		while (offset < payloadLength) {
@@ -79,7 +79,7 @@ class PacketReaderImpl implements PacketReader {
 
 	@Override
 	public boolean eof() throws IOException {
-		if (state == State.BUFFER_EMPTY) readPacket();
+		if (state == State.BUFFER_EMPTY) readRecord();
 		if (state == State.BUFFER_EMPTY) throw new IllegalStateException();
 		return state == State.EOF;
 	}
@@ -124,13 +124,12 @@ class PacketReaderImpl implements PacketReader {
 		// Timestamp
 		long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
 		if (timestamp < 0) throw new FormatException();
-		// Raw message
-		byte[] raw = new byte[payloadLength];
-		System.arraycopy(payload, 0, raw, 0, payloadLength);
+		// Body
+		byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH];
+		System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
+				payloadLength - MESSAGE_HEADER_LENGTH);
 		state = State.BUFFER_EMPTY;
-		// Message ID
-		MessageId messageId = new MessageId(crypto.hash(MessageId.LABEL, raw));
-		return new Message(messageId, groupId, timestamp, raw);
+		return messageFactory.createMessage(groupId, timestamp, body);
 	}
 
 	@Override
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1bcbde17f7688b3569c5734d926a9e671254d5ee
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterFactoryImpl.java
@@ -0,0 +1,16 @@
+package org.briarproject.bramble.sync;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.RecordWriter;
+import org.briarproject.bramble.api.sync.RecordWriterFactory;
+
+import java.io.OutputStream;
+
+@NotNullByDefault
+class RecordWriterFactoryImpl implements RecordWriterFactory {
+
+	@Override
+	public RecordWriter createRecordWriter(OutputStream out) {
+		return new RecordWriterImpl(out);
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketWriterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterImpl.java
similarity index 69%
rename from bramble-core/src/main/java/org/briarproject/bramble/sync/PacketWriterImpl.java
rename to bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterImpl.java
index 62e702e4157a61cc8b664ae364ed0384b60dbfb8..f7bf9f5fd8e7e557f8d844eb37ea1b4a638816c6 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/PacketWriterImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/RecordWriterImpl.java
@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.sync.Offer;
-import org.briarproject.bramble.api.sync.PacketTypes;
-import org.briarproject.bramble.api.sync.PacketWriter;
+import org.briarproject.bramble.api.sync.RecordTypes;
+import org.briarproject.bramble.api.sync.RecordWriter;
 import org.briarproject.bramble.api.sync.Request;
 import org.briarproject.bramble.util.ByteUtils;
 
@@ -15,30 +15,30 @@ import java.io.OutputStream;
 
 import javax.annotation.concurrent.NotThreadSafe;
 
-import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
-import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
-import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
-import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
-import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
+import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
+import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
+import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
+import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
 
 @NotThreadSafe
 @NotNullByDefault
-class PacketWriterImpl implements PacketWriter {
+class RecordWriterImpl implements RecordWriter {
 
 	private final OutputStream out;
 	private final byte[] header;
 	private final ByteArrayOutputStream payload;
 
-	PacketWriterImpl(OutputStream out) {
+	RecordWriterImpl(OutputStream out) {
 		this.out = out;
-		header = new byte[PACKET_HEADER_LENGTH];
+		header = new byte[RECORD_HEADER_LENGTH];
 		header[0] = PROTOCOL_VERSION;
-		payload = new ByteArrayOutputStream(MAX_PACKET_PAYLOAD_LENGTH);
+		payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
 	}
 
-	private void writePacket(byte packetType) throws IOException {
-		header[1] = packetType;
+	private void writeRecord(byte recordType) throws IOException {
+		header[1] = recordType;
 		ByteUtils.writeUint16(payload.size(), header, 2);
 		out.write(header);
 		payload.writeTo(out);
@@ -49,12 +49,12 @@ class PacketWriterImpl implements PacketWriter {
 	public void writeAck(Ack a) throws IOException {
 		if (payload.size() != 0) throw new IllegalStateException();
 		for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
-		writePacket(ACK);
+		writeRecord(ACK);
 	}
 
 	@Override
 	public void writeMessage(byte[] raw) throws IOException {
-		header[1] = PacketTypes.MESSAGE;
+		header[1] = RecordTypes.MESSAGE;
 		ByteUtils.writeUint16(raw.length, header, 2);
 		out.write(header);
 		out.write(raw);
@@ -64,14 +64,14 @@ class PacketWriterImpl implements PacketWriter {
 	public void writeOffer(Offer o) throws IOException {
 		if (payload.size() != 0) throw new IllegalStateException();
 		for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
-		writePacket(OFFER);
+		writeRecord(OFFER);
 	}
 
 	@Override
 	public void writeRequest(Request r) throws IOException {
 		if (payload.size() != 0) throw new IllegalStateException();
 		for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
-		writePacket(REQUEST);
+		writeRecord(REQUEST);
 	}
 
 	@Override
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java
index c2a1bc64e4707b9a8836699687bac46141906ece..0af9eea38bedf10f33c3a6f360f3f775980b7276 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
-import org.briarproject.bramble.api.sync.PacketWriter;
+import org.briarproject.bramble.api.sync.RecordWriter;
 import org.briarproject.bramble.api.sync.SyncSession;
 
 import java.io.IOException;
@@ -29,7 +29,7 @@ import javax.annotation.concurrent.ThreadSafe;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
-import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
 
 /**
  * An outgoing {@link SyncSession} suitable for simplex transports. The session
@@ -55,7 +55,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 	private final EventBus eventBus;
 	private final ContactId contactId;
 	private final int maxLatency;
-	private final PacketWriter packetWriter;
+	private final RecordWriter recordWriter;
 	private final AtomicInteger outstandingQueries;
 	private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
 
@@ -63,13 +63,13 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 
 	SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
 			EventBus eventBus, ContactId contactId,
-			int maxLatency, PacketWriter packetWriter) {
+			int maxLatency, RecordWriter recordWriter) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.contactId = contactId;
 		this.maxLatency = maxLatency;
-		this.packetWriter = packetWriter;
+		this.recordWriter = recordWriter;
 		outstandingQueries = new AtomicInteger(2); // One per type of packet
 		writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
 	}
@@ -89,7 +89,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 					if (task == CLOSE) break;
 					task.run();
 				}
-				packetWriter.flush();
+				recordWriter.flush();
 			} catch (InterruptedException e) {
 				LOG.info("Interrupted while waiting for a packet to write");
 				Thread.currentThread().interrupt();
@@ -157,7 +157,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 		@Override
 		public void run() throws IOException {
 			if (interrupted) return;
-			packetWriter.writeAck(ack);
+			recordWriter.writeAck(ack);
 			LOG.info("Sent ack");
 			dbExecutor.execute(new GenerateAck());
 		}
@@ -174,7 +174,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 				Transaction txn = db.startTransaction(false);
 				try {
 					b = db.generateBatch(txn, contactId,
-							MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
+							MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
 					db.commitTransaction(txn);
 				} finally {
 					db.endTransaction(txn);
@@ -202,7 +202,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 		@Override
 		public void run() throws IOException {
 			if (interrupted) return;
-			for (byte[] raw : batch) packetWriter.writeMessage(raw);
+			for (byte[] raw : batch) recordWriter.writeMessage(raw);
 			LOG.info("Sent batch");
 			dbExecutor.execute(new GenerateBatch());
 		}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java
index 44703c8b39fd4645e40aa3b06f857eeffd507c35..bb28a18eb8836779c80d9fa7189ba90c80abce24 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java
@@ -7,8 +7,8 @@ import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.sync.GroupFactory;
 import org.briarproject.bramble.api.sync.MessageFactory;
-import org.briarproject.bramble.api.sync.PacketReaderFactory;
-import org.briarproject.bramble.api.sync.PacketWriterFactory;
+import org.briarproject.bramble.api.sync.RecordReaderFactory;
+import org.briarproject.bramble.api.sync.RecordWriterFactory;
 import org.briarproject.bramble.api.sync.SyncSessionFactory;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.bramble.api.system.Clock;
@@ -40,23 +40,24 @@ public class SyncModule {
 	}
 
 	@Provides
-	PacketReaderFactory providePacketReaderFactory(CryptoComponent crypto) {
-		return new PacketReaderFactoryImpl(crypto);
+	RecordReaderFactory provideRecordReaderFactory(CryptoComponent crypto,
+			MessageFactory messageFactory) {
+		return new RecordReaderFactoryImpl(crypto, messageFactory);
 	}
 
 	@Provides
-	PacketWriterFactory providePacketWriterFactory() {
-		return new PacketWriterFactoryImpl();
+	RecordWriterFactory provideRecordWriterFactory() {
+		return new RecordWriterFactoryImpl();
 	}
 
 	@Provides
 	@Singleton
 	SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
 			@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
-			Clock clock, PacketReaderFactory packetReaderFactory,
-			PacketWriterFactory packetWriterFactory) {
+			Clock clock, RecordReaderFactory recordReaderFactory,
+			RecordWriterFactory recordWriterFactory) {
 		return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
-				packetReaderFactory, packetWriterFactory);
+				recordReaderFactory, recordWriterFactory);
 	}
 
 	@Provides
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java
index c337bf94ac3638355545a24b67a366b0d93fb127..277a21dcde1ec78aa0db2cbc8a71a950f31bdb96 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java
@@ -5,10 +5,10 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DatabaseExecutor;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.PacketReader;
-import org.briarproject.bramble.api.sync.PacketReaderFactory;
-import org.briarproject.bramble.api.sync.PacketWriter;
-import org.briarproject.bramble.api.sync.PacketWriterFactory;
+import org.briarproject.bramble.api.sync.RecordReader;
+import org.briarproject.bramble.api.sync.RecordReaderFactory;
+import org.briarproject.bramble.api.sync.RecordWriter;
+import org.briarproject.bramble.api.sync.RecordWriterFactory;
 import org.briarproject.bramble.api.sync.SyncSession;
 import org.briarproject.bramble.api.sync.SyncSessionFactory;
 import org.briarproject.bramble.api.system.Clock;
@@ -28,41 +28,41 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
 	private final Clock clock;
-	private final PacketReaderFactory packetReaderFactory;
-	private final PacketWriterFactory packetWriterFactory;
+	private final RecordReaderFactory recordReaderFactory;
+	private final RecordWriterFactory recordWriterFactory;
 
 	@Inject
 	SyncSessionFactoryImpl(DatabaseComponent db,
 			@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
-			Clock clock, PacketReaderFactory packetReaderFactory,
-			PacketWriterFactory packetWriterFactory) {
+			Clock clock, RecordReaderFactory recordReaderFactory,
+			RecordWriterFactory recordWriterFactory) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.clock = clock;
-		this.packetReaderFactory = packetReaderFactory;
-		this.packetWriterFactory = packetWriterFactory;
+		this.recordReaderFactory = recordReaderFactory;
+		this.recordWriterFactory = recordWriterFactory;
 	}
 
 	@Override
 	public SyncSession createIncomingSession(ContactId c, InputStream in) {
-		PacketReader packetReader = packetReaderFactory.createPacketReader(in);
-		return new IncomingSession(db, dbExecutor, eventBus, c, packetReader);
+		RecordReader recordReader = recordReaderFactory.createRecordReader(in);
+		return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
 	}
 
 	@Override
 	public SyncSession createSimplexOutgoingSession(ContactId c,
 			int maxLatency, OutputStream out) {
-		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
+		RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
 		return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
-				maxLatency, packetWriter);
+				maxLatency, recordWriter);
 	}
 
 	@Override
 	public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
 			int maxIdleTime, OutputStream out) {
-		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
+		RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
 		return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
-				maxLatency, maxIdleTime, packetWriter);
+				maxLatency, maxIdleTime, recordWriter);
 	}
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index 7c46cc6e59b0617a62cef606811441612fc8be19..62309e2d13179d8507d4c0cc4ee79c46f57f51c1 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -63,6 +63,7 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
 import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
@@ -95,7 +96,7 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
 	public DatabaseComponentImplTest() {
 		clientId = new ClientId(TestUtils.getRandomString(5));
 		groupId = new GroupId(TestUtils.getRandomId());
-		byte[] descriptor = new byte[0];
+		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		group = new Group(groupId, clientId, descriptor);
 		authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/H2DatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/H2DatabaseTest.java
index 3552beac7ea812f5405684894d695eabb7602a0e..7bd913489ad6522952e5dc88a21dd2d7a8d97afc 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/H2DatabaseTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/H2DatabaseTest.java
@@ -47,6 +47,7 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
@@ -84,7 +85,7 @@ public class H2DatabaseTest extends BrambleTestCase {
 	public H2DatabaseTest() throws Exception {
 		groupId = new GroupId(TestUtils.getRandomId());
 		clientId = new ClientId(TestUtils.getRandomString(5));
-		byte[] descriptor = new byte[0];
+		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		group = new Group(groupId, clientId, descriptor);
 		AuthorId authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
@@ -1316,7 +1317,7 @@ public class H2DatabaseTest extends BrambleTestCase {
 		// Add a second group
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
 		Group group1 = new Group(groupId1, clientId,
-				TestUtils.getRandomBytes(42));
+				TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
 		db.addGroup(txn, group1);
 
 		// Add a message to the second group
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java
index 25cff47783861f9cb6162d5e0248b968236a46c9..cac8923d1296bd0fa69165c0f18d932a72bdca7d 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java
@@ -92,7 +92,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
 			oneOf(callbacks).connectionWaiting();
 			oneOf(transport).receiveKey();
 			will(returnValue(BOB_PUBKEY));
-			oneOf(callbacks).initialPacketReceived();
+			oneOf(callbacks).initialRecordReceived();
 
 			// Alice verifies Bob's public key
 			oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
@@ -152,7 +152,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
 			// Bob receives Alice's public key
 			oneOf(transport).receiveKey();
 			will(returnValue(ALICE_PUBKEY));
-			oneOf(callbacks).initialPacketReceived();
+			oneOf(callbacks).initialRecordReceived();
 
 			// Bob verifies Alice's public key
 			oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
@@ -213,7 +213,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
 			oneOf(callbacks).connectionWaiting();
 			oneOf(transport).receiveKey();
 			will(returnValue(BAD_PUBKEY));
-			oneOf(callbacks).initialPacketReceived();
+			oneOf(callbacks).initialRecordReceived();
 
 			// Alice verifies Bob's public key
 			oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
@@ -250,7 +250,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
 			// Bob receives a bad public key
 			oneOf(transport).receiveKey();
 			will(returnValue(BAD_PUBKEY));
-			oneOf(callbacks).initialPacketReceived();
+			oneOf(callbacks).initialRecordReceived();
 
 			// Bob verifies Alice's public key
 			oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
@@ -296,7 +296,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
 			oneOf(callbacks).connectionWaiting();
 			oneOf(transport).receiveKey();
 			will(returnValue(BOB_PUBKEY));
-			oneOf(callbacks).initialPacketReceived();
+			oneOf(callbacks).initialRecordReceived();
 
 			// Alice verifies Bob's public key
 			oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
@@ -357,7 +357,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
 			// Bob receives Alice's public key
 			oneOf(transport).receiveKey();
 			will(returnValue(ALICE_PUBKEY));
-			oneOf(callbacks).initialPacketReceived();
+			oneOf(callbacks).initialRecordReceived();
 
 			// Bob verifies Alice's public key
 			oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/PacketReaderImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/RecordReaderImplTest.java
similarity index 56%
rename from bramble-core/src/test/java/org/briarproject/bramble/sync/PacketReaderImplTest.java
rename to bramble-core/src/test/java/org/briarproject/bramble/sync/RecordReaderImplTest.java
index 3d68494299824a783777ac8b1142596ff048fe77..8d1fca5154b4883f651c135be5a6eb19900afc21 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/sync/PacketReaderImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/RecordReaderImplTest.java
@@ -10,20 +10,20 @@ import org.junit.Test;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 
-import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
-import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
-import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
-import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
-import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
+import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
+import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
+import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
+import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
 import static org.junit.Assert.assertEquals;
 
-public class PacketReaderImplTest extends BrambleTestCase {
+public class RecordReaderImplTest extends BrambleTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testFormatExceptionIfAckIsTooLarge() throws Exception {
 		byte[] b = createAck(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readAck();
 	}
 
@@ -31,7 +31,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
 		byte[] b = createAck(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readAck();
 	}
 
@@ -39,7 +39,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testEmptyAck() throws Exception {
 		byte[] b = createEmptyAck();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readAck();
 	}
 
@@ -47,7 +47,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
 		byte[] b = createOffer(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readOffer();
 	}
 
@@ -55,7 +55,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
 		byte[] b = createOffer(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readOffer();
 	}
 
@@ -63,7 +63,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testEmptyOffer() throws Exception {
 		byte[] b = createEmptyOffer();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readOffer();
 	}
 
@@ -71,7 +71,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
 		byte[] b = createRequest(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readRequest();
 	}
 
@@ -79,7 +79,7 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
 		byte[] b = createRequest(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readRequest();
 	}
 
@@ -87,76 +87,76 @@ public class PacketReaderImplTest extends BrambleTestCase {
 	public void testEmptyRequest() throws Exception {
 		byte[] b = createEmptyRequest();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
-		PacketReaderImpl reader = new PacketReaderImpl(null, in);
+		RecordReaderImpl reader = new RecordReaderImpl(null, in);
 		reader.readRequest();
 	}
 
 	private byte[] createAck(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(new byte[PACKET_HEADER_LENGTH]);
-		while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
-				+ MAX_PACKET_PAYLOAD_LENGTH) {
+		out.write(new byte[RECORD_HEADER_LENGTH]);
+		while (out.size() + UniqueId.LENGTH <= RECORD_HEADER_LENGTH
+				+ MAX_RECORD_PAYLOAD_LENGTH) {
 			out.write(TestUtils.getRandomId());
 		}
 		if (tooBig) out.write(TestUtils.getRandomId());
-		assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
-				MAX_PACKET_PAYLOAD_LENGTH);
-		byte[] packet = out.toByteArray();
-		packet[1] = ACK;
-		ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
-		return packet;
+		assertEquals(tooBig, out.size() > RECORD_HEADER_LENGTH +
+				MAX_RECORD_PAYLOAD_LENGTH);
+		byte[] record = out.toByteArray();
+		record[1] = ACK;
+		ByteUtils.writeUint16(record.length - RECORD_HEADER_LENGTH, record, 2);
+		return record;
 	}
 
 	private byte[] createEmptyAck() throws Exception {
-		byte[] packet = new byte[PACKET_HEADER_LENGTH];
-		packet[1] = ACK;
-		ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
-		return packet;
+		byte[] record = new byte[RECORD_HEADER_LENGTH];
+		record[1] = ACK;
+		ByteUtils.writeUint16(record.length - RECORD_HEADER_LENGTH, record, 2);
+		return record;
 	}
 
 	private byte[] createOffer(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(new byte[PACKET_HEADER_LENGTH]);
-		while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
-				+ MAX_PACKET_PAYLOAD_LENGTH) {
+		out.write(new byte[RECORD_HEADER_LENGTH]);
+		while (out.size() + UniqueId.LENGTH <= RECORD_HEADER_LENGTH
+				+ MAX_RECORD_PAYLOAD_LENGTH) {
 			out.write(TestUtils.getRandomId());
 		}
 		if (tooBig) out.write(TestUtils.getRandomId());
-		assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
-				MAX_PACKET_PAYLOAD_LENGTH);
-		byte[] packet = out.toByteArray();
-		packet[1] = OFFER;
-		ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
-		return packet;
+		assertEquals(tooBig, out.size() > RECORD_HEADER_LENGTH +
+				MAX_RECORD_PAYLOAD_LENGTH);
+		byte[] record = out.toByteArray();
+		record[1] = OFFER;
+		ByteUtils.writeUint16(record.length - RECORD_HEADER_LENGTH, record, 2);
+		return record;
 	}
 
 	private byte[] createEmptyOffer() throws Exception {
-		byte[] packet = new byte[PACKET_HEADER_LENGTH];
-		packet[1] = OFFER;
-		ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
-		return packet;
+		byte[] record = new byte[RECORD_HEADER_LENGTH];
+		record[1] = OFFER;
+		ByteUtils.writeUint16(record.length - RECORD_HEADER_LENGTH, record, 2);
+		return record;
 	}
 
 	private byte[] createRequest(boolean tooBig) throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(new byte[PACKET_HEADER_LENGTH]);
-		while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
-				+ MAX_PACKET_PAYLOAD_LENGTH) {
+		out.write(new byte[RECORD_HEADER_LENGTH]);
+		while (out.size() + UniqueId.LENGTH <= RECORD_HEADER_LENGTH
+				+ MAX_RECORD_PAYLOAD_LENGTH) {
 			out.write(TestUtils.getRandomId());
 		}
 		if (tooBig) out.write(TestUtils.getRandomId());
-		assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
-				MAX_PACKET_PAYLOAD_LENGTH);
-		byte[] packet = out.toByteArray();
-		packet[1] = REQUEST;
-		ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
-		return packet;
+		assertEquals(tooBig, out.size() > RECORD_HEADER_LENGTH +
+				MAX_RECORD_PAYLOAD_LENGTH);
+		byte[] record = out.toByteArray();
+		record[1] = REQUEST;
+		ByteUtils.writeUint16(record.length - RECORD_HEADER_LENGTH, record, 2);
+		return record;
 	}
 
 	private byte[] createEmptyRequest() throws Exception {
-		byte[] packet = new byte[PACKET_HEADER_LENGTH];
-		packet[1] = REQUEST;
-		ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
-		return packet;
+		byte[] record = new byte[RECORD_HEADER_LENGTH];
+		record[1] = REQUEST;
+		ByteUtils.writeUint16(record.length - RECORD_HEADER_LENGTH, record, 2);
+		return record;
 	}
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java
index 2304b4053b1f0cbfef149ae67145522434a96a9f..a2ebedfeb9a7e115dfdfbcf2631984b577d22434 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java
@@ -6,10 +6,10 @@ import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.bramble.api.sync.PacketWriter;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.bramble.test.ImmediateExecutor;
 import org.briarproject.bramble.test.TestUtils;
+import org.briarproject.bramble.api.sync.RecordWriter;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Test;
@@ -29,14 +29,14 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 	private final ContactId contactId;
 	private final MessageId messageId;
 	private final int maxLatency;
-	private final PacketWriter packetWriter;
+	private final RecordWriter recordWriter;
 
 	public SimplexOutgoingSessionTest() {
 		context = new Mockery();
 		db = context.mock(DatabaseComponent.class);
 		dbExecutor = new ImmediateExecutor();
 		eventBus = context.mock(EventBus.class);
-		packetWriter = context.mock(PacketWriter.class);
+		recordWriter = context.mock(RecordWriter.class);
 		contactId = new ContactId(234);
 		messageId = new MessageId(TestUtils.getRandomId());
 		maxLatency = Integer.MAX_VALUE;
@@ -45,7 +45,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 	@Test
 	public void testNothingToSend() throws Exception {
 		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
-				dbExecutor, eventBus, contactId, maxLatency, packetWriter);
+				dbExecutor, eventBus, contactId, maxLatency, recordWriter);
 		final Transaction noAckTxn = new Transaction(null, false);
 		final Transaction noMsgTxn = new Transaction(null, false);
 
@@ -68,7 +68,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 			oneOf(db).commitTransaction(noMsgTxn);
 			oneOf(db).endTransaction(noMsgTxn);
 			// Flush the output stream
-			oneOf(packetWriter).flush();
+			oneOf(recordWriter).flush();
 			// Remove listener
 			oneOf(eventBus).removeListener(session);
 		}});
@@ -83,7 +83,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 		final Ack ack = new Ack(Collections.singletonList(messageId));
 		final byte[] raw = new byte[1234];
 		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
-				dbExecutor, eventBus, contactId, maxLatency, packetWriter);
+				dbExecutor, eventBus, contactId, maxLatency, recordWriter);
 		final Transaction ackTxn = new Transaction(null, false);
 		final Transaction noAckTxn = new Transaction(null, false);
 		final Transaction msgTxn = new Transaction(null, false);
@@ -99,7 +99,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 			will(returnValue(ack));
 			oneOf(db).commitTransaction(ackTxn);
 			oneOf(db).endTransaction(ackTxn);
-			oneOf(packetWriter).writeAck(ack);
+			oneOf(recordWriter).writeAck(ack);
 			// One message to send
 			oneOf(db).startTransaction(false);
 			will(returnValue(msgTxn));
@@ -108,7 +108,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 			will(returnValue(Arrays.asList(raw)));
 			oneOf(db).commitTransaction(msgTxn);
 			oneOf(db).endTransaction(msgTxn);
-			oneOf(packetWriter).writeMessage(raw);
+			oneOf(recordWriter).writeMessage(raw);
 			// No more acks
 			oneOf(db).startTransaction(false);
 			will(returnValue(noAckTxn));
@@ -125,7 +125,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
 			oneOf(db).commitTransaction(noMsgTxn);
 			oneOf(db).endTransaction(noMsgTxn);
 			// Flush the output stream
-			oneOf(packetWriter).flush();
+			oneOf(recordWriter).flush();
 			// Remove listener
 			oneOf(eventBus).removeListener(session);
 		}});
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java
index 3d895990968ed579fdcdf520c4cf0469bc9bf20e..533dfb2fab638178323b3dfe534b044a19daf663 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java
@@ -12,10 +12,10 @@ import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageFactory;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.sync.Offer;
-import org.briarproject.bramble.api.sync.PacketReader;
-import org.briarproject.bramble.api.sync.PacketReaderFactory;
-import org.briarproject.bramble.api.sync.PacketWriter;
-import org.briarproject.bramble.api.sync.PacketWriterFactory;
+import org.briarproject.bramble.api.sync.RecordReader;
+import org.briarproject.bramble.api.sync.RecordReaderFactory;
+import org.briarproject.bramble.api.sync.RecordWriter;
+import org.briarproject.bramble.api.sync.RecordWriterFactory;
 import org.briarproject.bramble.api.sync.Request;
 import org.briarproject.bramble.api.transport.StreamContext;
 import org.briarproject.bramble.api.transport.StreamReaderFactory;
@@ -33,6 +33,7 @@ import java.util.Collection;
 
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -50,9 +51,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
 	@Inject
 	StreamWriterFactory streamWriterFactory;
 	@Inject
-	PacketReaderFactory packetReaderFactory;
+	RecordReaderFactory recordReaderFactory;
 	@Inject
-	PacketWriterFactory packetWriterFactory;
+	RecordWriterFactory recordWriterFactory;
 	@Inject
 	CryptoComponent crypto;
 
@@ -77,7 +78,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
 		streamNumber = 123;
 		// Create a group
 		ClientId clientId = new ClientId(TestUtils.getRandomString(5));
-		byte[] descriptor = new byte[0];
+		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		Group group = groupFactory.createGroup(clientId, descriptor);
 		// Add two messages to the group
 		long timestamp = System.currentTimeMillis();
@@ -98,14 +99,14 @@ public class SyncIntegrationTest extends BrambleTestCase {
 				headerKey, streamNumber);
 		OutputStream streamWriter = streamWriterFactory.createStreamWriter(out,
 				ctx);
-		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
+		RecordWriter recordWriter = recordWriterFactory.createRecordWriter(
 				streamWriter);
 
-		packetWriter.writeAck(new Ack(messageIds));
-		packetWriter.writeMessage(message.getRaw());
-		packetWriter.writeMessage(message1.getRaw());
-		packetWriter.writeOffer(new Offer(messageIds));
-		packetWriter.writeRequest(new Request(messageIds));
+		recordWriter.writeAck(new Ack(messageIds));
+		recordWriter.writeMessage(message.getRaw());
+		recordWriter.writeMessage(message1.getRaw());
+		recordWriter.writeOffer(new Offer(messageIds));
+		recordWriter.writeRequest(new Request(messageIds));
 
 		streamWriter.flush();
 		return out.toByteArray();
@@ -127,31 +128,31 @@ public class SyncIntegrationTest extends BrambleTestCase {
 				headerKey, streamNumber);
 		InputStream streamReader = streamReaderFactory.createStreamReader(in,
 				ctx);
-		PacketReader packetReader = packetReaderFactory.createPacketReader(
+		RecordReader recordReader = recordReaderFactory.createRecordReader(
 				streamReader);
 
 		// Read the ack
-		assertTrue(packetReader.hasAck());
-		Ack a = packetReader.readAck();
+		assertTrue(recordReader.hasAck());
+		Ack a = recordReader.readAck();
 		assertEquals(messageIds, a.getMessageIds());
 
 		// Read the messages
-		assertTrue(packetReader.hasMessage());
-		Message m = packetReader.readMessage();
+		assertTrue(recordReader.hasMessage());
+		Message m = recordReader.readMessage();
 		checkMessageEquality(message, m);
-		assertTrue(packetReader.hasMessage());
-		m = packetReader.readMessage();
+		assertTrue(recordReader.hasMessage());
+		m = recordReader.readMessage();
 		checkMessageEquality(message1, m);
-		assertFalse(packetReader.hasMessage());
+		assertFalse(recordReader.hasMessage());
 
 		// Read the offer
-		assertTrue(packetReader.hasOffer());
-		Offer o = packetReader.readOffer();
+		assertTrue(recordReader.hasOffer());
+		Offer o = recordReader.readOffer();
 		assertEquals(messageIds, o.getMessageIds());
 
 		// Read the request
-		assertTrue(packetReader.hasRequest());
-		Request req = packetReader.readRequest();
+		assertTrue(recordReader.hasRequest());
+		Request req = recordReader.readRequest();
 		assertEquals(messageIds, req.getMessageIds());
 
 		in.close();
diff --git a/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java
index 9b207cbaa310a0924dba34e7581ed667e1c7e0ef..03722734b01285b2d694c50e353e20a8c6dd6701 100644
--- a/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java
@@ -39,7 +39,11 @@ class QueueMessageFactoryImpl implements QueueMessageFactory {
 		ByteUtils.writeUint64(queuePosition, raw, MESSAGE_HEADER_LENGTH);
 		System.arraycopy(body, 0, raw, QUEUE_MESSAGE_HEADER_LENGTH,
 				body.length);
-		MessageId id = new MessageId(crypto.hash(MessageId.LABEL, raw));
+		byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
+		ByteUtils.writeUint64(timestamp, timeBytes, 0);
+		MessageId id = new MessageId(
+				crypto.hash(MessageId.LABEL, groupId.getBytes(), timeBytes,
+						body));
 		return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
 	}
 
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
index 79197bf4393fb6b58d97da582c57df2eb6736324..bf5f2a387c378e93eb30b69f4491065cf9314f65 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
@@ -22,8 +22,8 @@ import javax.inject.Inject;
 
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
 import static org.junit.Assert.assertTrue;
@@ -59,7 +59,7 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
 		int length = message.getMessage().getRaw().length;
 		assertTrue(
 				length > UniqueId.LENGTH + 8 + MAX_PRIVATE_MESSAGE_BODY_LENGTH);
-		assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH);
+		assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH);
 	}
 
 	@Test
@@ -85,7 +85,7 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
 				+ MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH
 				+ ForumConstants.MAX_CONTENT_TYPE_LENGTH
 				+ MAX_FORUM_POST_BODY_LENGTH);
-		assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH);
+		assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH);
 	}
 
 	private static void injectEagerSingletons(