diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java
index bffaa76c16e3cf565f26fdaee047e748aa4ccaf3..77ba91d23d1212333d4c5380c2792de84e241631 100644
--- a/components/net/sf/briar/protocol/AckReader.java
+++ b/components/net/sf/briar/protocol/AckReader.java
@@ -6,6 +6,7 @@ import java.util.Collection;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
@@ -24,7 +25,7 @@ class AckReader implements ObjectReader<Ack> {
 
 	public Ack readObject(Reader r) throws IOException {
 		// Initialise the consumer
-		CountingConsumer counting = new CountingConsumer(Ack.MAX_SIZE);
+		Consumer counting = new CountingConsumer(Ack.MAX_SIZE);
 		// Read and digest the data
 		r.addConsumer(counting);
 		r.readUserDefinedTag(Tags.ACK);
diff --git a/components/net/sf/briar/protocol/BatchReader.java b/components/net/sf/briar/protocol/BatchReader.java
index 95b3ae3446087916241410e42f6f825ef81cd700..15ccb655cd310cd41be88130a5459f90727ca49d 100644
--- a/components/net/sf/briar/protocol/BatchReader.java
+++ b/components/net/sf/briar/protocol/BatchReader.java
@@ -9,6 +9,7 @@ import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
@@ -30,7 +31,7 @@ class BatchReader implements ObjectReader<Batch> {
 
 	public Batch readObject(Reader r) throws IOException {
 		// Initialise the consumers
-		CountingConsumer counting = new CountingConsumer(Batch.MAX_SIZE);
+		Consumer counting = new CountingConsumer(Batch.MAX_SIZE);
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		messageDigest.reset();
 		// Read and digest the data
diff --git a/components/net/sf/briar/protocol/MessageIdReader.java b/components/net/sf/briar/protocol/MessageIdReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..118bf0ea484065d0ccb429938086361d361f28ee
--- /dev/null
+++ b/components/net/sf/briar/protocol/MessageIdReader.java
@@ -0,0 +1,20 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+
+import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.UniqueId;
+import net.sf.briar.api.serial.FormatException;
+import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+class MessageIdReader implements ObjectReader<MessageId> {
+
+	public MessageId readObject(Reader r) throws IOException {
+		r.readUserDefinedTag(Tags.MESSAGE_ID);
+		byte[] b = r.readBytes();
+		if(b.length != UniqueId.LENGTH) throw new FormatException();
+		return new MessageId(b);
+	}
+}
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
index bc206e00b4e9f72b2957fb6fb0714ae7076b0374..ef71831285afc77942f08fbd69134af6c6649506 100644
--- a/components/net/sf/briar/protocol/MessageReader.java
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -14,7 +14,6 @@ import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Tags;
-import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
@@ -23,6 +22,7 @@ import com.google.inject.Inject;
 
 class MessageReader implements ObjectReader<Message> {
 
+	private final ObjectReader<MessageId> messageIdReader;
 	private final ObjectReader<Group> groupReader;
 	private final ObjectReader<Author> authorReader;
 	private final KeyParser keyParser;
@@ -30,8 +30,11 @@ class MessageReader implements ObjectReader<Message> {
 	private final MessageDigest messageDigest;
 
 	@Inject
-	MessageReader(CryptoComponent crypto, ObjectReader<Group> groupReader,
+	MessageReader(CryptoComponent crypto,
+			ObjectReader<MessageId> messageIdReader,
+			ObjectReader<Group> groupReader,
 			ObjectReader<Author> authorReader) {
+		this.messageIdReader = messageIdReader;
 		this.groupReader = groupReader;
 		this.authorReader = authorReader;
 		keyParser = crypto.getKeyParser();
@@ -47,10 +50,9 @@ class MessageReader implements ObjectReader<Message> {
 		// Read the initial tag
 		r.readUserDefinedTag(Tags.MESSAGE);
 		// Read the parent's message ID
-		r.readUserDefinedTag(Tags.MESSAGE_ID);
-		byte[] b = r.readBytes();
-		if(b.length != UniqueId.LENGTH) throw new FormatException();
-		MessageId parent = new MessageId(b);
+		r.addObjectReader(Tags.MESSAGE_ID, messageIdReader);
+		MessageId parent = r.readUserDefined(Tags.MESSAGE_ID, MessageId.class);
+		r.removeObjectReader(Tags.MESSAGE_ID);
 		// Read the group
 		r.addObjectReader(Tags.GROUP, groupReader);
 		Group group = r.readUserDefined(Tags.GROUP, Group.class);
diff --git a/components/net/sf/briar/protocol/OfferFactory.java b/components/net/sf/briar/protocol/OfferFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..23fbea52d438578072dcaa7142c0d9e4fb9c15b1
--- /dev/null
+++ b/components/net/sf/briar/protocol/OfferFactory.java
@@ -0,0 +1,11 @@
+package net.sf.briar.protocol;
+
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Offer;
+
+interface OfferFactory {
+
+	Offer createOffer(Collection<MessageId> messages);
+}
diff --git a/components/net/sf/briar/protocol/OfferFactoryImpl.java b/components/net/sf/briar/protocol/OfferFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8635b0b96adf24738f3b3f2080c3616ade4e8d62
--- /dev/null
+++ b/components/net/sf/briar/protocol/OfferFactoryImpl.java
@@ -0,0 +1,13 @@
+package net.sf.briar.protocol;
+
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Offer;
+
+class OfferFactoryImpl implements OfferFactory {
+
+	public Offer createOffer(Collection<MessageId> messages) {
+		return new OfferImpl(messages);
+	}
+}
diff --git a/components/net/sf/briar/protocol/OfferImpl.java b/components/net/sf/briar/protocol/OfferImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..129d2054e5e68eb78edbc1ac4bf3cf3e1102ff2f
--- /dev/null
+++ b/components/net/sf/briar/protocol/OfferImpl.java
@@ -0,0 +1,19 @@
+package net.sf.briar.protocol;
+
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Offer;
+
+class OfferImpl implements Offer {
+
+	private final Collection<MessageId> messages;
+
+	OfferImpl(Collection<MessageId> messages) {
+		this.messages = messages;
+	}
+
+	public Collection<MessageId> getMessages() {
+		return messages;
+	}
+}
diff --git a/components/net/sf/briar/protocol/OfferReader.java b/components/net/sf/briar/protocol/OfferReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..459310dec1f056b9521266603794a390990c6b83
--- /dev/null
+++ b/components/net/sf/briar/protocol/OfferReader.java
@@ -0,0 +1,40 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.serial.Consumer;
+import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+import com.google.inject.Inject;
+
+class OfferReader implements ObjectReader<Offer> {
+
+	private final ObjectReader<MessageId> messageIdReader;
+	private final OfferFactory offerFactory;
+
+	@Inject
+	OfferReader(ObjectReader<MessageId> messageIdReader,
+			OfferFactory offerFactory) {
+		this.messageIdReader = messageIdReader;
+		this.offerFactory = offerFactory;
+	}
+
+	public Offer readObject(Reader r) throws IOException {
+		// Initialise the consumer
+		Consumer counting = new CountingConsumer(Offer.MAX_SIZE);
+		// Read the data
+		r.addConsumer(counting);
+		r.readUserDefinedTag(Tags.OFFER);
+		r.addObjectReader(Tags.MESSAGE_ID, messageIdReader);
+		Collection<MessageId> messages = r.readList(MessageId.class);
+		r.removeObjectReader(Tags.MESSAGE_ID);
+		r.removeConsumer(counting);
+		// Build and return the offer
+		return offerFactory.createOffer(messages);
+	}
+}
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index c5ea605579b52f2e1ff580b0b50423b08bf4520a..ffd8e51c9806025ccf6ce8bffb9abfe8813916ff 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -8,6 +8,7 @@ import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupFactory;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
+import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.serial.ObjectReader;
 
 import com.google.inject.AbstractModule;
@@ -21,6 +22,7 @@ public class ProtocolModule extends AbstractModule {
 		bind(AuthorFactory.class).to(AuthorFactoryImpl.class);
 		bind(BatchFactory.class).to(BatchFactoryImpl.class);
 		bind(GroupFactory.class).to(GroupFactoryImpl.class);
+		bind(OfferFactory.class).to(OfferFactoryImpl.class);
 		bind(SubscriptionFactory.class).to(SubscriptionFactoryImpl.class);
 		bind(TransportFactory.class).to(TransportFactoryImpl.class);
 		bind(MessageEncoder.class).to(MessageEncoderImpl.class);
@@ -31,6 +33,11 @@ public class ProtocolModule extends AbstractModule {
 		return new BatchIdReader();
 	}
 
+	@Provides
+	ObjectReader<MessageId> getMessageIdReader() {
+		return new MessageIdReader();
+	}
+
 	@Provides
 	ObjectReader<Group> getGroupReader(CryptoComponent crypto,
 			GroupFactory groupFactory) {
@@ -45,8 +52,10 @@ public class ProtocolModule extends AbstractModule {
 
 	@Provides
 	ObjectReader<Message> getMessageReader(CryptoComponent crypto,
+			ObjectReader<MessageId> messageIdReader,
 			ObjectReader<Group> groupReader,
 			ObjectReader<Author> authorReader) {
-		return new MessageReader(crypto, groupReader, authorReader);
+		return new MessageReader(crypto, messageIdReader, groupReader,
+				authorReader);
 	}
 }
diff --git a/components/net/sf/briar/protocol/SubscriptionReader.java b/components/net/sf/briar/protocol/SubscriptionReader.java
index 88a693b26a7a95284109eb3f9ade39c94b608a8e..b3c68b9b632f5e017e00905b39f6d4d3223324b4 100644
--- a/components/net/sf/briar/protocol/SubscriptionReader.java
+++ b/components/net/sf/briar/protocol/SubscriptionReader.java
@@ -6,6 +6,7 @@ import java.util.Collection;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.Subscriptions;
 import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
@@ -25,8 +26,7 @@ class SubscriptionReader implements ObjectReader<Subscriptions> {
 
 	public Subscriptions readObject(Reader r) throws IOException {
 		// Initialise the consumer
-		CountingConsumer counting =
-			new CountingConsumer(Subscriptions.MAX_SIZE);
+		Consumer counting = new CountingConsumer(Subscriptions.MAX_SIZE);
 		// Read the data
 		r.addConsumer(counting);
 		r.readUserDefinedTag(Tags.SUBSCRIPTIONS);
diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java
index 41d5f21f6f10ed42216d79bdee6b1dbc0285f992..bec0dd0f6a7ca9ff29d781d20e610b1e2385a5a7 100644
--- a/components/net/sf/briar/protocol/TransportReader.java
+++ b/components/net/sf/briar/protocol/TransportReader.java
@@ -5,6 +5,7 @@ import java.util.Map;
 
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.Transports;
+import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
@@ -21,7 +22,7 @@ class TransportReader implements ObjectReader<Transports> {
 
 	public Transports readObject(Reader r) throws IOException {
 		// Initialise the consumer
-		CountingConsumer counting = new CountingConsumer(Transports.MAX_SIZE);
+		Consumer counting = new CountingConsumer(Transports.MAX_SIZE);
 		// Read the data
 		r.addConsumer(counting);
 		r.readUserDefinedTag(Tags.TRANSPORTS);
diff --git a/test/net/sf/briar/protocol/FileReadWriteTest.java b/test/net/sf/briar/protocol/FileReadWriteTest.java
index 695008fa5b3e9b7d15cde8a31facc7d693f19d33..9b77922d90458ee1c02918f204f7d43e73ef4a08 100644
--- a/test/net/sf/briar/protocol/FileReadWriteTest.java
+++ b/test/net/sf/briar/protocol/FileReadWriteTest.java
@@ -23,12 +23,14 @@ import net.sf.briar.api.protocol.GroupFactory;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Offer;
 import net.sf.briar.api.protocol.Subscriptions;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.Transports;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.protocol.writers.AckWriter;
 import net.sf.briar.api.protocol.writers.BatchWriter;
+import net.sf.briar.api.protocol.writers.OfferWriter;
 import net.sf.briar.api.protocol.writers.PacketWriterFactory;
 import net.sf.briar.api.protocol.writers.SubscriptionWriter;
 import net.sf.briar.api.protocol.writers.TransportWriter;
@@ -58,6 +60,7 @@ public class FileReadWriteTest extends TestCase {
 	private final CryptoComponent crypto;
 	private final AckReader ackReader;
 	private final BatchReader batchReader;
+	private final OfferReader offerReader;
 	private final SubscriptionReader subscriptionReader;
 	private final TransportReader transportReader;
 	private final Author author;
@@ -78,6 +81,7 @@ public class FileReadWriteTest extends TestCase {
 				UniqueId.LENGTH);
 		ackReader = i.getInstance(AckReader.class);
 		batchReader = i.getInstance(BatchReader.class);
+		offerReader = i.getInstance(OfferReader.class);
 		subscriptionReader = i.getInstance(SubscriptionReader.class);
 		transportReader = i.getInstance(TransportReader.class);
 		// Create two groups: one restricted, one unrestricted
@@ -124,6 +128,13 @@ public class FileReadWriteTest extends TestCase {
 		assertTrue(b.writeMessage(message3.getBytes()));
 		b.finish();
 
+		OfferWriter o = packetWriterFactory.createOfferWriter(out);
+		assertTrue(o.writeMessageId(message.getId()));
+		assertTrue(o.writeMessageId(message1.getId()));
+		assertTrue(o.writeMessageId(message2.getId()));
+		assertTrue(o.writeMessageId(message3.getId()));
+		o.finish();
+
 		SubscriptionWriter s =
 			packetWriterFactory.createSubscriptionWriter(out);
 		Collection<Group> subs = new ArrayList<Group>();
@@ -148,6 +159,7 @@ public class FileReadWriteTest extends TestCase {
 		Reader reader = readerFactory.createReader(in);
 		reader.addObjectReader(Tags.ACK, ackReader);
 		reader.addObjectReader(Tags.BATCH, batchReader);
+		reader.addObjectReader(Tags.OFFER, offerReader);
 		reader.addObjectReader(Tags.SUBSCRIPTIONS, subscriptionReader);
 		reader.addObjectReader(Tags.TRANSPORTS, transportReader);
 
@@ -166,16 +178,27 @@ public class FileReadWriteTest extends TestCase {
 		checkMessageEquality(message1, i.next());
 		checkMessageEquality(message2, i.next());
 		checkMessageEquality(message3, i.next());
-		
+
+		// Read the offer
+		assertTrue(reader.hasUserDefined(Tags.OFFER));
+		Offer o = reader.readUserDefined(Tags.OFFER, Offer.class);
+		Collection<MessageId> ids = o.getMessages();
+		assertEquals(4, ids.size());
+		Iterator<MessageId> i1 = ids.iterator();
+		assertEquals(message.getId(), i1.next());
+		assertEquals(message1.getId(), i1.next());
+		assertEquals(message2.getId(), i1.next());
+		assertEquals(message3.getId(), i1.next());
+
 		// Read the subscriptions update
 		assertTrue(reader.hasUserDefined(Tags.SUBSCRIPTIONS));
 		Subscriptions s = reader.readUserDefined(Tags.SUBSCRIPTIONS,
 				Subscriptions.class);
 		Collection<Group> subs = s.getSubscriptions();
 		assertEquals(2, subs.size());
-		Iterator<Group> i1 = subs.iterator();
-		checkGroupEquality(group, i1.next());
-		checkGroupEquality(group1, i1.next());
+		Iterator<Group> i2 = subs.iterator();
+		checkGroupEquality(group, i2.next());
+		checkGroupEquality(group1, i2.next());
 		assertTrue(s.getTimestamp() > start);
 		assertTrue(s.getTimestamp() <= System.currentTimeMillis());