diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index ab1bc0a9dc55ae0bdeee22e7ca79e0ff1aeb2f69..b67ff2fd484d975ee5f5b83ede9db76016d29b95 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -45,9 +45,6 @@ public interface DatabaseComponent {
 	 */
 	ContactId addContact(Author remote, AuthorId local) throws DbException;
 
-	/** Adds a group to the given contact's subscriptions. */
-	void addContactGroup(ContactId c, Group g) throws DbException;
-
 	/**
 	 * Subscribes to a group, or returns false if the user already has the
 	 * maximum number of subscriptions.
@@ -111,12 +108,6 @@ public interface DatabaseComponent {
 	Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength,
 			int maxLatency) throws DbException;
 
-	/**
-	 * Returns all groups belonging to the given client to which the user could
-	 * subscribe.
-	 */
-	Collection<Group> getAvailableGroups(ClientId c) throws DbException;
-
 	/** Returns the contact with the given ID. */
 	Contact getContact(ContactId c) throws DbException;
 
@@ -180,9 +171,6 @@ public interface DatabaseComponent {
 	/** Returns all settings in the given namespace. */
 	Settings getSettings(String namespace) throws DbException;
 
-	/** Returns all contacts who subscribe to the given group. */
-	Collection<Contact> getSubscribers(GroupId g) throws DbException;
-
 	/** Returns all transport keys for the given transport. */
 	Map<ContactId, TransportKeys> getTransportKeys(TransportId t)
 			throws DbException;
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index bc502f42e74a254116fd4d73ee7de6d03ef49c42..10d95b4e97f044391bf96dd311a683e3e4176c40 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -83,13 +83,6 @@ interface Database<T> {
 	ContactId addContact(T txn, Author remote, AuthorId local)
 			throws DbException;
 
-	/**
-	 * Adds a group to the given contact's subscriptions.
-	 * <p>
-	 * Locking: write.
-	 */
-	void addContactGroup(T txn, ContactId c, Group g) throws DbException;
-
 	/**
 	 * Subscribes to a group, or returns false if the user already has the
 	 * maximum number of subscriptions.
@@ -223,14 +216,6 @@ interface Database<T> {
 	 */
 	int countOfferedMessages(T txn, ContactId c) throws DbException;
 
-	/**
-	 * Returns all groups belonging to the given client to which the user could
-	 * subscribe.
-	 * <p>
-	 * Locking: read.
-	 */
-	Collection<Group> getAvailableGroups(T txn, ClientId c) throws DbException;
-
 	/**
 	 * Returns the contact with the given ID.
 	 * <p>
@@ -411,13 +396,6 @@ interface Database<T> {
 	 */
 	Settings getSettings(T txn, String namespace) throws DbException;
 
-	/**
-	 * Returns all contacts who subscribe to the given group.
-	 * <p>
-	 * Locking: read.
-	 */
-	Collection<Contact> getSubscribers(T txn, GroupId g) throws DbException;
-
 	/**
 	 * Returns all transport keys for the given transport.
 	 * <p>
@@ -624,25 +602,6 @@ interface Database<T> {
 	void setReorderingWindow(T txn, ContactId c, TransportId t,
 			long rotationPeriod, long base, byte[] bitmap) throws DbException;
 
-	/**
-	 * Updates the given contact's subscriptions and returns true, unless an
-	 * update with an equal or higher version number has already been received
-	 * from the contact.
-	 * <p>
-	 * Locking: write.
-	 */
-	boolean setGroups(T txn, ContactId c, Collection<Group> groups,
-			long version) throws DbException;
-
-	/**
-	 * Records a subscription ack from the given contact for the given version,
-	 * unless the contact has already acked an equal or higher version.
-	 * <p>
-	 * Locking: write.
-	 */
-	void setSubscriptionUpdateAcked(T txn, ContactId c, long version)
-			throws DbException;
-
 	/**
 	 * Makes a group visible or invisible to future contacts by default.
 	 * <p>
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index c75793fc33929a6bcff51ca28a64d980f1dca0fe..4334226663cc7ad91625bcb4ba8d507e67a10092 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -161,22 +161,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
-	public void addContactGroup(ContactId c, Group g) throws DbException {
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				db.addContactGroup(txn, c, g);
-				db.commitTransaction(txn);
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-	}
-
 	public boolean addGroup(Group g) throws DbException {
 		boolean added = false;
 		lock.writeLock().lock();
@@ -431,23 +415,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		return Collections.unmodifiableList(messages);
 	}
 
-	public Collection<Group> getAvailableGroups(ClientId c) throws DbException {
-		lock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				Collection<Group> groups = db.getAvailableGroups(txn, c);
-				db.commitTransaction(txn);
-				return groups;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.readLock().unlock();
-		}
-	}
-
 	public Contact getContact(ContactId c) throws DbException {
 		lock.readLock().lock();
 		try {
@@ -750,23 +717,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
-	public Collection<Contact> getSubscribers(GroupId g) throws DbException {
-		lock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				Collection<Contact> contacts = db.getSubscribers(txn, g);
-				db.commitTransaction(txn);
-				return contacts;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.readLock().unlock();
-		}
-	}
-
 	public Map<ContactId, TransportKeys> getTransportKeys(TransportId t)
 			throws DbException {
 		lock.readLock().lock();
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index cc7a74a318e71d231291bbee468ce478ecc6e279..92c808167f2f4bd4119775f5b8e4f4c38e1f8a34 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -37,12 +37,10 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
@@ -132,31 +130,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES groups (groupId)"
 					+ " ON DELETE CASCADE)";
 
-	private static final String CREATE_CONTACT_GROUPS =
-			"CREATE TABLE contactGroups"
-					+ " (contactId INT NOT NULL,"
-					+ " groupId HASH NOT NULL," // Not a foreign key
-					+ " clientId HASH NOT NULL,"
-					+ " descriptor BINARY NOT NULL,"
-					+ " PRIMARY KEY (contactId, groupId),"
-					+ " FOREIGN KEY (contactId)"
-					+ " REFERENCES contacts (contactId)"
-					+ " ON DELETE CASCADE)";
-
-	private static final String CREATE_GROUP_VERSIONS =
-			"CREATE TABLE groupVersions"
-					+ " (contactId INT NOT NULL,"
-					+ " localVersion BIGINT NOT NULL,"
-					+ " localAcked BIGINT NOT NULL,"
-					+ " remoteVersion BIGINT NOT NULL,"
-					+ " remoteAcked BOOLEAN NOT NULL,"
-					+ " expiry BIGINT NOT NULL,"
-					+ " txCount INT NOT NULL,"
-					+ " PRIMARY KEY (contactId),"
-					+ " FOREIGN KEY (contactid)"
-					+ " REFERENCES contacts (contactId)"
-					+ " ON DELETE CASCADE)";
-
 	private static final String CREATE_MESSAGES =
 			"CREATE TABLE messages"
 					+ " (messageId HASH NOT NULL,"
@@ -351,8 +324,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			s.executeUpdate(insertTypeNames(CREATE_GROUPS));
 			s.executeUpdate(insertTypeNames(CREATE_GROUP_METADATA));
 			s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
-			s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
-			s.executeUpdate(insertTypeNames(CREATE_GROUP_VERSIONS));
 			s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
 			s.executeUpdate(insertTypeNames(CREATE_MESSAGE_METADATA));
 			s.executeUpdate(insertTypeNames(CREATE_OFFERS));
@@ -565,16 +536,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					if (rows != 1) throw new DbStateException();
 				ps.close();
 			}
-			// Create a group version row
-			sql = "INSERT INTO groupVersions (contactId, localVersion,"
-					+ " localAcked, remoteVersion, remoteAcked, expiry,"
-					+ " txCount)"
-					+ " VALUES (?, 1, 0, 0, TRUE, 0, 0)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			affected = ps.executeUpdate();
-			if (affected != 1) throw new DbStateException();
-			ps.close();
 			return c;
 		} catch (SQLException e) {
 			tryToClose(rs);
@@ -583,40 +544,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void addContactGroup(Connection txn, ContactId c, Group g)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT NULL FROM contactGroups"
-					+ " WHERE contactId = ? AND groupId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setBytes(2, g.getId().getBytes());
-			rs = ps.executeQuery();
-			boolean found = rs.next();
-			if (rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			if (found) return;
-			sql = "INSERT INTO contactGroups"
-					+ " (contactId, groupId, clientId, descriptor)"
-					+ " VALUES (?, ?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setBytes(2, g.getId().getBytes());
-			ps.setBytes(3, g.getClientId().getBytes());
-			ps.setBytes(4, g.getDescriptor());
-			int affected = ps.executeUpdate();
-			if (affected != 1) throw new DbStateException();
-			ps.close();
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public boolean addGroup(Connection txn, Group g) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -853,16 +780,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
-			// Bump the subscription version
-			sql = "UPDATE groupVersions"
-					+ " SET localVersion = localVersion + 1,"
-					+ " expiry = 0, txCount = 0"
-					+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			affected = ps.executeUpdate();
-			if (affected != 1) throw new DbStateException();
-			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
 			throw new DbException(e);
@@ -1070,39 +987,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<Group> getAvailableGroups(Connection txn, ClientId c)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT DISTINCT cg.groupId, cg.descriptor"
-					+ " FROM contactGroups AS cg"
-					+ " LEFT OUTER JOIN groups AS g"
-					+ " ON cg.groupId = g.groupId"
-					+ " WHERE cg.clientId = ?"
-					+ " AND g.groupId IS NULL"
-					+ " GROUP BY cg.groupId";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, c.getBytes());
-			rs = ps.executeQuery();
-			List<Group> groups = new ArrayList<Group>();
-			Set<GroupId> ids = new HashSet<GroupId>();
-			while (rs.next()) {
-				GroupId id = new GroupId(rs.getBytes(1));
-				if (!ids.add(id)) throw new DbStateException();
-				byte[] descriptor = rs.getBytes(2);
-				groups.add(new Group(id, c, descriptor));
-			}
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableList(groups);
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Contact getContact(Connection txn, ContactId c) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -1472,15 +1356,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT m.messageId FROM messages AS m"
-					+ " JOIN contactGroups AS cg"
-					+ " ON m.groupId = cg.groupId"
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
-					+ " AND cg.contactId = gv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
-					+ " AND cg.contactId = s.contactId"
-					+ " WHERE cg.contactId = ?"
+					+ " AND gv.contactId = s.contactId"
+					+ " WHERE gv.contactId = ?"
 					+ " AND valid = ? AND shared = TRUE"
 					+ " AND seen = FALSE AND requested = FALSE"
 					+ " AND s.expiry < ?"
@@ -1534,15 +1415,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT length, m.messageId FROM messages AS m"
-					+ " JOIN contactGroups AS cg"
-					+ " ON m.groupId = cg.groupId"
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
-					+ " AND cg.contactId = gv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
-					+ " AND cg.contactId = s.contactId"
-					+ " WHERE cg.contactId = ?"
+					+ " AND gv.contactId = s.contactId"
+					+ " WHERE gv.contactId = ?"
 					+ " AND valid = ? AND shared = TRUE"
 					+ " AND seen = FALSE"
 					+ " AND s.expiry < ?"
@@ -1623,15 +1501,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT length, m.messageId FROM messages AS m"
-					+ " JOIN contactGroups AS cg"
-					+ " ON m.groupId = cg.groupId"
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
-					+ " AND cg.contactId = gv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
-					+ " AND cg.contactId = s.contactId"
-					+ " WHERE cg.contactId = ?"
+					+ " AND gv.contactId = s.contactId"
+					+ " WHERE gv.contactId = ?"
 					+ " AND valid = ? AND shared = TRUE"
 					+ " AND seen = FALSE AND requested = TRUE"
 					+ " AND s.expiry < ?"
@@ -1680,42 +1555,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<Contact> getSubscribers(Connection txn, GroupId g)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT c.contactId, authorId, c.name, publicKey,"
-					+ " localAuthorId, status"
-					+ " FROM contacts AS c"
-					+ " JOIN contactGroups AS cg"
-					+ " ON c.contactId = cg.contactId"
-					+ " WHERE groupId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getBytes());
-			rs = ps.executeQuery();
-			List<Contact> contacts = new ArrayList<Contact>();
-			while (rs.next()) {
-				ContactId contactId = new ContactId(rs.getInt(1));
-				AuthorId authorId = new AuthorId(rs.getBytes(2));
-				String name = rs.getString(3);
-				byte[] publicKey = rs.getBytes(4);
-				Author author = new Author(authorId, name, publicKey);
-				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
-				StorageStatus status = StorageStatus.fromValue(rs.getInt(6));
-				contacts.add(new Contact(contactId, author, localAuthorId,
-						status));
-			}
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableList(contacts);
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
 			TransportId t) throws DbException {
 		PreparedStatement ps = null;
@@ -2097,44 +1936,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public void removeGroup(Connection txn, GroupId g) throws DbException {
 		PreparedStatement ps = null;
-		ResultSet rs = null;
 		try {
-			// Find out which contacts are affected
-			String sql = "SELECT contactId FROM groupVisibilities"
-					+ " WHERE groupId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getBytes());
-			rs = ps.executeQuery();
-			Collection<Integer> visible = new ArrayList<Integer>();
-			while (rs.next()) visible.add(rs.getInt(1));
-			rs.close();
-			ps.close();
-			// Delete the group
-			sql = "DELETE FROM groups WHERE groupId = ?";
+			String sql = "DELETE FROM groups WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getBytes());
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
-			if (visible.isEmpty()) return;
-			// Bump the subscription versions for the affected contacts
-			sql = "UPDATE groupVersions"
-					+ " SET localVersion = localVersion + 1, expiry = 0"
-					+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			for (Integer c : visible) {
-				ps.setInt(1, c);
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != visible.size())
-				throw new DbStateException();
-			for (int rows : batchAffected)
-				if (rows != 1) throw new DbStateException();
-			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
-			tryToClose(rs);
 			throw new DbException(e);
 		}
 	}
@@ -2241,15 +2051,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
-			// Bump the subscription version
-			sql = "UPDATE groupVersions"
-					+ " SET localVersion = localVersion + 1, expiry = 0"
-					+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			affected = ps.executeUpdate();
-			if (affected != 1) throw new DbStateException();
-			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
 			throw new DbException(e);
@@ -2364,113 +2165,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean setGroups(Connection txn, ContactId c,
-			Collection<Group> groups, long version) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			// Mark the update as needing to be acked
-			String sql = "UPDATE groupVersions"
-					+ " SET remoteVersion = ?, remoteAcked = FALSE"
-					+ " WHERE contactId = ? AND remoteVersion < ?";
-			ps = txn.prepareStatement(sql);
-			ps.setLong(1, version);
-			ps.setInt(2, c.getInt());
-			ps.setLong(3, version);
-			int affected = ps.executeUpdate();
-			if (affected < 0 || affected > 1) throw new DbStateException();
-			ps.close();
-			// Return false if the update is obsolete
-			if (affected == 0) return false;
-			// Find any messages in groups that are being removed
-			Set<GroupId> newIds = new HashSet<GroupId>();
-			for (Group g : groups) newIds.add(g.getId());
-			sql = "SELECT messageId, m.groupId"
-					+ " FROM messages AS m"
-					+ " JOIN contactGroups AS cg"
-					+ " ON m.groupId = cg.groupId"
-					+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			rs = ps.executeQuery();
-			List<MessageId> removed = new ArrayList<MessageId>();
-			while (rs.next()) {
-				if (!newIds.contains(new GroupId(rs.getBytes(2))))
-					removed.add(new MessageId(rs.getBytes(1)));
-			}
-			rs.close();
-			ps.close();
-			// Reset any statuses for messages in groups that are being removed
-			if (!removed.isEmpty()) {
-				sql = "UPDATE statuses SET ack = FALSE, seen = FALSE,"
-						+ " requested = FALSE, expiry = 0, txCount = 0"
-						+ " WHERE contactId = ? AND messageId = ?";
-				ps = txn.prepareStatement(sql);
-				ps.setInt(1, c.getInt());
-				for (MessageId m : removed) {
-					ps.setBytes(2, m.getBytes());
-					ps.addBatch();
-				}
-				int[] batchAffected = ps.executeBatch();
-				if (batchAffected.length != removed.size())
-					throw new DbStateException();
-				for (int rows : batchAffected)
-					if (rows < 0) throw new DbStateException();
-				ps.close();
-			}
-			// Delete the existing subscriptions, if any
-			sql = "DELETE FROM contactGroups WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.executeUpdate();
-			// Store the new subscriptions, if any
-			if (groups.isEmpty()) return true;
-			sql = "INSERT INTO contactGroups"
-					+ " (contactId, groupId, clientId, descriptor)"
-					+ " VALUES (?, ?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			for (Group g : groups) {
-				ps.setBytes(2, g.getId().getBytes());
-				ps.setBytes(3, g.getClientId().getBytes());
-				ps.setBytes(4, g.getDescriptor());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != groups.size())
-				throw new DbStateException();
-			for (int rows : batchAffected)
-				if (rows != 1) throw new DbStateException();
-			ps.close();
-			return true;
-		} catch (SQLException e) {
-			tryToClose(ps);
-			tryToClose(rs);
-			throw new DbException(e);
-		}
-	}
-
-	public void setSubscriptionUpdateAcked(Connection txn, ContactId c,
-			long version) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			String sql = "UPDATE groupVersions SET localAcked = ?"
-					+ " WHERE contactId = ?"
-					+ " AND localAcked < ? AND localVersion >= ?";
-			ps = txn.prepareStatement(sql);
-			ps.setLong(1, version);
-			ps.setInt(2, c.getInt());
-			ps.setLong(3, version);
-			ps.setLong(4, version);
-			int affected = ps.executeUpdate();
-			if (affected < 0 || affected > 1) throw new DbStateException();
-			ps.close();
-		} catch (SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public void setVisibleToAll(Connection txn, GroupId g, boolean all)
 			throws DbException {
 		PreparedStatement ps = null;
diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index 6b846d6714153d21a1b8ed36c01ab4ace2dfc633..77f4f7dd4fcaa9cdbc6854ae2dd060c8b5148628 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -143,16 +143,8 @@ class ForumManagerImpl implements ForumManager {
 
 	@Override
 	public Collection<Forum> getAvailableForums() throws DbException {
-		Collection<Group> groups = db.getAvailableGroups(CLIENT_ID);
-		List<Forum> forums = new ArrayList<Forum>(groups.size());
-		for (Group g : groups) {
-			try {
-				forums.add(parseForum(g));
-			} catch (FormatException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			}
-		}
-		return Collections.unmodifiableList(forums);
+		// TODO
+		return Collections.emptyList();
 	}
 
 	private Forum parseForum(Group g) throws FormatException {
@@ -272,7 +264,8 @@ class ForumManagerImpl implements ForumManager {
 
 	@Override
 	public Collection<Contact> getSubscribers(GroupId g) throws DbException {
-		return db.getSubscribers(g);
+		// TODO
+		return Collections.emptyList();
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
index ea1a852a90303d5f8072982f81e3fb9450fb8825..286d97f5485a76b91135570a6d7f5065e1822e41 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -75,7 +75,6 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 			Group g = getConversationGroup(db.getContact(c));
 			// Subscribe to the group and share it with the contact
 			db.addGroup(g);
-			db.addContactGroup(c, g);
 			db.setVisibility(g.getId(), Collections.singletonList(c));
 			// Attach the contact ID to the group
 			BdfDictionary d = new BdfDictionary();
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
index 1df75db183f034f588f0184a951ecc8143226020..c2a0c25e8255f89d132116eea5abd27e40203387 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -98,7 +98,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 			Group g = getContactGroup(db.getContact(c));
 			// Subscribe to the group and share it with the contact
 			db.addGroup(g);
-			db.addContactGroup(c, g);
 			db.setVisibility(g.getId(), Collections.singletonList(c));
 			// Copy the latest local properties into the group
 			DeviceId dev = db.getDeviceId();
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index 3b127a88bcd7e527224f7b73adbed609d2df9758..46d6e274c985b0c848ea79cae586ccf58a299482 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -62,7 +62,6 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	private final File testDir = TestUtils.getTestDirectory();
 	private final Random random = new Random();
-	private final ClientId clientId;
 	private final GroupId groupId;
 	private final Group group;
 	private final Author author;
@@ -77,8 +76,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	private final ContactId contactId;
 
 	public H2DatabaseTest() throws Exception {
-		clientId = new ClientId(TestUtils.getRandomId());
 		groupId = new GroupId(TestUtils.getRandomId());
+		ClientId clientId = new ClientId(TestUtils.getRandomId());
 		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		group = new Group(groupId, clientId, descriptor);
 		AuthorId authorId = new AuthorId(TestUtils.getRandomId());
@@ -174,7 +173,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, VALID, true);
 
 		// The message has no status yet, so it should not be sendable
@@ -212,7 +210,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, UNKNOWN, true);
 		db.addStatus(txn, contactId, messageId, false, false);
 
@@ -251,7 +248,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, VALID, false);
 		db.addStatus(txn, contactId, messageId, false, false);
 
@@ -280,44 +276,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.close();
 	}
 
-	@Test
-	public void testSendableMessagesMustBeSubscribed() throws Exception {
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a contact, subscribe to a group and store a message
-		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, groupId);
-		db.addMessage(txn, message, VALID, true);
-		db.addStatus(txn, contactId, messageId, false, false);
-
-		// The contact is not subscribed, so the message should not be sendable
-		Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
-				ONE_MEGABYTE);
-		assertTrue(ids.isEmpty());
-		ids = db.getMessagesToOffer(txn, contactId, 100);
-		assertTrue(ids.isEmpty());
-
-		// The contact subscribing should make the message sendable
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
-		ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
-		assertEquals(Collections.singletonList(messageId), ids);
-		ids = db.getMessagesToOffer(txn, contactId, 100);
-		assertEquals(Collections.singletonList(messageId), ids);
-
-		// The contact unsubscribing should make the message unsendable
-		db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
-		ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
-		assertTrue(ids.isEmpty());
-		ids = db.getMessagesToOffer(txn, contactId, 100);
-		assertTrue(ids.isEmpty());
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
 	@Test
 	public void testSendableMessagesMustFitCapacity() throws Exception {
 		Database<Connection> db = open(false);
@@ -328,7 +286,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, VALID, true);
 		db.addStatus(txn, contactId, messageId, false, false);
 
@@ -354,7 +311,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, VALID, true);
 		db.addStatus(txn, contactId, messageId, false, false);
 
@@ -393,7 +349,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
+		db.addVisibility(txn, contactId, groupId);
 
 		// Add some messages to ack
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
@@ -430,7 +386,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, VALID, true);
 		db.addStatus(txn, contactId, messageId, false, false);
 
@@ -595,7 +550,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 
 		// The message is not in the database
 		assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
@@ -610,10 +564,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
-		// Add a contact with a subscription
+		// Add a contact
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 
 		// There's no local subscription for the group
 		assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
@@ -632,7 +585,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 		db.addMessage(txn, message, VALID, true);
 		db.addStatus(txn, contactId, messageId, false, false);
 
@@ -838,57 +790,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.close();
 	}
 
-	@Test
-	public void testGetAvailableGroups() throws Exception {
-		ContactId contactId1 = new ContactId(2);
-		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
-		Author author1 = new Author(authorId1, "Carol",
-				new byte[MAX_PUBLIC_KEY_LENGTH]);
-
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add two contacts who subscribe to a group
-		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		assertEquals(contactId1, db.addContact(txn, author1, localAuthorId));
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
-		db.setGroups(txn, contactId1, Collections.singletonList(group), 1);
-
-		// The group should be available
-		assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
-		assertEquals(Collections.singletonList(group),
-				db.getAvailableGroups(txn, clientId));
-
-		// Subscribe to the group - it should no longer be available
-		db.addGroup(txn, group);
-		assertEquals(Collections.singletonList(group),
-				db.getGroups(txn, clientId));
-		assertEquals(Collections.emptyList(),
-				db.getAvailableGroups(txn, clientId));
-
-		// Unsubscribe from the group - it should be available again
-		db.removeGroup(txn, groupId);
-		assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
-		assertEquals(Collections.singletonList(group),
-				db.getAvailableGroups(txn, clientId));
-
-		// The first contact unsubscribes - it should still be available
-		db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
-		assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
-		assertEquals(Collections.singletonList(group),
-				db.getAvailableGroups(txn, clientId));
-
-		// The second contact unsubscribes - it should no longer be available
-		db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2);
-		assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
-		assertEquals(Collections.emptyList(),
-				db.getAvailableGroups(txn, clientId));
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
 	@Test
 	public void testGetContactsByLocalAuthorId() throws Exception {
 		Database<Connection> db = open(false);
@@ -943,46 +844,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.close();
 	}
 
-	@Test
-	public void testContactUnsubscribingResetsMessageStatus() throws Exception {
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a contact who subscribes to a group
-		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
-
-		// Subscribe to the group and make it visible to the contact
-		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, groupId);
-
-		// Add a message - it should be sendable to the contact
-		db.addMessage(txn, message, VALID, true);
-		db.addStatus(txn, contactId, messageId, false, false);
-		Collection<MessageId> sendable = db.getMessagesToSend(txn, contactId,
-				ONE_MEGABYTE);
-		assertEquals(Collections.singletonList(messageId), sendable);
-
-		// Mark the message as seen - it should no longer be sendable
-		db.raiseSeenFlag(txn, contactId, messageId);
-		sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
-		assertEquals(Collections.emptyList(), sendable);
-
-		// The contact unsubscribes - the message should not be sendable
-		db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
-		sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
-		assertEquals(Collections.emptyList(), sendable);
-
-		// The contact resubscribes - the message should be sendable again
-		db.setGroups(txn, contactId, Collections.singletonList(group), 3);
-		sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
-		assertEquals(Collections.singletonList(messageId), sendable);
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
 	@Test
 	public void testGroupMetadata() throws Exception {
 		Database<Connection> db = open(false);
@@ -1086,12 +947,11 @@ public class H2DatabaseTest extends BriarTestCase {
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
-		// Add a contact who subscribes to a group
+		// Add a contact
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		db.setGroups(txn, contactId, Collections.singletonList(group), 1);
 
-		// Subscribe to the group and make it visible to the contact
+		// Subscribe to a group and make it visible to the contact
 		db.addGroup(txn, group);
 		db.addVisibility(txn, contactId, groupId);