From 631f4e74b587ded31547a0f4138a94817c5eff2f Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Tue, 11 Oct 2011 19:08:10 +0100
Subject: [PATCH] Nudge the database API in the direction of sanity.

---
 api/net/sf/briar/api/TransportId.java         |   8 +-
 .../sf/briar/api/db/DatabaseComponent.java    |  37 ++-
 components/net/sf/briar/db/Database.java      |  56 ++--
 .../sf/briar/db/DatabaseComponentImpl.java    | 143 +++++-----
 components/net/sf/briar/db/JdbcDatabase.java  | 268 +++++++++---------
 .../sf/briar/protocol/TransportReader.java    |   4 +-
 .../sf/briar/db/DatabaseComponentTest.java    |  30 +-
 test/net/sf/briar/db/H2DatabaseTest.java      | 103 +++----
 .../briar/protocol/writers/ConstantsTest.java |   3 +-
 9 files changed, 307 insertions(+), 345 deletions(-)

diff --git a/api/net/sf/briar/api/TransportId.java b/api/net/sf/briar/api/TransportId.java
index 1c5ab82ed4..591ba0769c 100644
--- a/api/net/sf/briar/api/TransportId.java
+++ b/api/net/sf/briar/api/TransportId.java
@@ -8,7 +8,7 @@ import net.sf.briar.api.serial.Writer;
 /**
  * Type-safe wrapper for an integer that uniquely identifies a transport plugin.
  */
-public class TransportId implements Writable, Comparable<TransportId> {
+public class TransportId implements Writable {
 
 	public static final int MIN_ID = 0;
 	public static final int MAX_ID = 65535;
@@ -38,10 +38,4 @@ public class TransportId implements Writable, Comparable<TransportId> {
 	public int hashCode() {
 		return id;
 	}
-
-	public int compareTo(TransportId t) {
-		if(id < t.id) return -1;
-		if(id > t.id) return 1;
-		return 0;
-	}
 }
diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index 99e7a92466..80afa5e11f 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -101,6 +101,9 @@ public interface DatabaseComponent {
 	void generateTransportUpdate(ContactId c, TransportWriter t) throws
 	DbException, IOException;
 
+	/** Returns the configuration for the given transport. */
+	TransportConfig getConfig(TransportId t) throws DbException;
+
 	/**
 	 * Returns an outgoing connection number for the given contact and
 	 * transport.
@@ -124,8 +127,8 @@ public interface DatabaseComponent {
 	/** Returns the user's rating for the given author. */
 	Rating getRating(AuthorId a) throws DbException;
 
-	/** Returns all remote transport properties. */
-	Map<TransportId, Map<ContactId, TransportProperties>> getRemoteTransports()
+	/** Returns all remote transport properties for the given transport. */
+	Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
 	throws DbException;
 
 	/** Returns the secret shared with the given contact. */
@@ -134,9 +137,6 @@ public interface DatabaseComponent {
 	/** Returns the set of groups to which the user subscribes. */
 	Collection<Group> getSubscriptions() throws DbException;
 
-	/** Returns the configuration for the given transport. */
-	TransportConfig getTransportConfig(TransportId t) throws DbException;
-
 	/** Returns the contacts to which the given group is visible. */
 	Collection<ContactId> getVisibility(GroupId g) throws DbException;
 
@@ -171,6 +171,12 @@ public interface DatabaseComponent {
 	/** Removes a contact (and all associated state) from the database. */
 	void removeContact(ContactId c) throws DbException;
 
+	/**
+	 * Sets the configuration for the given transport, replacing any existing
+	 * configuration for that transport.
+	 */
+	void setConfig(TransportId t, TransportConfig c) throws DbException;
+
 	/**
 	 * Sets the connection reordering window for the given contact and
 	 * transport.
@@ -178,26 +184,19 @@ public interface DatabaseComponent {
 	void setConnectionWindow(ContactId c, TransportId t, ConnectionWindow w)
 	throws DbException;
 
+	/**
+	 * Sets the local transport properties for the given transport, replacing
+	 * any existing properties for that transport.
+	 */
+	void setLocalProperties(TransportId t, TransportProperties p)
+	throws DbException;
+
 	/** Records the user's rating for the given author. */
 	void setRating(AuthorId a, Rating r) throws DbException;
 
 	/** Records the given messages as having been seen by the given contact. */
 	void setSeen(ContactId c, Collection<MessageId> seen) throws DbException;
 
-	/**
-	 * Sets the configuration for the given transport, replacing any existing
-	 * configuration for that transport.
-	 */
-	void setTransportConfig(TransportId t, TransportConfig config)
-	throws DbException;
-
-	/**
-	 * Sets the transport properties for the given transport, replacing any
-	 * existing properties for that transport.
-	 */
-	void setTransportProperties(TransportId t, TransportProperties properties)
-	throws DbException;
-
 	/**
 	 * Makes the given group visible to the given set of contacts and invisible
 	 * to any other contacts.
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index c8e01cce3d..542c04729b 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -170,6 +170,13 @@ interface Database<T> {
 	 */
 	Collection<BatchId> getBatchesToAck(T txn, ContactId c) throws DbException;
 
+	/**
+	 * Returns the configuration for the given transport.
+	 * <p>
+	 * Locking: transports read.
+	 */
+	TransportConfig getConfig(T txn, TransportId t) throws DbException;
+
 	/**
 	 * Allocates and returns a connection number for the given contact and
 	 * transport.
@@ -280,12 +287,12 @@ interface Database<T> {
 	Rating getRating(T txn, AuthorId a) throws DbException;
 
 	/**
-	 * Returns all remote transport properties.
+	 * Returns all remote properties for the given transport.
 	 * <p>
 	 * Locking: contacts read, transports read.
 	 */
-	Map<TransportId, Map<ContactId, TransportProperties>>
-	getRemoteTransports(T txn) throws DbException;
+	Map<ContactId, TransportProperties> getRemoteProperties(T txn,
+			TransportId t) throws DbException;
 
 	/**
 	 * Returns the sendability score of the given group message.
@@ -335,13 +342,6 @@ interface Database<T> {
 	 */
 	Collection<Group> getSubscriptions(T txn, ContactId c) throws DbException;
 
-	/**
-	 * Returns the configuration for the given transport.
-	 * <p>
-	 * Locking: transports read.
-	 */
-	TransportConfig getTransportConfig(T txn, TransportId t) throws DbException;
-
 	/**
 	 * Returns the contacts to which the given group is visible.
 	 * <p>
@@ -414,6 +414,15 @@ interface Database<T> {
 	 */
 	void removeSubscription(T txn, GroupId g) throws DbException;
 
+	/**
+	 * Sets the configuration for the given transport, replacing any existing
+	 * configuration for that transport.
+	 * <p>
+	 * Locking: transports write.
+	 */
+	void setConfig(T txn, TransportId t, TransportConfig config)
+	throws DbException;
+
 	/**
 	 * Sets the connection reordering window for the given contact and
 	 * transport.
@@ -423,6 +432,15 @@ interface Database<T> {
 	void setConnectionWindow(T txn, ContactId c, TransportId t,
 			ConnectionWindow w) throws DbException;
 
+	/**
+	 * Sets the local transport properties for the given transport, replacing
+	 * any existing properties for that transport.
+	 * <p>
+	 * Locking: transports write.
+	 */
+	void setLocalProperties(T txn, TransportId t, TransportProperties p)
+	throws DbException;
+
 	/**
 	 * Sets the user's rating for the given author.
 	 * <p>
@@ -475,24 +493,6 @@ interface Database<T> {
 	void setSubscriptionTimestamp(T txn, ContactId c, long timestamp)
 	throws DbException;
 
-	/**
-	 * Sets the configuration for the given transport, replacing any existing
-	 * configuration for that transport.
-	 * <p>
-	 * Locking: transports write.
-	 */
-	void setTransportConfig(T txn, TransportId t, TransportConfig config)
-	throws DbException;
-
-	/**
-	 * Sets the transport properties for the given transport, replacing any
-	 * existing properties for that transport.
-	 * <p>
-	 * Locking: transports write.
-	 */
-	void setTransportProperties(T txn, TransportId t,
-			TransportProperties properties) throws DbException;
-
 	/**
 	 * Sets the transport properties for the given contact, replacing any
 	 * existing properties unless the existing properties have a newer
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index 603db8e613..c6d89ee634 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -636,6 +636,23 @@ DatabaseCleaner.Callback {
 			LOG.fine("Added " + transports.size() + " transports to update");
 	}
 
+	public TransportConfig getConfig(TransportId t) throws DbException {
+		transportLock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				TransportConfig config = db.getConfig(txn, t);
+				db.commitTransaction(txn);
+				return config;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			transportLock.readLock().unlock();
+		}
+	}
+
 	public long getConnectionNumber(ContactId c, TransportId t)
 	throws DbException {
 		contactLock.readLock().lock();
@@ -737,18 +754,18 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Map<TransportId, Map<ContactId, TransportProperties>>
-	getRemoteTransports() throws DbException {
+	public Map<ContactId, TransportProperties> getRemoteProperties(
+			TransportId t) throws DbException {
 		contactLock.readLock().lock();
 		try {
 			transportLock.readLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
-					Map<TransportId, Map<ContactId, TransportProperties>>
-					transports = db.getRemoteTransports(txn);
+					Map<ContactId, TransportProperties> properties =
+						db.getRemoteProperties(txn, t);
 					db.commitTransaction(txn);
-					return transports;
+					return properties;
 				} catch(DbException e) {
 					db.abortTransaction(txn);
 					throw e;
@@ -796,24 +813,6 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public TransportConfig getTransportConfig(TransportId t)
-	throws DbException {
-		transportLock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				TransportConfig config = db.getTransportConfig(txn, t);
-				db.commitTransaction(txn);
-				return config;
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			transportLock.readLock().unlock();
-		}
-	}
-
 	public Collection<ContactId> getVisibility(GroupId g) throws DbException {
 		contactLock.readLock().lock();
 		try {
@@ -1105,6 +1104,30 @@ DatabaseCleaner.Callback {
 		callListeners(Event.CONTACTS_UPDATED);
 	}
 
+	public void setConfig(TransportId t, TransportConfig config)
+	throws DbException {
+		boolean changed = false;
+		transportLock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				TransportConfig old = db.getConfig(txn, t);
+				if(!config.equals(old)) {
+					db.setConfig(txn, t, config);
+					changed = true;
+				}
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			transportLock.writeLock().unlock();
+		}
+		// Call the listeners outside the lock
+		if(changed) callListeners(Event.TRANSPORTS_UPDATED);
+	}
+
 	public void setConnectionWindow(ContactId c, TransportId t,
 			ConnectionWindow w) throws DbException {
 		contactLock.readLock().lock();
@@ -1127,6 +1150,30 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public void setLocalProperties(TransportId t,
+			TransportProperties properties) throws DbException {
+		boolean changed = false;
+		transportLock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				TransportProperties old = db.getLocalTransports(txn).get(t);
+				if(!properties.equals(old)) {
+					db.setLocalProperties(txn, t, properties);
+					changed = true;
+				}
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			transportLock.writeLock().unlock();
+		}
+		// Call the listeners outside the lock
+		if(changed) callListeners(Event.TRANSPORTS_UPDATED);
+	}
+
 	public void setRating(AuthorId a, Rating r) throws DbException {
 		messageLock.writeLock().lock();
 		try {
@@ -1221,56 +1268,6 @@ DatabaseCleaner.Callback {
 					+ indirect + " indirectly");
 	}
 
-	public void setTransportConfig(TransportId t, TransportConfig config)
-	throws DbException {
-		boolean changed = false;
-		transportLock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				TransportConfig old = db.getTransportConfig(txn, t);
-				if(!config.equals(old)) {
-					db.setTransportConfig(txn, t, config);
-					changed = true;
-				}
-				db.commitTransaction(txn);
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			transportLock.writeLock().unlock();
-		}
-		// Call the listeners outside the lock
-		if(changed) callListeners(Event.TRANSPORTS_UPDATED);
-	}
-
-	public void setTransportProperties(TransportId t,
-			TransportProperties properties) throws DbException {
-		boolean changed = false;
-		transportLock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				Map<TransportId, TransportProperties> transports =
-					db.getLocalTransports(txn);
-				TransportProperties old = transports.get(t);
-				if(!properties.equals(old)) {
-					db.setTransportProperties(txn, t, properties);
-					changed = true;
-				}
-				db.commitTransaction(txn);
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			transportLock.writeLock().unlock();
-		}
-		// Call the listeners outside the lock
-		if(changed) callListeners(Event.TRANSPORTS_UPDATED);
-	}
-
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 	throws DbException {
 		contactLock.readLock().lock();
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index aa8f8279bc..f3e901d128 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -16,7 +16,6 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.TreeMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -811,6 +810,28 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public TransportConfig getConfig(Connection txn, TransportId t)
+	throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT key, value FROM transportConfig"
+				+ " WHERE transportId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, t.getInt());
+			rs = ps.executeQuery();
+			TransportConfig config = new TransportConfig();
+			while(rs.next()) config.put(rs.getString(1), rs.getString(2));
+			rs.close();
+			ps.close();
+			return config;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public long getConnectionNumber(Connection txn, ContactId c,
 			TransportId t) throws DbException {
 		PreparedStatement ps = null;
@@ -918,6 +939,34 @@ abstract class JdbcDatabase implements Database<Connection> {
 		} else return f.length();
 	}
 
+	public MessageId getGroupMessageParent(Connection txn, MessageId m)
+	throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT m1.parentId FROM messages AS m1"
+				+ " JOIN messages AS m2"
+				+ " ON m1.parentId = m2.messageId"
+				+ " AND m1.groupId = m2.groupId"
+				+ " WHERE m1.messageId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setBytes(1, m.getBytes());
+			rs = ps.executeQuery();
+			MessageId parent = null;
+			if(rs.next()) {
+				parent = new MessageId(rs.getBytes(1));
+				if(rs.next()) throw new DbStateException();
+			}
+			rs.close();
+			ps.close();
+			return parent;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public Map<TransportId, TransportProperties> getLocalTransports(
 			Connection txn) throws DbException {
 		PreparedStatement ps = null;
@@ -928,7 +977,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			Map<TransportId, TransportProperties> transports =
-				new TreeMap<TransportId, TransportProperties>();
+				new HashMap<TransportId, TransportProperties>();
 			TransportProperties properties = null;
 			TransportId lastId = null;
 			while(rs.next()) {
@@ -1164,34 +1213,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public MessageId getGroupMessageParent(Connection txn, MessageId m)
-	throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT m1.parentId FROM messages AS m1"
-				+ " JOIN messages AS m2"
-				+ " ON m1.parentId = m2.messageId"
-				+ " AND m1.groupId = m2.groupId"
-				+ " WHERE m1.messageId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, m.getBytes());
-			rs = ps.executeQuery();
-			MessageId parent = null;
-			if(rs.next()) {
-				parent = new MessageId(rs.getBytes(1));
-				if(rs.next()) throw new DbStateException();
-			}
-			rs.close();
-			ps.close();
-			return parent;
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Rating getRating(Connection txn, AuthorId a) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -1214,61 +1235,32 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<TransportId, Map<ContactId, TransportProperties>>
-	getRemoteTransports(Connection txn) throws DbException {
+	public Map<ContactId, TransportProperties> getRemoteProperties(
+			Connection txn, TransportId t) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT transportId, contactId, key, value"
-				+ " FROM contactTransports"
-				+ " ORDER BY transportId";
+			String sql = "SELECT contactId, key, value FROM contactTransports"
+				+ " WHERE transportId = ?"
+				+ " ORDER BY contactId";
 			ps = txn.prepareStatement(sql);
+			ps.setInt(1, t.getInt());
 			rs = ps.executeQuery();
-			Map<TransportId, Map<ContactId, TransportProperties>> transports =
-				new TreeMap<TransportId, Map<ContactId, TransportProperties>>();
-			Map<ContactId, TransportProperties> contacts = null;
-			TransportProperties properties = null;
-			TransportId lastTransportId = null;
+			Map<ContactId, TransportProperties> properties =
+				new HashMap<ContactId, TransportProperties>();
+			TransportProperties p = null;
 			ContactId lastContactId = null;
 			while(rs.next()) {
-				TransportId transportId = new TransportId(rs.getInt(1));
-				if(!transportId.equals(lastTransportId)) {
-					contacts = new HashMap<ContactId, TransportProperties>();
-					transports.put(transportId, contacts);
-					lastContactId = null;
-				}
-				ContactId contactId = new ContactId(rs.getInt(2));
+				ContactId contactId = new ContactId(rs.getInt(1));
 				if(!contactId.equals(lastContactId)) {
-					properties = new TransportProperties();
-					contacts.put(contactId, properties);
+					p = new TransportProperties();
+					properties.put(contactId, p);
 				}
-				properties.put(rs.getString(3), rs.getString(4));
+				p.put(rs.getString(2), rs.getString(3));
 			}
 			rs.close();
 			ps.close();
-			return transports;
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	public byte[] getSharedSecret(Connection txn, ContactId c)
-	throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT secret FROM contacts WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			byte[] secret = rs.getBytes(1);
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			return secret;
+			return properties;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1415,6 +1407,28 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public byte[] getSharedSecret(Connection txn, ContactId c)
+	throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT secret FROM contacts WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			rs = ps.executeQuery();
+			if(!rs.next()) throw new DbStateException();
+			byte[] secret = rs.getBytes(1);
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			return secret;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public Collection<Group> getSubscriptions(Connection txn)
 	throws DbException {
 		PreparedStatement ps = null;
@@ -1469,28 +1483,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public TransportConfig getTransportConfig(Connection txn,
-			TransportId t) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT key, value FROM transportConfig"
-				+ " WHERE transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, t.getInt());
-			rs = ps.executeQuery();
-			TransportConfig config = new TransportConfig();
-			while(rs.next()) config.put(rs.getString(1), rs.getString(2));
-			rs.close();
-			ps.close();
-			return config;
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Collection<ContactId> getVisibility(Connection txn, GroupId g)
 	throws DbException {
 		PreparedStatement ps = null;
@@ -1778,6 +1770,44 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setConfig(Connection txn, TransportId t, TransportConfig config)
+	throws DbException {
+		setTransportDetails(txn, t, config, "transportConfig");
+	}
+
+	private void setTransportDetails(Connection txn, TransportId t,
+			Map<String, String> details, String table) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			// Delete any existing details for the given transport
+			String sql = "DELETE FROM " + table + " WHERE transportId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, t.getInt());
+			ps.executeUpdate();
+			ps.close();
+			// Store the new details
+			sql = "INSERT INTO " + table + " (transportId, key, value)"
+			+ " VALUES (?, ?, ?)";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, t.getInt());
+			for(Entry<String, String> e : details.entrySet()) {
+				ps.setString(2, e.getKey());
+				ps.setString(3, e.getValue());
+				ps.addBatch();
+			}
+			int[] batchAffected = ps.executeBatch();
+			if(batchAffected.length != details.size())
+				throw new DbStateException();
+			for(int i = 0; i < batchAffected.length; i++) {
+				if(batchAffected[i] != 1) throw new DbStateException();
+			}
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void setConnectionWindow(Connection txn, ContactId c,
 			TransportId t, ConnectionWindow w) throws DbException {
 		PreparedStatement ps = null;
@@ -1826,6 +1856,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setLocalProperties(Connection txn, TransportId t,
+			TransportProperties properties) throws DbException {
+		setTransportDetails(txn, t, properties, "transports");
+	}
+
 	public Rating setRating(Connection txn, AuthorId a, Rating r)
 	throws DbException {
 		PreparedStatement ps = null;
@@ -2051,49 +2086,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setTransportConfig(Connection txn, TransportId t,
-			TransportConfig config) throws DbException {
-		setTransportDetails(txn, t, config, "transportConfig");
-	}
-
-	private void setTransportDetails(Connection txn, TransportId t,
-			Map<String, String> details, String table) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			// Delete any existing details for the given transport
-			String sql = "DELETE FROM " + table + " WHERE transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, t.getInt());
-			ps.executeUpdate();
-			ps.close();
-			// Store the new details
-			sql = "INSERT INTO " + table + " (transportId, key, value)"
-			+ " VALUES (?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, t.getInt());
-			for(Entry<String, String> e : details.entrySet()) {
-				ps.setString(2, e.getKey());
-				ps.setString(3, e.getValue());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if(batchAffected.length != details.size())
-				throw new DbStateException();
-			for(int i = 0; i < batchAffected.length; i++) {
-				if(batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
-		} catch(SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	public void setTransportProperties(Connection txn, TransportId t,
-			TransportProperties properties) throws DbException {
-		setTransportDetails(txn, t, properties, "transports");
-	}
-
 	public void setTransports(Connection txn, ContactId c,
 			Map<TransportId, TransportProperties> transports, long timestamp)
 	throws DbException {
diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java
index 196d8560fc..0010d4262f 100644
--- a/components/net/sf/briar/protocol/TransportReader.java
+++ b/components/net/sf/briar/protocol/TransportReader.java
@@ -1,9 +1,9 @@
 package net.sf.briar.protocol;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.TransportId;
@@ -40,7 +40,7 @@ class TransportReader implements ObjectReader<TransportUpdate> {
 		if(l.size() > TransportUpdate.MAX_PLUGINS_PER_UPDATE)
 			throw new FormatException();
 		Map<TransportId, TransportProperties> transports =
-			new TreeMap<TransportId, TransportProperties>();
+			new HashMap<TransportId, TransportProperties>();
 		for(Transport t : l) {
 			if(transports.put(t.id, t.properties) != null)
 				throw new FormatException(); // Duplicate transport ID
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index facb84dd6e..476cc49f99 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -57,8 +57,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 	private final Group group;
 	private final TransportId transportId;
 	private final Map<TransportId, TransportProperties> transports;
-	private final Map<TransportId, Map<ContactId, TransportProperties>>
-	remoteTransports;
+	private final Map<ContactId, TransportProperties> remoteProperties;
 	private final byte[] secret;
 
 	public DatabaseComponentTest() {
@@ -81,8 +80,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		TransportProperties p =
 			new TransportProperties(Collections.singletonMap("foo", "bar"));
 		transports = Collections.singletonMap(transportId, p);
-		remoteTransports = Collections.singletonMap(transportId,
-				Collections.singletonMap(contactId, p));
+		remoteProperties = Collections.singletonMap(contactId, p);
 		secret = new byte[123];
 	}
 
@@ -128,9 +126,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(true));
 			oneOf(database).getSharedSecret(txn, contactId);
 			will(returnValue(secret));
-			// getRemoteTransports()
-			oneOf(database).getRemoteTransports(txn);
-			will(returnValue(remoteTransports));
+			// getTransportProperties(transportId)
+			oneOf(database).getRemoteProperties(txn, transportId);
+			will(returnValue(remoteProperties));
 			// subscribe(group)
 			oneOf(group).getId();
 			will(returnValue(groupId));
@@ -176,7 +174,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		assertEquals(connectionWindow,
 				db.getConnectionWindow(contactId, transportId));
 		assertEquals(secret, db.getSharedSecret(contactId));
-		assertEquals(remoteTransports, db.getRemoteTransports());
+		assertEquals(remoteProperties, db.getRemoteProperties(transportId));
 		db.subscribe(group); // First time - check listeners are called
 		db.subscribe(group); // Second time - check listeners aren't called
 		assertEquals(Collections.singletonList(groupId), db.getSubscriptions());
@@ -1289,7 +1287,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getLocalTransports(txn);
 			will(returnValue(Collections.singletonMap(transportId,
 					properties)));
-			oneOf(database).setTransportProperties(txn, transportId,
+			oneOf(database).setLocalProperties(txn, transportId,
 					properties1);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(Event.TRANSPORTS_UPDATED);
@@ -1297,7 +1295,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
 		db.addListener(listener);
-		db.setTransportProperties(transportId, properties1);
+		db.setLocalProperties(transportId, properties1);
 
 		context.assertIsSatisfied();
 	}
@@ -1323,7 +1321,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
 		db.addListener(listener);
-		db.setTransportProperties(transportId, properties);
+		db.setLocalProperties(transportId, properties);
 
 		context.assertIsSatisfied();
 	}
@@ -1342,16 +1340,16 @@ public abstract class DatabaseComponentTest extends TestCase {
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
-			oneOf(database).getTransportConfig(txn, transportId);
+			oneOf(database).getConfig(txn, transportId);
 			will(returnValue(config));
-			oneOf(database).setTransportConfig(txn, transportId, config1);
+			oneOf(database).setConfig(txn, transportId, config1);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(Event.TRANSPORTS_UPDATED);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
 		db.addListener(listener);
-		db.setTransportConfig(transportId, config1);
+		db.setConfig(transportId, config1);
 
 		context.assertIsSatisfied();
 	}
@@ -1369,14 +1367,14 @@ public abstract class DatabaseComponentTest extends TestCase {
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
-			oneOf(database).getTransportConfig(txn, transportId);
+			oneOf(database).getConfig(txn, transportId);
 			will(returnValue(config));
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
 		db.addListener(listener);
-		db.setTransportConfig(transportId, config);
+		db.setConfig(transportId, config);
 
 		context.assertIsSatisfied();
 	}
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index fb3fe636cc..6f609e62ee 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -11,7 +11,6 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Random;
-import java.util.TreeMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -73,9 +72,9 @@ public class H2DatabaseTest extends TestCase {
 	private final Message message, privateMessage;
 	private final Group group;
 	private final TransportId transportId;
+	private final TransportProperties properties;
 	private final Map<TransportId, TransportProperties> transports;
-	private final Map<TransportId, Map<ContactId, TransportProperties>>
-	remoteTransports;
+	private final Map<ContactId, TransportProperties> remoteProperties;
 	private final Map<Group, Long> subscriptions;
 	private final byte[] secret;
 
@@ -102,11 +101,10 @@ public class H2DatabaseTest extends TestCase {
 			new TestMessage(privateMessageId, null, null, null, timestamp, raw);
 		group = groupFactory.createGroup(groupId, "Group name", null);
 		transportId = new TransportId(0);
-		TransportProperties p =
-			new TransportProperties(Collections.singletonMap("foo", "bar"));
-		transports = Collections.singletonMap(transportId, p);
-		remoteTransports = Collections.singletonMap(transportId,
-				Collections.singletonMap(contactId, p));
+		properties = new TransportProperties(
+				Collections.singletonMap("foo", "bar"));
+		transports = Collections.singletonMap(transportId, properties);
+		remoteProperties = Collections.singletonMap(contactId, properties);
 		subscriptions = Collections.singletonMap(group, 0L);
 		secret = new byte[123];
 	}
@@ -140,7 +138,8 @@ public class H2DatabaseTest extends TestCase {
 		db = open(true);
 		txn = db.startTransaction();
 		assertTrue(db.containsContact(txn, contactId));
-		assertEquals(remoteTransports, db.getRemoteTransports(txn));
+		assertEquals(remoteProperties,
+				db.getRemoteProperties(txn, transportId));
 		assertTrue(db.containsSubscription(txn, groupId));
 		assertTrue(db.containsMessage(txn, messageId));
 		byte[] raw1 = db.getMessage(txn, messageId);
@@ -160,7 +159,8 @@ public class H2DatabaseTest extends TestCase {
 		db = open(true);
 		txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
-		assertEquals(Collections.emptyMap(), db.getRemoteTransports(txn));
+		assertEquals(Collections.emptyMap(),
+				db.getRemoteProperties(txn, transportId));
 		assertFalse(db.containsSubscription(txn, groupId));
 		assertFalse(db.containsMessage(txn, messageId));
 		assertFalse(db.containsMessage(txn, privateMessageId));
@@ -991,47 +991,40 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testUpdateTransportProperties() throws Exception {
-		TransportProperties properties =
-			new TransportProperties(Collections.singletonMap("foo", "bar"));
-		TransportProperties properties1 =
-			new TransportProperties(Collections.singletonMap("baz", "bam"));
-
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
 		// Add a contact with some transport properties
 		assertEquals(contactId, db.addContact(txn, transports, secret));
-		assertEquals(remoteTransports, db.getRemoteTransports(txn));
+		assertEquals(remoteProperties,
+				db.getRemoteProperties(txn, transportId));
 
 		// Replace the transport properties
-		TransportId transportId1 = new TransportId(1);
+		TransportProperties properties1 =
+			new TransportProperties(Collections.singletonMap("baz", "bam"));
 		Map<TransportId, TransportProperties> transports1 =
-			new TreeMap<TransportId, TransportProperties>();
-		transports1.put(transportId, properties);
-		transports1.put(transportId1, properties1);
-		Map<TransportId, Map<ContactId, TransportProperties>> remoteTransports1
-		= new TreeMap<TransportId, Map<ContactId, TransportProperties>>();
-		remoteTransports1.put(transportId, Collections.singletonMap(contactId,
-				properties));
-		remoteTransports1.put(transportId1, Collections.singletonMap(contactId,
-				properties1));
+			Collections.singletonMap(transportId, properties1);
+		Map<ContactId, TransportProperties> remoteProperties1 =
+			Collections.singletonMap(contactId, properties1);
 		db.setTransports(txn, contactId, transports1, 1);
-		assertEquals(remoteTransports1, db.getRemoteTransports(txn));
+		assertEquals(remoteProperties1,
+				db.getRemoteProperties(txn, transportId));
 
 		// Remove the transport properties
 		db.setTransports(txn, contactId,
 				Collections.<TransportId, TransportProperties>emptyMap(), 2);
-		assertEquals(Collections.emptyMap(), db.getRemoteTransports(txn));
+		assertEquals(Collections.emptyMap(),
+				db.getRemoteProperties(txn, transportId));
 
 		// Set the local transport properties
 		for(Entry<TransportId, TransportProperties> e : transports.entrySet()) {
-			db.setTransportProperties(txn, e.getKey(), e.getValue());
+			db.setLocalProperties(txn, e.getKey(), e.getValue());
 		}
 		assertEquals(transports, db.getLocalTransports(txn));
 
 		// Remove the local transport properties
 		for(TransportId t : transports.keySet()) {
-			db.setTransportProperties(txn, t, new TransportProperties());
+			db.setLocalProperties(txn, t, new TransportProperties());
 		}
 		assertEquals(Collections.emptyMap(), db.getLocalTransports(txn));
 
@@ -1051,17 +1044,17 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Set the transport config
-		db.setTransportConfig(txn, transportId, config);
-		assertEquals(config, db.getTransportConfig(txn, transportId));
+		db.setConfig(txn, transportId, config);
+		assertEquals(config, db.getConfig(txn, transportId));
 
 		// Update the transport config
-		db.setTransportConfig(txn, transportId, config1);
-		assertEquals(config1, db.getTransportConfig(txn, transportId));
+		db.setConfig(txn, transportId, config1);
+		assertEquals(config1, db.getConfig(txn, transportId));
 
 		// Remove the transport config
-		db.setTransportConfig(txn, transportId, new TransportConfig());
+		db.setConfig(txn, transportId, new TransportConfig());
 		assertEquals(Collections.emptyMap(),
-				db.getTransportConfig(txn, transportId));
+				db.getConfig(txn, transportId));
 
 		db.commitTransaction(txn);
 		db.close();
@@ -1069,45 +1062,35 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testTransportsNotUpdatedIfTimestampIsOld() throws Exception {
-		TransportProperties properties =
-			new TransportProperties(Collections.singletonMap("foo", "bar"));
-		TransportProperties properties1 =
-			new TransportProperties(Collections.singletonMap("baz", "bam"));
-		TransportProperties properties2 =
-			new TransportProperties(Collections.singletonMap("quux", "etc"));
-
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
 		// Add a contact with some transport properties
 		assertEquals(contactId, db.addContact(txn, transports, secret));
-		assertEquals(remoteTransports, db.getRemoteTransports(txn));
+		assertEquals(remoteProperties,
+				db.getRemoteProperties(txn, transportId));
 
 		// Replace the transport properties using a timestamp of 2
-		TransportId transportId1 = new TransportId(1);
+		TransportProperties properties1 =
+			new TransportProperties(Collections.singletonMap("baz", "bam"));
 		Map<TransportId, TransportProperties> transports1 =
-			new TreeMap<TransportId, TransportProperties>();
-		transports1.put(transportId, properties);
-		transports1.put(transportId1, properties1);
-		Map<TransportId, Map<ContactId, TransportProperties>> remoteTransports1
-		= new TreeMap<TransportId, Map<ContactId, TransportProperties>>();
-		remoteTransports1.put(transportId, Collections.singletonMap(contactId,
-				properties));
-		remoteTransports1.put(transportId1, Collections.singletonMap(contactId,
-				properties1));
+			Collections.singletonMap(transportId, properties1);
+		Map<ContactId, TransportProperties> remoteProperties1 =
+			Collections.singletonMap(contactId, properties1);
 		db.setTransports(txn, contactId, transports1, 2);
-		assertEquals(remoteTransports1, db.getRemoteTransports(txn));
+		assertEquals(remoteProperties1,
+				db.getRemoteProperties(txn, transportId));
 
 		// Try to replace the transport properties using a timestamp of 1
-		TransportId transportId2 = new TransportId(2);
+		TransportProperties properties2 =
+			new TransportProperties(Collections.singletonMap("quux", "etc"));
 		Map<TransportId, TransportProperties> transports2 =
-			new TreeMap<TransportId, TransportProperties>();
-		transports2.put(transportId1, properties1);
-		transports2.put(transportId2, properties2);
+			Collections.singletonMap(transportId, properties2);
 		db.setTransports(txn, contactId, transports2, 1);
 
 		// The old properties should still be there
-		assertEquals(remoteTransports1, db.getRemoteTransports(txn));
+		assertEquals(remoteProperties1,
+				db.getRemoteProperties(txn, transportId));
 
 		db.commitTransaction(txn);
 		db.close();
diff --git a/test/net/sf/briar/protocol/writers/ConstantsTest.java b/test/net/sf/briar/protocol/writers/ConstantsTest.java
index 73d58333b8..84c30ebc0d 100644
--- a/test/net/sf/briar/protocol/writers/ConstantsTest.java
+++ b/test/net/sf/briar/protocol/writers/ConstantsTest.java
@@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
 import java.security.PrivateKey;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.TreeMap;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
@@ -193,7 +192,7 @@ public class ConstantsTest extends TestCase {
 		// Create the maximum number of plugins, each with the maximum number
 		// of maximum-length properties
 		Map<TransportId, TransportProperties> transports =
-			new TreeMap<TransportId, TransportProperties>();
+			new HashMap<TransportId, TransportProperties>();
 		for(int i = 0; i < TransportUpdate.MAX_PLUGINS_PER_UPDATE; i++) {
 			TransportProperties p = new TransportProperties();
 			for(int j = 0; j < TransportUpdate.MAX_PROPERTIES_PER_PLUGIN; j++) {
-- 
GitLab