diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index ec074b8d37c874f20ac6c161fd62e4ffda063a91..993af1b6e9dd13c815d95f2ef2e6006543631165 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -297,8 +297,11 @@ public interface DatabaseComponent {
 	void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException;
 
-	/** Subscribes to the given group. */
-	void subscribe(Group g) throws DbException;
+	/**
+	 * Subscribes to the given group, or returns false if the user already has
+	 * the maximum number of subscriptions.
+	 */
+	boolean subscribe(Group g) throws DbException;
 
 	/**
 	 * Unsubscribes from the given group. Any messages belonging to the group
diff --git a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
index e95c7462f1c0e58be3e2e7d6bf299da6bf3b0f1f..5790af212c2eb0f14c9eda975d509e64681737f8 100644
--- a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
+++ b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
@@ -11,6 +11,9 @@ public interface MessagingConstants {
 	 */
 	int MAX_PACKET_LENGTH = MIN_CONNECTION_LENGTH / 2;
 
+	/** The maximum number of groups a user may subscribe to. */
+	int MAX_SUBSCRIPTIONS = 3000;
+
 	/** The maximum number of properties per transport. */
 	int MAX_PROPERTIES_PER_TRANSPORT = 100;
 
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 9caffadad56cd42875d0f3fbac036c22fd184a69..dfc9d00d7809f6d4ff85c63b1c6a04ced20c7c89 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -132,11 +132,12 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Subscribes to the given group.
+	 * Subscribes to the given group, or returns false if the user already has
+	 * the maximum number of subscriptions.
 	 * <p>
 	 * Locking: subscription write.
 	 */
-	void addSubscription(T txn, Group g) throws DbException;
+	boolean addSubscription(T txn, Group g) throws DbException;
 
 	/**
 	 * Adds a new transport to the database.
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index a1d766df65aa79431a0bd13c14593632ce8af710..e18601d06d3fc1f223c17a61e0ee7feb5d290edc 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -1715,14 +1715,16 @@ DatabaseCleaner.Callback {
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
-	public void subscribe(Group g) throws DbException {
+	public boolean subscribe(Group g) throws DbException {
 		subscriptionLock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
+				boolean added = false;
 				if(!db.containsSubscription(txn, g.getId()))
-					db.addSubscription(txn, g);
+					added = db.addSubscription(txn, g);
 				db.commitTransaction(txn);
+				return added;
 			} catch(DbException e) {
 				db.abortTransaction(txn);
 				throw e;
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index dfa50aef8f2c317abb89d3fd2adf866a60803dcb..78d5adfe279c869986854a4edacbafb8e1795d9c 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -4,6 +4,7 @@ import static java.sql.Types.BINARY;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static net.sf.briar.api.Rating.UNRATED;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
 import static net.sf.briar.db.DatabaseConstants.RETENTION_MODULUS;
 import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
 
@@ -709,11 +710,21 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void addSubscription(Connection txn, Group g) throws DbException {
+	public boolean addSubscription(Connection txn, Group g) throws DbException {
 		PreparedStatement ps = null;
+		ResultSet rs = null;
 		try {
-			String sql = "INSERT INTO groups"
-					+ " (groupId, name, key) VALUES (?, ?, ?)";
+			String sql = "SELECT COUNT (groupId) from GROUPS";
+			ps = txn.prepareStatement(sql);
+			rs = ps.executeQuery();
+			if(!rs.next()) throw new DbStateException();
+			int count = rs.getInt(1);
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			if(count > MAX_SUBSCRIPTIONS) throw new DbStateException();
+			if(count == MAX_SUBSCRIPTIONS) return false;
+			sql = "INSERT INTO groups (groupId, name, key) VALUES (?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getId().getBytes());
 			ps.setString(2, g.getName());
@@ -721,6 +732,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
+			return true;
 		} catch(SQLException e) {
 			tryToClose(ps);
 			throw new DbException(e);
diff --git a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java
index 85244e31174f9c51963b3f31f82bd32f99a2fbb5..9795dfe78132d562f7f440e782f851c6ecfd209c 100644
--- a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java
@@ -221,8 +221,7 @@ class PacketReaderImpl implements PacketReader {
 		r.setMaxStringLength(MAX_PROPERTY_LENGTH);
 		Map<String, String> m = r.readMap(String.class, String.class);
 		r.resetMaxStringLength();
-		if(m.size() > MAX_PROPERTIES_PER_TRANSPORT)
-			throw new FormatException();
+		if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) throw new FormatException();
 		// Read the version number
 		long version = r.readInt64();
 		if(version < 0) throw new FormatException();
diff --git a/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java b/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java
index d73b86b44ed1011b989b5e3e39de0695d15de55f..6312e4ea31896d8682d5aa0e0533dbc3f4e73337 100644
--- a/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java
+++ b/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java
@@ -1,6 +1,7 @@
 package net.sf.briar.messaging;
 
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
 import static net.sf.briar.api.messaging.Types.SUBSCRIPTION_UPDATE;
 
 import java.io.IOException;
@@ -31,7 +32,8 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
 		// Read the subscriptions
 		List<Group> subs = new ArrayList<Group>();
 		r.readListStart();
-		while(!r.hasListEnd()) subs.add(groupReader.readStruct(r));
+		for(int i = 0; i < MAX_SUBSCRIPTIONS && !r.hasListEnd(); i++)
+			subs.add(groupReader.readStruct(r));
 		r.readListEnd();
 		// Read the version number
 		long version = r.readInt64();
diff --git a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
index 91209dadcbcfd7a1d706b712fe7bd41b46fd844e..ca6a804605759e34a8fbbfcf4d819f1ea1efeb3b 100644
--- a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
@@ -4,16 +4,24 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_AUTHOR_NAME_LENG
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTIES_PER_TRANSPORT;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTY_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PUBLIC_KEY_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
 
 import java.io.ByteArrayOutputStream;
+import java.security.KeyPair;
 import java.security.PrivateKey;
+import java.security.Signature;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Random;
 
 import net.sf.briar.BriarTestCase;
 import net.sf.briar.TestUtils;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.messaging.Ack;
 import net.sf.briar.api.messaging.Author;
@@ -26,10 +34,12 @@ import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Offer;
 import net.sf.briar.api.messaging.PacketWriter;
 import net.sf.briar.api.messaging.PacketWriterFactory;
+import net.sf.briar.api.messaging.SubscriptionUpdate;
+import net.sf.briar.api.messaging.TransportId;
+import net.sf.briar.api.messaging.TransportUpdate;
 import net.sf.briar.api.messaging.UniqueId;
 import net.sf.briar.clock.ClockModule;
 import net.sf.briar.crypto.CryptoModule;
-import net.sf.briar.messaging.MessagingModule;
 import net.sf.briar.serial.SerialModule;
 
 import org.junit.Test;
@@ -56,6 +66,37 @@ public class ConstantsTest extends BriarTestCase {
 		packetWriterFactory = i.getInstance(PacketWriterFactory.class);
 	}
 
+	@Test
+	public void testAgreementPublicKeys() throws Exception {
+		// Generate 10 agreement key pairs
+		for(int i = 0; i < 10; i++) {
+			KeyPair keyPair = crypto.generateSignatureKeyPair();
+			// Check the length of the public key
+			byte[] publicKey = keyPair.getPublic().getEncoded();
+			assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
+		}
+	}
+
+	@Test
+	public void testSignaturePublicKeys() throws Exception {
+		Random random = new Random();
+		Signature sig = crypto.getSignature();
+		// Generate 10 signature key pairs
+		for(int i = 0; i < 10; i++) {
+			KeyPair keyPair = crypto.generateSignatureKeyPair();
+			// Check the length of the public key
+			byte[] publicKey = keyPair.getPublic().getEncoded();
+			assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
+			// Sign some random data and check the length of the signature
+			byte[] toBeSigned = new byte[100];
+			random.nextBytes(toBeSigned);
+			sig.initSign(keyPair.getPrivate());
+			sig.update(toBeSigned);
+			byte[] signature = sig.sign();
+			assertTrue(signature.length <= MAX_SIGNATURE_LENGTH);
+		}
+	}
+
 	@Test
 	public void testMessageIdsFitIntoLargeAck() throws Exception {
 		testMessageIdsFitIntoAck(MAX_PACKET_LENGTH);
@@ -66,21 +107,6 @@ public class ConstantsTest extends BriarTestCase {
 		testMessageIdsFitIntoAck(1000);
 	}
 
-	private void testMessageIdsFitIntoAck(int length) throws Exception {
-		// Create an ack with as many message IDs as possible
-		ByteArrayOutputStream out = new ByteArrayOutputStream(length);
-		PacketWriter writer = packetWriterFactory.createPacketWriter(out,
-				true);
-		int maxMessages = writer.getMaxMessagesForAck(length);
-		Collection<MessageId> acked = new ArrayList<MessageId>();
-		for(int i = 0; i < maxMessages; i++) {
-			acked.add(new MessageId(TestUtils.getRandomId()));
-		}
-		writer.writeAck(new Ack(acked));
-		// Check the size of the serialised ack
-		assertTrue(out.size() <= length);
-	}
-
 	@Test
 	public void testMessageFitsIntoPacket() throws Exception {
 		// Create a maximum-length group
@@ -92,8 +118,10 @@ public class ConstantsTest extends BriarTestCase {
 		byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
 		Author author = authorFactory.createAuthor(authorName, authorPublic);
 		// Create a maximum-length message
-		PrivateKey groupPrivate = crypto.generateSignatureKeyPair().getPrivate();
-		PrivateKey authorPrivate = crypto.generateSignatureKeyPair().getPrivate();
+		PrivateKey groupPrivate =
+				crypto.generateSignatureKeyPair().getPrivate();
+		PrivateKey authorPrivate =
+				crypto.generateSignatureKeyPair().getPrivate();
 		String subject = createRandomString(MAX_SUBJECT_LENGTH);
 		byte[] body = new byte[MAX_BODY_LENGTH];
 		Message message = messageFactory.createMessage(null, group,
@@ -116,16 +144,66 @@ public class ConstantsTest extends BriarTestCase {
 		testMessageIdsFitIntoOffer(1000);
 	}
 
+	@Test
+	public void testPropertiesFitIntoTransportUpdate() throws Exception {
+		// Create the maximum number of properties with the maximum length
+		TransportProperties p = new TransportProperties();
+		for(int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT; i++) {
+			String key = createRandomString(MAX_PROPERTY_LENGTH);
+			String value = createRandomString(MAX_PROPERTY_LENGTH);
+			p.put(key, value);
+		}
+		// Create a maximum-length transport update
+		TransportId id = new TransportId(TestUtils.getRandomId());
+		TransportUpdate u = new TransportUpdate(id, p, Long.MAX_VALUE);
+		// Serialise the update
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
+		writer.writeTransportUpdate(u);
+		// Check the size of the serialised transport update
+		assertTrue(out.size() <= MAX_PACKET_LENGTH);
+	}
+
+	@Test
+	public void testGroupsFitIntoSubscriptionUpdate() throws Exception {
+		// Create the maximum number of maximum-length groups
+		Collection<Group> subs = new ArrayList<Group>();
+		for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
+			String groupName = createRandomString(MAX_GROUP_NAME_LENGTH);
+			byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
+			subs.add(groupFactory.createGroup(groupName, groupPublic));
+		}
+		// Create a maximum-length subscription update
+		SubscriptionUpdate u = new SubscriptionUpdate(subs, Long.MAX_VALUE);
+		// Serialise the update
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
+		writer.writeSubscriptionUpdate(u);
+		// Check the size of the serialised subscription update
+		assertTrue(out.size() <= MAX_PACKET_LENGTH);
+	}
+
+	private void testMessageIdsFitIntoAck(int length) throws Exception {
+		// Create an ack with as many message IDs as possible
+		ByteArrayOutputStream out = new ByteArrayOutputStream(length);
+		PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
+		int maxMessages = writer.getMaxMessagesForAck(length);
+		Collection<MessageId> acked = new ArrayList<MessageId>();
+		for(int i = 0; i < maxMessages; i++)
+			acked.add(new MessageId(TestUtils.getRandomId()));
+		writer.writeAck(new Ack(acked));
+		// Check the size of the serialised ack
+		assertTrue(out.size() <= length);
+	}
+
 	private void testMessageIdsFitIntoOffer(int length) throws Exception {
 		// Create an offer with as many message IDs as possible
 		ByteArrayOutputStream out = new ByteArrayOutputStream(length);
-		PacketWriter writer = packetWriterFactory.createPacketWriter(out,
-				true);
+		PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
 		int maxMessages = writer.getMaxMessagesForOffer(length);
 		Collection<MessageId> offered = new ArrayList<MessageId>();
-		for(int i = 0; i < maxMessages; i++) {
+		for(int i = 0; i < maxMessages; i++)
 			offered.add(new MessageId(TestUtils.getRandomId()));
-		}
 		writer.writeOffer(new Offer(offered));
 		// Check the size of the serialised offer
 		assertTrue(out.size() <= length);