From 941460e3bc52b2e83047428ee9cf73d5d2f7fb5a Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Sat, 23 Jul 2011 21:46:47 +0100
Subject: [PATCH] Readers, writers and factories for subscription and transport
 updates.

---
 api/net/sf/briar/api/protocol/Group.java      |  4 +-
 .../sf/briar/api/protocol/GroupFactory.java   |  3 +-
 .../sf/briar/api/protocol/Subscriptions.java  |  6 ++
 api/net/sf/briar/api/protocol/Tags.java       | 11 +--
 api/net/sf/briar/api/protocol/Transports.java |  6 ++
 .../briar/api/protocol/writers/AckWriter.java |  2 +-
 .../api/protocol/writers/BatchWriter.java     |  2 +-
 .../protocol/writers/SubscriptionWriter.java  |  5 +-
 .../api/protocol/writers/TransportWriter.java |  4 +-
 .../db/ReadWriteLockDatabaseComponent.java    |  8 +-
 .../db/SynchronizedDatabaseComponent.java     |  8 +-
 .../net/sf/briar/protocol/AckReader.java      |  6 +-
 .../sf/briar/protocol/GroupFactoryImpl.java   | 10 +--
 .../net/sf/briar/protocol/GroupImpl.java      | 21 ++++-
 .../net/sf/briar/protocol/GroupReader.java    | 38 +++++++++
 .../sf/briar/protocol/MessageEncoderImpl.java |  3 +
 .../net/sf/briar/protocol/ProtocolModule.java |  2 +
 .../briar/protocol/SubscriptionFactory.java   | 11 +++
 .../protocol/SubscriptionFactoryImpl.java     | 14 ++++
 .../sf/briar/protocol/SubscriptionReader.java | 38 +++++++++
 .../sf/briar/protocol/SubscriptionsImpl.java  | 25 ++++++
 .../sf/briar/protocol/TransportFactory.java   | 10 +++
 .../briar/protocol/TransportFactoryImpl.java  | 13 ++++
 .../sf/briar/protocol/TransportReader.java    | 31 ++++++++
 .../net/sf/briar/protocol/TransportsImpl.java | 24 ++++++
 .../briar/protocol/writers/AckWriterImpl.java |  4 +-
 .../protocol/writers/BatchWriterImpl.java     |  2 +-
 .../writers/PacketWriterFactoryImpl.java      |  6 +-
 .../writers/SubscriptionWriterImpl.java       | 33 ++++++++
 .../protocol/writers/TransportWriterImpl.java | 33 ++++++++
 .../sf/briar/db/DatabaseComponentTest.java    |  4 +-
 test/net/sf/briar/db/H2DatabaseTest.java      |  5 +-
 test/net/sf/briar/protocol/AckReaderTest.java |  6 +-
 .../sf/briar/protocol/FileReadWriteTest.java  | 78 ++++++++++++++++---
 34 files changed, 423 insertions(+), 53 deletions(-)
 create mode 100644 components/net/sf/briar/protocol/GroupReader.java
 create mode 100644 components/net/sf/briar/protocol/SubscriptionFactory.java
 create mode 100644 components/net/sf/briar/protocol/SubscriptionFactoryImpl.java
 create mode 100644 components/net/sf/briar/protocol/SubscriptionReader.java
 create mode 100644 components/net/sf/briar/protocol/SubscriptionsImpl.java
 create mode 100644 components/net/sf/briar/protocol/TransportFactory.java
 create mode 100644 components/net/sf/briar/protocol/TransportFactoryImpl.java
 create mode 100644 components/net/sf/briar/protocol/TransportReader.java
 create mode 100644 components/net/sf/briar/protocol/TransportsImpl.java
 create mode 100644 components/net/sf/briar/protocol/writers/SubscriptionWriterImpl.java
 create mode 100644 components/net/sf/briar/protocol/writers/TransportWriterImpl.java

diff --git a/api/net/sf/briar/api/protocol/Group.java b/api/net/sf/briar/api/protocol/Group.java
index 43de430292..13e63a2b30 100644
--- a/api/net/sf/briar/api/protocol/Group.java
+++ b/api/net/sf/briar/api/protocol/Group.java
@@ -2,8 +2,10 @@ package net.sf.briar.api.protocol;
 
 import java.security.PublicKey;
 
+import net.sf.briar.api.serial.Writable;
+
 /** A group to which users may subscribe. */
-public interface Group {
+public interface Group extends Writable {
 
 	/** Returns the group's unique identifier. */
 	GroupId getId();
diff --git a/api/net/sf/briar/api/protocol/GroupFactory.java b/api/net/sf/briar/api/protocol/GroupFactory.java
index 55a4f79e2e..d10ab913a5 100644
--- a/api/net/sf/briar/api/protocol/GroupFactory.java
+++ b/api/net/sf/briar/api/protocol/GroupFactory.java
@@ -2,5 +2,6 @@ package net.sf.briar.api.protocol;
 
 public interface GroupFactory {
 
-	Group createGroup(GroupId id, String name, boolean restricted, byte[] b);
+	Group createGroup(GroupId id, String name, boolean restricted,
+			byte[] saltOrKey);
 }
diff --git a/api/net/sf/briar/api/protocol/Subscriptions.java b/api/net/sf/briar/api/protocol/Subscriptions.java
index ce9fc892c0..28d670c086 100644
--- a/api/net/sf/briar/api/protocol/Subscriptions.java
+++ b/api/net/sf/briar/api/protocol/Subscriptions.java
@@ -5,6 +5,12 @@ import java.util.Collection;
 /** A packet updating the sender's subscriptions. */
 public interface Subscriptions {
 
+	/**
+	 * The maximum size of a serialized subscriptions update, excluding
+	 * encryption and authentication.
+	 */
+	static final int MAX_SIZE = (1024 * 1024) - 100;
+
 	/** Returns the subscriptions contained in the update. */
 	Collection<Group> getSubscriptions();
 
diff --git a/api/net/sf/briar/api/protocol/Tags.java b/api/net/sf/briar/api/protocol/Tags.java
index 982c1106c9..b8edfb5d0c 100644
--- a/api/net/sf/briar/api/protocol/Tags.java
+++ b/api/net/sf/briar/api/protocol/Tags.java
@@ -11,9 +11,10 @@ public interface Tags {
 	static final int AUTHOR_ID = 1;
 	static final int BATCH = 2;
 	static final int BATCH_ID = 3;
-	static final int GROUP_ID = 4;
-	static final int MESSAGE = 5;
-	static final int MESSAGE_ID = 6;
-	static final int SUBSCRIPTIONS = 7;
-	static final int TRANSPORTS = 8;
+	static final int GROUP = 4;
+	static final int GROUP_ID = 5;
+	static final int MESSAGE = 6;
+	static final int MESSAGE_ID = 7;
+	static final int SUBSCRIPTIONS = 8;
+	static final int TRANSPORTS = 9;
 }
diff --git a/api/net/sf/briar/api/protocol/Transports.java b/api/net/sf/briar/api/protocol/Transports.java
index 1c27f921c2..d18f5ccd3b 100644
--- a/api/net/sf/briar/api/protocol/Transports.java
+++ b/api/net/sf/briar/api/protocol/Transports.java
@@ -5,6 +5,12 @@ import java.util.Map;
 /** A packet updating the sender's transports. */
 public interface Transports {
 
+	/**
+	 * The maximum size of a serialised transports update, excluding
+	 * encryption and authentication.
+	 */
+	static final int MAX_SIZE = (1024 * 1024) - 100;
+
 	/** Returns the transports contained in the update. */
 	Map<String, String> getTransports();
 
diff --git a/api/net/sf/briar/api/protocol/writers/AckWriter.java b/api/net/sf/briar/api/protocol/writers/AckWriter.java
index 846b36144c..c362a60577 100644
--- a/api/net/sf/briar/api/protocol/writers/AckWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/AckWriter.java
@@ -11,7 +11,7 @@ public interface AckWriter {
 	 * Attempts to add the given BatchId to the ack and returns true if it
 	 * was added.
 	 */
-	boolean addBatchId(BatchId b) throws IOException;
+	boolean writeBatchId(BatchId b) throws IOException;
 
 	/** Finishes writing the ack. */
 	void finish() throws IOException;
diff --git a/api/net/sf/briar/api/protocol/writers/BatchWriter.java b/api/net/sf/briar/api/protocol/writers/BatchWriter.java
index 4b6ecc0e1a..62e150dedc 100644
--- a/api/net/sf/briar/api/protocol/writers/BatchWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/BatchWriter.java
@@ -14,7 +14,7 @@ public interface BatchWriter {
 	 * Attempts to add the given raw message to the batch and returns true if
 	 * it was added.
 	 */
-	boolean addMessage(byte[] raw) throws IOException;
+	boolean writeMessage(byte[] raw) throws IOException;
 
 	/** Finishes writing the batch and returns its unique identifier. */
 	BatchId finish() throws IOException;
diff --git a/api/net/sf/briar/api/protocol/writers/SubscriptionWriter.java b/api/net/sf/briar/api/protocol/writers/SubscriptionWriter.java
index 7e7d340d0c..5bbf4b5065 100644
--- a/api/net/sf/briar/api/protocol/writers/SubscriptionWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/SubscriptionWriter.java
@@ -1,12 +1,13 @@
 package net.sf.briar.api.protocol.writers;
 
 import java.io.IOException;
+import java.util.Collection;
 
 import net.sf.briar.api.protocol.Group;
 
 /** An interface for creating a subscription update. */
 public interface SubscriptionWriter {
 
-	/** Sets the contents of the update. */
-	void setSubscriptions(Iterable<Group> subs) throws IOException;
+	/** Writes the contents of the update. */
+	void writeSubscriptions(Collection<Group> subs) throws IOException;
 }
diff --git a/api/net/sf/briar/api/protocol/writers/TransportWriter.java b/api/net/sf/briar/api/protocol/writers/TransportWriter.java
index 5caa756c42..6e03d2f562 100644
--- a/api/net/sf/briar/api/protocol/writers/TransportWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/TransportWriter.java
@@ -6,6 +6,6 @@ import java.util.Map;
 /** An interface for creating a transports update. */
 public interface TransportWriter {
 
-	/** Sets the contents of the update. */
-	void setTransports(Map<String, String> transports) throws IOException;
+	/** Writes the contents of the update. */
+	void writeTransports(Map<String, String> transports) throws IOException;
 }
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index 15cee66876..41f38f6efb 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -259,7 +259,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				try {
 					Collection<BatchId> acks = db.getBatchesToAck(txn, c);
 					Collection<BatchId> sent = new ArrayList<BatchId>();
-					for(BatchId b : acks) if(a.addBatchId(b)) sent.add(b);
+					for(BatchId b : acks) if(a.writeBatchId(b)) sent.add(b);
 					a.finish();
 					db.removeBatchesToAck(txn, c, sent);
 					if(LOG.isLoggable(Level.FINE))
@@ -300,7 +300,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 						while(it.hasNext()) {
 							MessageId m = it.next();
 							byte[] message = db.getMessage(txn, m);
-							if(!b.addMessage(message)) break;
+							if(!b.writeMessage(message)) break;
 							bytesSent += message.length;
 							sent.add(m);
 						}
@@ -349,7 +349,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				Txn txn = db.startTransaction();
 				try {
 					Collection<Group> subs = db.getSubscriptions(txn);
-					s.setSubscriptions(subs);
+					s.writeSubscriptions(subs);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added " + subs.size() + " subscriptions");
 					db.commitTransaction(txn);
@@ -378,7 +378,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				Txn txn = db.startTransaction();
 				try {
 					Map<String, String> transports = db.getTransports(txn);
-					t.setTransports(transports);
+					t.writeTransports(transports);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added " + transports.size() + " transports");
 					db.commitTransaction(txn);
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index 1377739517..dc9241d8ad 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -192,7 +192,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				try {
 					Collection<BatchId> acks = db.getBatchesToAck(txn, c);
 					Collection<BatchId> sent = new ArrayList<BatchId>();
-					for(BatchId b : acks) if(a.addBatchId(b)) sent.add(b);
+					for(BatchId b : acks) if(a.writeBatchId(b)) sent.add(b);
 					a.finish();
 					db.removeBatchesToAck(txn, c, sent);
 					if(LOG.isLoggable(Level.FINE))
@@ -225,7 +225,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 						while(it.hasNext()) {
 							MessageId m = it.next();
 							byte[] message = db.getMessage(txn, m);
-							if(!b.addMessage(message)) break;
+							if(!b.writeMessage(message)) break;
 							bytesSent += message.length;
 							sent.add(m);
 						}
@@ -254,7 +254,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				Txn txn = db.startTransaction();
 				try {
 					Collection<Group> subs = db.getSubscriptions(txn);
-					s.setSubscriptions(subs);
+					s.writeSubscriptions(subs);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added " + subs.size() + " subscriptions");
 					db.commitTransaction(txn);
@@ -277,7 +277,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				Txn txn = db.startTransaction();
 				try {
 					Map<String, String> transports = db.getTransports(txn);
-					t.setTransports(transports);
+					t.writeTransports(transports);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added " + transports.size() + " transports");
 					db.commitTransaction(txn);
diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java
index 270b29f508..93c88fc8ab 100644
--- a/components/net/sf/briar/protocol/AckReader.java
+++ b/components/net/sf/briar/protocol/AckReader.java
@@ -11,9 +11,11 @@ import net.sf.briar.api.serial.Reader;
 
 class AckReader implements ObjectReader<Ack> {
 
+	private final ObjectReader<BatchId> batchIdReader;
 	private final AckFactory ackFactory;
 
-	AckReader(AckFactory ackFactory) {
+	AckReader(ObjectReader<BatchId> batchIdReader, AckFactory ackFactory) {
+		this.batchIdReader = batchIdReader;
 		this.ackFactory = ackFactory;
 	}
 
@@ -23,7 +25,7 @@ class AckReader implements ObjectReader<Ack> {
 		// Read and digest the data
 		r.addConsumer(counting);
 		r.readUserDefinedTag(Tags.ACK);
-		r.addObjectReader(Tags.BATCH_ID, new BatchIdReader());
+		r.addObjectReader(Tags.BATCH_ID, batchIdReader);
 		Collection<BatchId> batches = r.readList(BatchId.class);
 		r.removeObjectReader(Tags.BATCH_ID);
 		r.removeConsumer(counting);
diff --git a/components/net/sf/briar/protocol/GroupFactoryImpl.java b/components/net/sf/briar/protocol/GroupFactoryImpl.java
index d9b1941066..b76028565f 100644
--- a/components/net/sf/briar/protocol/GroupFactoryImpl.java
+++ b/components/net/sf/briar/protocol/GroupFactoryImpl.java
@@ -20,14 +20,14 @@ class GroupFactoryImpl implements GroupFactory {
 	}
 
 	public Group createGroup(GroupId id, String name, boolean restricted,
-			byte[] b) {
+			byte[] saltOrKey) {
 		if(restricted) {
 			try {
-				PublicKey key = keyParser.parsePublicKey(b);
-				return new GroupImpl(id, name, null, key);
-			} catch (InvalidKeySpecException e) {
+				PublicKey key = keyParser.parsePublicKey(saltOrKey);
+				return new GroupImpl(id, name, key);
+			} catch(InvalidKeySpecException e) {
 				throw new IllegalArgumentException(e);
 			}
-		} else return new GroupImpl(id, name, b, null);
+		} else return new GroupImpl(id, name, saltOrKey);
 	}
 }
diff --git a/components/net/sf/briar/protocol/GroupImpl.java b/components/net/sf/briar/protocol/GroupImpl.java
index 3335ab98e9..b70a544035 100644
--- a/components/net/sf/briar/protocol/GroupImpl.java
+++ b/components/net/sf/briar/protocol/GroupImpl.java
@@ -1,9 +1,12 @@
 package net.sf.briar.protocol;
 
+import java.io.IOException;
 import java.security.PublicKey;
 
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.serial.Writer;
 
 class GroupImpl implements Group {
 
@@ -12,12 +15,18 @@ class GroupImpl implements Group {
 	private final byte[] salt;
 	private final PublicKey publicKey;
 
-	GroupImpl(GroupId id, String name, byte[] salt, PublicKey publicKey) {
-		assert salt == null || publicKey == null;
+	GroupImpl(GroupId id, String name, byte[] salt) {
 		this.id = id;
 		this.name = name;
 		this.salt = salt;
+		publicKey = null;
+	}
+
+	GroupImpl(GroupId id, String name, PublicKey publicKey) {
+		this.id = id;
+		this.name = name;
 		this.publicKey = publicKey;
+		salt = null;
 	}
 
 	public GroupId getId() {
@@ -40,6 +49,14 @@ class GroupImpl implements Group {
 		return publicKey;
 	}
 
+	public void writeTo(Writer w) throws IOException {
+		w.writeUserDefinedTag(Tags.GROUP);
+		w.writeString(name);
+		w.writeBoolean(isRestricted());
+		if(salt == null) w.writeRaw(publicKey.getEncoded());
+		else w.writeRaw(salt);
+	}
+
 	@Override
 	public boolean equals(Object o) {
 		return o instanceof Group && id.equals(((Group) o).getId());
diff --git a/components/net/sf/briar/protocol/GroupReader.java b/components/net/sf/briar/protocol/GroupReader.java
new file mode 100644
index 0000000000..af94f29440
--- /dev/null
+++ b/components/net/sf/briar/protocol/GroupReader.java
@@ -0,0 +1,38 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupFactory;
+import net.sf.briar.api.protocol.GroupId;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+class GroupReader implements ObjectReader<Group> {
+
+	private final MessageDigest messageDigest;
+	private final GroupFactory groupFactory;
+
+	GroupReader(MessageDigest messageDigest, GroupFactory groupFactory) {
+		this.messageDigest = messageDigest;
+		this.groupFactory = groupFactory;
+	}
+
+	public Group readObject(Reader r) throws IOException {
+		// Initialise the consumer
+		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
+		messageDigest.reset();
+		// Read and digest the data
+		r.addConsumer(digesting);
+		r.readUserDefinedTag(Tags.GROUP);
+		String name = r.readString();
+		boolean restricted = r.readBoolean();
+		byte[] saltOrKey = r.readRaw();
+		r.removeConsumer(digesting);
+		// Build and return the group
+		GroupId id = new GroupId(messageDigest.digest());
+		return groupFactory.createGroup(id, name, restricted, saltOrKey);
+	}
+}
diff --git a/components/net/sf/briar/protocol/MessageEncoderImpl.java b/components/net/sf/briar/protocol/MessageEncoderImpl.java
index 11a99d6f39..e945166ce0 100644
--- a/components/net/sf/briar/protocol/MessageEncoderImpl.java
+++ b/components/net/sf/briar/protocol/MessageEncoderImpl.java
@@ -16,12 +16,15 @@ import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
 
+import com.google.inject.Inject;
+
 class MessageEncoderImpl implements MessageEncoder {
 
 	private final Signature signature;
 	private final MessageDigest messageDigest;
 	private final WriterFactory writerFactory;
 
+	@Inject
 	MessageEncoderImpl(Signature signature, MessageDigest messageDigest,
 			WriterFactory writerFactory) {
 		this.signature = signature;
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index 402d6af9ac..d7d2311c77 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -1,6 +1,7 @@
 package net.sf.briar.protocol;
 
 import net.sf.briar.api.protocol.GroupFactory;
+import net.sf.briar.api.protocol.MessageEncoder;
 
 import com.google.inject.AbstractModule;
 
@@ -11,5 +12,6 @@ public class ProtocolModule extends AbstractModule {
 		bind(AckFactory.class).to(AckFactoryImpl.class);
 		bind(BatchFactory.class).to(BatchFactoryImpl.class);
 		bind(GroupFactory.class).to(GroupFactoryImpl.class);
+		bind(MessageEncoder.class).to(MessageEncoderImpl.class);
 	}
 }
diff --git a/components/net/sf/briar/protocol/SubscriptionFactory.java b/components/net/sf/briar/protocol/SubscriptionFactory.java
new file mode 100644
index 0000000000..d6be2b293a
--- /dev/null
+++ b/components/net/sf/briar/protocol/SubscriptionFactory.java
@@ -0,0 +1,11 @@
+package net.sf.briar.protocol;
+
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.Subscriptions;
+
+interface SubscriptionFactory {
+
+	Subscriptions createSubscriptions(Collection<Group> subs, long timestamp);
+}
diff --git a/components/net/sf/briar/protocol/SubscriptionFactoryImpl.java b/components/net/sf/briar/protocol/SubscriptionFactoryImpl.java
new file mode 100644
index 0000000000..53c21e50c9
--- /dev/null
+++ b/components/net/sf/briar/protocol/SubscriptionFactoryImpl.java
@@ -0,0 +1,14 @@
+package net.sf.briar.protocol;
+
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.Subscriptions;
+
+class SubscriptionFactoryImpl implements SubscriptionFactory {
+
+	public Subscriptions createSubscriptions(Collection<Group> subs,
+			long timestamp) {
+		return new SubscriptionsImpl(subs, timestamp);
+	}
+}
diff --git a/components/net/sf/briar/protocol/SubscriptionReader.java b/components/net/sf/briar/protocol/SubscriptionReader.java
new file mode 100644
index 0000000000..e490213711
--- /dev/null
+++ b/components/net/sf/briar/protocol/SubscriptionReader.java
@@ -0,0 +1,38 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+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.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+class SubscriptionReader implements ObjectReader<Subscriptions> {
+
+	private final ObjectReader<Group> groupReader;
+	private final SubscriptionFactory subscriptionFactory;
+
+	SubscriptionReader(ObjectReader<Group> groupReader,
+			SubscriptionFactory subscriptionFactory) {
+		this.groupReader = groupReader;
+		this.subscriptionFactory = subscriptionFactory;
+	}
+
+	public Subscriptions readObject(Reader r) throws IOException {
+		// Initialise the consumer
+		CountingConsumer counting =
+			new CountingConsumer(Subscriptions.MAX_SIZE);
+		// Read the data
+		r.addConsumer(counting);
+		r.readUserDefinedTag(Tags.SUBSCRIPTIONS);
+		r.addObjectReader(Tags.GROUP, groupReader);
+		Collection<Group> subs = r.readList(Group.class);
+		r.removeObjectReader(Tags.GROUP);
+		long timestamp = r.readInt64();
+		r.removeConsumer(counting);
+		// Build and return the subscriptions update
+		return subscriptionFactory.createSubscriptions(subs, timestamp);
+	}
+}
diff --git a/components/net/sf/briar/protocol/SubscriptionsImpl.java b/components/net/sf/briar/protocol/SubscriptionsImpl.java
new file mode 100644
index 0000000000..c3967cfc89
--- /dev/null
+++ b/components/net/sf/briar/protocol/SubscriptionsImpl.java
@@ -0,0 +1,25 @@
+package net.sf.briar.protocol;
+
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.Subscriptions;
+
+class SubscriptionsImpl implements Subscriptions {
+
+	private final Collection<Group> subs;
+	private final long timestamp;
+
+	SubscriptionsImpl(Collection<Group> subs, long timestamp) {
+		this.subs = subs;
+		this.timestamp = timestamp;
+	}
+
+	public Collection<Group> getSubscriptions() {
+		return subs;
+	}
+
+	public long getTimestamp() {
+		return timestamp;
+	}
+}
diff --git a/components/net/sf/briar/protocol/TransportFactory.java b/components/net/sf/briar/protocol/TransportFactory.java
new file mode 100644
index 0000000000..8a2b397259
--- /dev/null
+++ b/components/net/sf/briar/protocol/TransportFactory.java
@@ -0,0 +1,10 @@
+package net.sf.briar.protocol;
+
+import java.util.Map;
+
+import net.sf.briar.api.protocol.Transports;
+
+interface TransportFactory {
+
+	Transports createTransports(Map<String, String> transports, long timestamp);
+}
diff --git a/components/net/sf/briar/protocol/TransportFactoryImpl.java b/components/net/sf/briar/protocol/TransportFactoryImpl.java
new file mode 100644
index 0000000000..85ceefc19b
--- /dev/null
+++ b/components/net/sf/briar/protocol/TransportFactoryImpl.java
@@ -0,0 +1,13 @@
+package net.sf.briar.protocol;
+
+import java.util.Map;
+
+import net.sf.briar.api.protocol.Transports;
+
+class TransportFactoryImpl implements TransportFactory {
+
+	public Transports createTransports(Map<String, String> transports,
+			long timestamp) {
+		return new TransportsImpl(transports, timestamp);
+	}
+}
diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java
new file mode 100644
index 0000000000..580d0ffd47
--- /dev/null
+++ b/components/net/sf/briar/protocol/TransportReader.java
@@ -0,0 +1,31 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+import java.util.Map;
+
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.Transports;
+import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+class TransportReader implements ObjectReader<Transports> {
+
+	private final TransportFactory transportFactory;
+
+	TransportReader(TransportFactory transportFactory) {
+		this.transportFactory = transportFactory;
+	}
+
+	public Transports readObject(Reader r) throws IOException {
+		// Initialise the consumer
+		CountingConsumer counting = new CountingConsumer(Transports.MAX_SIZE);
+		// Read the data
+		r.addConsumer(counting);
+		r.readUserDefinedTag(Tags.TRANSPORTS);
+		Map<String, String> transports = r.readMap(String.class, String.class);
+		long timestamp = r.readInt64();
+		r.removeConsumer(counting);
+		// Build and return the transports update
+		return transportFactory.createTransports(transports, timestamp);
+	}
+}
diff --git a/components/net/sf/briar/protocol/TransportsImpl.java b/components/net/sf/briar/protocol/TransportsImpl.java
new file mode 100644
index 0000000000..247f4aa5fd
--- /dev/null
+++ b/components/net/sf/briar/protocol/TransportsImpl.java
@@ -0,0 +1,24 @@
+package net.sf.briar.protocol;
+
+import java.util.Map;
+
+import net.sf.briar.api.protocol.Transports;
+
+class TransportsImpl implements Transports {
+
+	private final Map<String, String> transports;
+	private final long timestamp;
+
+	TransportsImpl(Map<String, String> transports, long timestamp) {
+		this.transports = transports;
+		this.timestamp = timestamp;
+	}
+
+	public Map<String, String> getTransports() {
+		return transports;
+	}
+
+	public long getTimestamp() {
+		return timestamp;
+	}
+}
diff --git a/components/net/sf/briar/protocol/writers/AckWriterImpl.java b/components/net/sf/briar/protocol/writers/AckWriterImpl.java
index ebb6f9fe86..fbc854f0fe 100644
--- a/components/net/sf/briar/protocol/writers/AckWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/AckWriterImpl.java
@@ -19,10 +19,10 @@ class AckWriterImpl implements AckWriter {
 
 	AckWriterImpl(OutputStream out, WriterFactory writerFactory) {
 		this.out = out;
-		this.w = writerFactory.createWriter(out);
+		w = writerFactory.createWriter(out);
 	}
 
-	public boolean addBatchId(BatchId b) throws IOException {
+	public boolean writeBatchId(BatchId b) throws IOException {
 		if(finished) throw new IllegalStateException();
 		if(!started) {
 			w.writeUserDefinedTag(Tags.ACK);
diff --git a/components/net/sf/briar/protocol/writers/BatchWriterImpl.java b/components/net/sf/briar/protocol/writers/BatchWriterImpl.java
index 99a06fcf75..1c026127f0 100644
--- a/components/net/sf/briar/protocol/writers/BatchWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/BatchWriterImpl.java
@@ -31,7 +31,7 @@ class BatchWriterImpl implements BatchWriter {
 		return Batch.MAX_SIZE - 3;
 	}
 
-	public boolean addMessage(byte[] message) throws IOException {
+	public boolean writeMessage(byte[] message) throws IOException {
 		if(finished) throw new IllegalStateException();
 		if(!started) {
 			messageDigest.reset();
diff --git a/components/net/sf/briar/protocol/writers/PacketWriterFactoryImpl.java b/components/net/sf/briar/protocol/writers/PacketWriterFactoryImpl.java
index 3b99500486..97294daa51 100644
--- a/components/net/sf/briar/protocol/writers/PacketWriterFactoryImpl.java
+++ b/components/net/sf/briar/protocol/writers/PacketWriterFactoryImpl.java
@@ -33,12 +33,10 @@ class PacketWriterFactoryImpl implements PacketWriterFactory {
 	}
 
 	public SubscriptionWriter createSubscriptionWriter(OutputStream out) {
-		// TODO Auto-generated method stub
-		return null;
+		return new SubscriptionWriterImpl(out, writerFactory);
 	}
 
 	public TransportWriter createTransportWriter(OutputStream out) {
-		// TODO Auto-generated method stub
-		return null;
+		return new TransportWriterImpl(out, writerFactory);
 	}
 }
diff --git a/components/net/sf/briar/protocol/writers/SubscriptionWriterImpl.java b/components/net/sf/briar/protocol/writers/SubscriptionWriterImpl.java
new file mode 100644
index 0000000000..ad4f6081bd
--- /dev/null
+++ b/components/net/sf/briar/protocol/writers/SubscriptionWriterImpl.java
@@ -0,0 +1,33 @@
+package net.sf.briar.protocol.writers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.writers.SubscriptionWriter;
+import net.sf.briar.api.serial.Writer;
+import net.sf.briar.api.serial.WriterFactory;
+
+class SubscriptionWriterImpl implements SubscriptionWriter {
+
+	private final OutputStream out;
+	private final Writer w;
+
+	private boolean used = false;
+
+	SubscriptionWriterImpl(OutputStream out, WriterFactory writerFactory) {
+		this.out = out;
+		w = writerFactory.createWriter(out);
+	}
+
+	public void writeSubscriptions(Collection<Group> subs) throws IOException {
+		if(used) throw new IllegalStateException();
+		w.writeUserDefinedTag(Tags.SUBSCRIPTIONS);
+		w.writeList(subs);
+		w.writeInt64(System.currentTimeMillis());
+		out.flush();
+		used = true;
+	}
+}
diff --git a/components/net/sf/briar/protocol/writers/TransportWriterImpl.java b/components/net/sf/briar/protocol/writers/TransportWriterImpl.java
new file mode 100644
index 0000000000..0bcca53c54
--- /dev/null
+++ b/components/net/sf/briar/protocol/writers/TransportWriterImpl.java
@@ -0,0 +1,33 @@
+package net.sf.briar.protocol.writers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.writers.TransportWriter;
+import net.sf.briar.api.serial.Writer;
+import net.sf.briar.api.serial.WriterFactory;
+
+class TransportWriterImpl implements TransportWriter {
+
+	private final OutputStream out;
+	private final Writer w;
+
+	private boolean used = false;
+
+	TransportWriterImpl(OutputStream out, WriterFactory writerFactory) {
+		this.out = out;
+		w = writerFactory.createWriter(out);
+	}
+
+	public void writeTransports(Map<String, String> transports)
+	throws IOException {
+		if(used) throw new IllegalStateException();
+		w.writeUserDefinedTag(Tags.TRANSPORTS);
+		w.writeMap(transports);
+		w.writeInt64(System.currentTimeMillis());
+		out.flush();
+		used = true;
+	}
+}
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index c46107aad5..e389c37da4 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -464,9 +464,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getBatchesToAck(txn, contactId);
 			will(returnValue(twoAcks));
 			// Try to add both batches to the writer - only manage to add one
-			oneOf(ackWriter).addBatchId(batchId);
+			oneOf(ackWriter).writeBatchId(batchId);
 			will(returnValue(true));
-			oneOf(ackWriter).addBatchId(batchId1);
+			oneOf(ackWriter).writeBatchId(batchId1);
 			will(returnValue(false));
 			oneOf(ackWriter).finish();
 			// Record the batch that was acked
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 2ca8f09637..25fa730d4f 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -28,6 +28,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.protocol.ProtocolModule;
+import net.sf.briar.serial.SerialModule;
 
 import org.apache.commons.io.FileSystemUtils;
 import org.junit.After;
@@ -62,8 +63,8 @@ public class H2DatabaseTest extends TestCase {
 
 	public H2DatabaseTest() throws Exception {
 		super();
-		Injector i = Guice.createInjector(new ProtocolModule(),
-				new CryptoModule());
+		Injector i = Guice.createInjector(new CryptoModule(),
+				new ProtocolModule(), new SerialModule());
 		groupFactory = i.getInstance(GroupFactory.class);
 		authorId = new AuthorId(TestUtils.getRandomId());
 		batchId = new BatchId(TestUtils.getRandomId());
diff --git a/test/net/sf/briar/protocol/AckReaderTest.java b/test/net/sf/briar/protocol/AckReaderTest.java
index e3c7259c05..4c793e218f 100644
--- a/test/net/sf/briar/protocol/AckReaderTest.java
+++ b/test/net/sf/briar/protocol/AckReaderTest.java
@@ -42,7 +42,7 @@ public class AckReaderTest extends TestCase {
 	@Test
 	public void testFormatExceptionIfAckIsTooLarge() throws Exception {
 		AckFactory ackFactory = context.mock(AckFactory.class);
-		AckReader ackReader = new AckReader(ackFactory);
+		AckReader ackReader = new AckReader(new BatchIdReader(), ackFactory);
 
 		byte[] b = createAck(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
@@ -60,7 +60,7 @@ public class AckReaderTest extends TestCase {
 	@SuppressWarnings("unchecked")
 	public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
 		final AckFactory ackFactory = context.mock(AckFactory.class);
-		AckReader ackReader = new AckReader(ackFactory);
+		AckReader ackReader = new AckReader(new BatchIdReader(), ackFactory);
 		final Ack ack = context.mock(Ack.class);
 		context.checking(new Expectations() {{
 			oneOf(ackFactory).createAck(with(any(Collection.class)));
@@ -79,7 +79,7 @@ public class AckReaderTest extends TestCase {
 	@Test
 	public void testEmptyAck() throws Exception {
 		final AckFactory ackFactory = context.mock(AckFactory.class);
-		AckReader ackReader = new AckReader(ackFactory);
+		AckReader ackReader = new AckReader(new BatchIdReader(), ackFactory);
 		final Ack ack = context.mock(Ack.class);
 		context.checking(new Expectations() {{
 			oneOf(ackFactory).createAck(
diff --git a/test/net/sf/briar/protocol/FileReadWriteTest.java b/test/net/sf/briar/protocol/FileReadWriteTest.java
index f355d01e37..27a180459d 100644
--- a/test/net/sf/briar/protocol/FileReadWriteTest.java
+++ b/test/net/sf/briar/protocol/FileReadWriteTest.java
@@ -1,5 +1,6 @@
 package net.sf.briar.protocol;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -16,22 +17,31 @@ import net.sf.briar.api.crypto.KeyParser;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupFactory;
 import net.sf.briar.api.protocol.GroupId;
 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.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.PacketWriterFactory;
+import net.sf.briar.api.protocol.writers.SubscriptionWriter;
+import net.sf.briar.api.protocol.writers.TransportWriter;
+import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
+import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
 import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.protocol.writers.WritersModule;
 import net.sf.briar.serial.SerialModule;
 
+import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -48,6 +58,7 @@ public class FileReadWriteTest extends TestCase {
 	private final GroupId sub = new GroupId(TestUtils.getRandomId());
 	private final String nick = "Foo Bar";
 	private final String messageBody = "This is the message body! Wooooooo!";
+	private final long start = System.currentTimeMillis();
 
 	private final ReaderFactory readerFactory;
 	private final WriterFactory writerFactory;
@@ -56,11 +67,13 @@ public class FileReadWriteTest extends TestCase {
 	private final MessageDigest messageDigest, batchDigest;
 	private final KeyParser keyParser;
 	private final Message message;
+	private final Group group;
 
 	public FileReadWriteTest() throws Exception {
 		super();
-		Injector i = Guice.createInjector(new SerialModule(),
-				new CryptoModule(), new WritersModule());
+		Injector i = Guice.createInjector(new CryptoModule(),
+				new ProtocolModule(), new SerialModule(),
+				new WritersModule());
 		readerFactory = i.getInstance(ReaderFactory.class);
 		writerFactory = i.getInstance(WriterFactory.class);
 		packetWriterFactory = i.getInstance(PacketWriterFactory.class);
@@ -71,11 +84,23 @@ public class FileReadWriteTest extends TestCase {
 		assertEquals(messageDigest.getDigestLength(), UniqueId.LENGTH);
 		assertEquals(batchDigest.getDigestLength(), UniqueId.LENGTH);
 		// Create and encode a test message
-		MessageEncoder messageEncoder = new MessageEncoderImpl(signature,
-				messageDigest, writerFactory);
+		MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
 		KeyPair keyPair = i.getInstance(KeyPair.class);
 		message = messageEncoder.encodeMessage(MessageId.NONE, sub, nick,
 				keyPair, messageBody.getBytes("UTF-8"));
+		// Create a test group, then write and read it to calculate its ID
+		GroupFactory groupFactory = i.getInstance(GroupFactory.class);
+		Group noId = groupFactory.createGroup(
+				new GroupId(new byte[UniqueId.LENGTH]), "Group name", false,
+				TestUtils.getRandomId());
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		Writer w = writerFactory.createWriter(out);
+		noId.writeTo(w);
+		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+		Reader r = readerFactory.createReader(in);
+		ObjectReader<Group> groupReader = new GroupReader(batchDigest,
+				groupFactory);
+		group = groupReader.readObject(r);
 	}
 
 	@Before
@@ -88,13 +113,20 @@ public class FileReadWriteTest extends TestCase {
 		FileOutputStream out = new FileOutputStream(file);
 
 		AckWriter a = packetWriterFactory.createAckWriter(out);
-		a.addBatchId(ack);
+		assertTrue(a.writeBatchId(ack));
 		a.finish();
 
 		BatchWriter b = packetWriterFactory.createBatchWriter(out);
-		b.addMessage(message.getBytes());
+		assertTrue(b.writeMessage(message.getBytes()));
 		b.finish();
 
+		SubscriptionWriter s =
+			packetWriterFactory.createSubscriptionWriter(out);
+		s.writeSubscriptions(Collections.singleton(group));
+
+		TransportWriter t = packetWriterFactory.createTransportWriter(out);
+		t.writeTransports(Collections.singletonMap("foo", "bar"));
+
 		out.close();
 		assertTrue(file.exists());
 		assertTrue(file.length() > message.getSize());
@@ -107,18 +139,30 @@ public class FileReadWriteTest extends TestCase {
 
 		MessageReader messageReader =
 			new MessageReader(keyParser, signature, messageDigest);
-		AckReader ackReader = new AckReader(new AckFactoryImpl());
-		BatchReader batchReader = new BatchReader(batchDigest, messageReader,
-				new BatchFactoryImpl());
+		ObjectReader<Ack> ackReader = new AckReader(new BatchIdReader(),
+				new AckFactoryImpl());
+		ObjectReader<Batch> batchReader = new BatchReader(batchDigest,
+				messageReader, new BatchFactoryImpl());
+		ObjectReader<Group> groupReader = new GroupReader(batchDigest,
+				new GroupFactoryImpl(keyParser));
+		ObjectReader<Subscriptions> subscriptionReader =
+			new SubscriptionReader(groupReader, new SubscriptionFactoryImpl());
+		ObjectReader<Transports> transportReader =
+			new TransportReader(new TransportFactoryImpl());
+
 		FileInputStream in = new FileInputStream(file);
 		Reader reader = readerFactory.createReader(in);
 		reader.addObjectReader(Tags.ACK, ackReader);
 		reader.addObjectReader(Tags.BATCH, batchReader);
+		reader.addObjectReader(Tags.SUBSCRIPTIONS, subscriptionReader);
+		reader.addObjectReader(Tags.TRANSPORTS, transportReader);
 
+		// Read the ack
 		assertTrue(reader.hasUserDefined(Tags.ACK));
 		Ack a = reader.readUserDefined(Tags.ACK, Ack.class);
 		assertEquals(Collections.singletonList(ack), a.getBatches());
 
+		// Read the batch
 		assertTrue(reader.hasUserDefined(Tags.BATCH));
 		Batch b = reader.readUserDefined(Tags.BATCH, Batch.class);
 		Iterator<Message> i = b.getMessages().iterator();
@@ -132,6 +176,22 @@ public class FileReadWriteTest extends TestCase {
 		assertTrue(Arrays.equals(message.getBytes(), m.getBytes()));
 		assertFalse(i.hasNext());
 
+		// Read the subscriptions update
+		assertTrue(reader.hasUserDefined(Tags.SUBSCRIPTIONS));
+		Subscriptions s = reader.readUserDefined(Tags.SUBSCRIPTIONS,
+				Subscriptions.class);
+		assertEquals(Collections.singletonList(group), s.getSubscriptions());
+		assertTrue(s.getTimestamp() > start);
+		assertTrue(s.getTimestamp() <= System.currentTimeMillis());
+
+		// Read the transports update
+		assertTrue(reader.hasUserDefined(Tags.TRANSPORTS));
+		Transports t = reader.readUserDefined(Tags.TRANSPORTS,
+				Transports.class);
+		assertEquals(Collections.singletonMap("foo", "bar"), t.getTransports());
+		assertTrue(t.getTimestamp() > start);
+		assertTrue(t.getTimestamp() <= System.currentTimeMillis());
+
 		assertTrue(reader.eof());
 	}
 
-- 
GitLab