From a49a95347f206021ceaaacaf7872bdbb9b80b688 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Tue, 11 Oct 2011 17:28:47 +0100
Subject: [PATCH] Use dedicated classes for transport properties and configs.

---
 api/net/sf/briar/api/TransportConfig.java     | 17 +++++
 api/net/sf/briar/api/TransportProperties.java | 17 +++++
 .../sf/briar/api/db/DatabaseComponent.java    | 14 ++--
 .../sf/briar/api/plugins/TransportPlugin.java | 14 ++--
 .../briar/api/protocol/TransportUpdate.java   |  3 +-
 .../api/protocol/writers/TransportWriter.java |  3 +-
 .../api/transport/TransportCallback.java      |  7 +-
 components/net/sf/briar/db/Database.java      | 17 ++---
 .../sf/briar/db/DatabaseComponentImpl.java    | 32 ++++-----
 components/net/sf/briar/db/JdbcDatabase.java  | 42 ++++++------
 .../sf/briar/invitation/InvitationWorker.java |  3 +-
 .../net/sf/briar/plugins/AbstractPlugin.java  | 44 +++++-------
 .../plugins/bluetooth/BluetoothPlugin.java    | 29 ++++----
 .../file/RemovableDriveFinderImpl.java        | 25 -------
 .../plugins/file/RemovableDrivePlugin.java    |  8 ++-
 .../plugins/socket/SimpleSocketPlugin.java    | 17 +++--
 .../sf/briar/plugins/socket/SocketPlugin.java | 13 ++--
 .../sf/briar/protocol/TransportFactory.java   |  3 +-
 .../briar/protocol/TransportFactoryImpl.java  |  3 +-
 .../sf/briar/protocol/TransportReader.java    | 31 +++++----
 .../briar/protocol/TransportUpdateImpl.java   |  7 +-
 .../protocol/writers/TransportWriterImpl.java |  5 +-
 .../net/sf/briar/ProtocolIntegrationTest.java |  8 ++-
 .../sf/briar/db/DatabaseComponentTest.java    | 37 +++++-----
 test/net/sf/briar/db/H2DatabaseTest.java      | 67 ++++++++++---------
 .../invitation/InvitationWorkerTest.java      |  9 ++-
 .../sf/briar/plugins/StubStreamCallback.java  | 39 -----------
 .../bluetooth/BluetoothClientTest.java        | 24 +++----
 .../bluetooth/BluetoothServerTest.java        | 13 ++--
 .../file/RemovableDrivePluginTest.java        | 17 ++---
 .../socket/SimpleSocketPluginTest.java        | 59 ++++++++++++----
 .../briar/protocol/ProtocolReadWriteTest.java |  8 ++-
 .../briar/protocol/writers/ConstantsTest.java | 11 +--
 .../batch/BatchConnectionReadWriteTest.java   |  3 +-
 34 files changed, 341 insertions(+), 308 deletions(-)
 create mode 100644 api/net/sf/briar/api/TransportConfig.java
 create mode 100644 api/net/sf/briar/api/TransportProperties.java
 delete mode 100644 components/net/sf/briar/plugins/file/RemovableDriveFinderImpl.java
 delete mode 100644 test/net/sf/briar/plugins/StubStreamCallback.java

diff --git a/api/net/sf/briar/api/TransportConfig.java b/api/net/sf/briar/api/TransportConfig.java
new file mode 100644
index 0000000000..e57d8388db
--- /dev/null
+++ b/api/net/sf/briar/api/TransportConfig.java
@@ -0,0 +1,17 @@
+package net.sf.briar.api;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class TransportConfig extends TreeMap<String, String> {
+
+	private static final long serialVersionUID = 2330384620787778596L;
+
+	public TransportConfig(Map<String, String> c) {
+		super(c);
+	}
+
+	public TransportConfig() {
+		super();
+	}
+}
diff --git a/api/net/sf/briar/api/TransportProperties.java b/api/net/sf/briar/api/TransportProperties.java
new file mode 100644
index 0000000000..8eda0706cf
--- /dev/null
+++ b/api/net/sf/briar/api/TransportProperties.java
@@ -0,0 +1,17 @@
+package net.sf.briar.api;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class TransportProperties extends TreeMap<String, String> {
+
+	private static final long serialVersionUID = 7533739534204953625L;
+
+	public TransportProperties(Map<String, String> p) {
+		super(p);
+	}
+
+	public TransportProperties() {
+		super();
+	}
+}
diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index 5a4475108a..99e7a92466 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -6,7 +6,9 @@ import java.util.Map;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -51,7 +53,7 @@ public interface DatabaseComponent {
 	 * Adds a new contact to the database with the given transport properties
 	 * and shared secret, returns an ID for the contact.
 	 */
-	ContactId addContact(Map<TransportId, Map<String, String>> transports,
+	ContactId addContact(Map<TransportId, TransportProperties> transports,
 			byte[] secret) throws DbException;
 
 	/** Adds a locally generated group message to the database. */
@@ -116,14 +118,14 @@ public interface DatabaseComponent {
 	Collection<ContactId> getContacts() throws DbException;
 
 	/** Returns all local transport properties. */
-	Map<TransportId, Map<String, String>> getLocalTransports()
+	Map<TransportId, TransportProperties> getLocalTransports()
 	throws DbException;
 
 	/** Returns the user's rating for the given author. */
 	Rating getRating(AuthorId a) throws DbException;
 
 	/** Returns all remote transport properties. */
-	Map<TransportId, Map<ContactId, Map<String, String>>> getRemoteTransports()
+	Map<TransportId, Map<ContactId, TransportProperties>> getRemoteTransports()
 	throws DbException;
 
 	/** Returns the secret shared with the given contact. */
@@ -133,7 +135,7 @@ public interface DatabaseComponent {
 	Collection<Group> getSubscriptions() throws DbException;
 
 	/** Returns the configuration for the given transport. */
-	Map<String, String> getTransportConfig(TransportId t) throws DbException;
+	TransportConfig getTransportConfig(TransportId t) throws DbException;
 
 	/** Returns the contacts to which the given group is visible. */
 	Collection<ContactId> getVisibility(GroupId g) throws DbException;
@@ -186,14 +188,14 @@ public interface DatabaseComponent {
 	 * Sets the configuration for the given transport, replacing any existing
 	 * configuration for that transport.
 	 */
-	void setTransportConfig(TransportId t, Map<String, String> config)
+	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, Map<String, String> properties)
+	void setTransportProperties(TransportId t, TransportProperties properties)
 	throws DbException;
 
 	/**
diff --git a/api/net/sf/briar/api/plugins/TransportPlugin.java b/api/net/sf/briar/api/plugins/TransportPlugin.java
index ebc9be2cd2..213a89e3d6 100644
--- a/api/net/sf/briar/api/plugins/TransportPlugin.java
+++ b/api/net/sf/briar/api/plugins/TransportPlugin.java
@@ -4,7 +4,9 @@ import java.io.IOException;
 import java.util.Map;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 
 public interface TransportPlugin {
 
@@ -12,21 +14,21 @@ public interface TransportPlugin {
 	TransportId getId();
 
 	/** Starts the plugin. */
-	void start(Map<String, String> localProperties,
-			Map<ContactId, Map<String, String>> remoteProperties,
-			Map<String, String> config) throws IOException;
+	void start(TransportProperties localProperties,
+			Map<ContactId, TransportProperties> remoteProperties,
+			TransportConfig config) throws IOException;
 
 	/** Stops the plugin. */
 	void stop() throws IOException;
 
 	/** Updates the plugin's local transport properties. */
-	void setLocalProperties(Map<String, String> properties);
+	void setLocalProperties(TransportProperties p);
 
 	/** Updates the plugin's transport properties for the given contact. */
-	void setRemoteProperties(ContactId c, Map<String, String> properties);
+	void setRemoteProperties(ContactId c, TransportProperties p);
 
 	/** Updates the plugin's configuration properties. */
-	void setConfig(Map<String, String> config);
+	void setConfig(TransportConfig c);
 
 	/**
 	 * Returns true if the plugin's poll() method should be called
diff --git a/api/net/sf/briar/api/protocol/TransportUpdate.java b/api/net/sf/briar/api/protocol/TransportUpdate.java
index bb1680a1e4..c9ec131f8d 100644
--- a/api/net/sf/briar/api/protocol/TransportUpdate.java
+++ b/api/net/sf/briar/api/protocol/TransportUpdate.java
@@ -3,6 +3,7 @@ package net.sf.briar.api.protocol;
 import java.util.Map;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 
 /** A packet updating the sender's transport properties. */
 public interface TransportUpdate {
@@ -17,7 +18,7 @@ public interface TransportUpdate {
 	static final int MAX_PLUGINS_PER_UPDATE = 50;
 
 	/** Returns the transport properties contained in the update. */
-	Map<TransportId, Map<String, String>> getTransports();
+	Map<TransportId, TransportProperties> getTransports();
 
 	/**
 	 * Returns the update's timestamp. Updates that are older than the newest
diff --git a/api/net/sf/briar/api/protocol/writers/TransportWriter.java b/api/net/sf/briar/api/protocol/writers/TransportWriter.java
index a3367badf7..2f19ad31d9 100644
--- a/api/net/sf/briar/api/protocol/writers/TransportWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/TransportWriter.java
@@ -4,11 +4,12 @@ import java.io.IOException;
 import java.util.Map;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 
 /** An interface for creating a transport update. */
 public interface TransportWriter {
 
 	/** Writes the contents of the update. */
-	void writeTransports(Map<TransportId, Map<String, String>> transports,
+	void writeTransports(Map<TransportId, TransportProperties> transports,
 			long timestamp) throws IOException;
 }
diff --git a/api/net/sf/briar/api/transport/TransportCallback.java b/api/net/sf/briar/api/transport/TransportCallback.java
index 107fa5ae3c..ba78a06a13 100644
--- a/api/net/sf/briar/api/transport/TransportCallback.java
+++ b/api/net/sf/briar/api/transport/TransportCallback.java
@@ -1,12 +1,13 @@
 package net.sf.briar.api.transport;
 
-import java.util.Map;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
 
 public interface TransportCallback {
 
-	void setLocalProperties(Map<String, String> properties);
+	void setLocalProperties(TransportProperties p);
 
-	void setConfig(Map<String, String> config);
+	void setConfig(TransportConfig c);
 
 	void showMessage(String... message);
 
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index acb111fdd1..c8e01cce3d 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -6,7 +6,9 @@ import java.util.Map;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
@@ -86,7 +88,7 @@ interface Database<T> {
 	 * Locking: contacts write, transports write.
 	 */
 	ContactId addContact(T txn,
-			Map<TransportId, Map<String, String>> transports, byte[] secret)
+			Map<TransportId, TransportProperties> transports, byte[] secret)
 	throws DbException;
 
 	/**
@@ -216,7 +218,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: transports read.
 	 */
-	Map<TransportId, Map<String, String>> getLocalTransports(T txn)
+	Map<TransportId, TransportProperties> getLocalTransports(T txn)
 	throws DbException;
 
 	/**
@@ -282,7 +284,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: contacts read, transports read.
 	 */
-	Map<TransportId, Map<ContactId, Map<String, String>>>
+	Map<TransportId, Map<ContactId, TransportProperties>>
 	getRemoteTransports(T txn) throws DbException;
 
 	/**
@@ -338,8 +340,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: transports read.
 	 */
-	Map<String, String> getTransportConfig(T txn, TransportId t)
-	throws DbException;
+	TransportConfig getTransportConfig(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Returns the contacts to which the given group is visible.
@@ -480,7 +481,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: transports write.
 	 */
-	void setTransportConfig(T txn, TransportId t, Map<String, String> config)
+	void setTransportConfig(T txn, TransportId t, TransportConfig config)
 	throws DbException;
 
 	/**
@@ -490,7 +491,7 @@ interface Database<T> {
 	 * Locking: transports write.
 	 */
 	void setTransportProperties(T txn, TransportId t,
-			Map<String, String> properties) throws DbException;
+			TransportProperties properties) throws DbException;
 
 	/**
 	 * Sets the transport properties for the given contact, replacing any
@@ -500,7 +501,7 @@ interface Database<T> {
 	 * Locking: contacts read, transports write.
 	 */
 	void setTransports(T txn, ContactId c,
-			Map<TransportId, Map<String, String>> transports, long timestamp)
+			Map<TransportId, TransportProperties> transports, long timestamp)
 	throws DbException;
 
 	/**
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index 0c33930ec7..603db8e613 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -21,7 +21,9 @@ import java.util.logging.Logger;
 import net.sf.briar.api.Bytes;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseListener;
 import net.sf.briar.api.db.DatabaseListener.Event;
@@ -119,7 +121,7 @@ DatabaseCleaner.Callback {
 	}
 
 	public ContactId addContact(
-			Map<TransportId, Map<String, String>> transports, byte[] secret)
+			Map<TransportId, TransportProperties> transports, byte[] secret)
 	throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
 		ContactId c;
@@ -606,7 +608,7 @@ DatabaseCleaner.Callback {
 
 	public void generateTransportUpdate(ContactId c, TransportWriter t)
 	throws DbException, IOException {
-		Map<TransportId, Map<String, String>> transports;
+		Map<TransportId, TransportProperties> transports;
 		long timestamp;
 		contactLock.readLock().lock();
 		try {
@@ -699,13 +701,13 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Map<TransportId, Map<String, String>> getLocalTransports()
+	public Map<TransportId, TransportProperties> getLocalTransports()
 	throws DbException {
 		transportLock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				Map<TransportId, Map<String, String>> transports =
+				Map<TransportId, TransportProperties> transports =
 					db.getLocalTransports(txn);
 				db.commitTransaction(txn);
 				return transports;
@@ -735,7 +737,7 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Map<TransportId, Map<ContactId, Map<String, String>>>
+	public Map<TransportId, Map<ContactId, TransportProperties>>
 	getRemoteTransports() throws DbException {
 		contactLock.readLock().lock();
 		try {
@@ -743,7 +745,7 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					Map<TransportId, Map<ContactId, Map<String, String>>>
+					Map<TransportId, Map<ContactId, TransportProperties>>
 					transports = db.getRemoteTransports(txn);
 					db.commitTransaction(txn);
 					return transports;
@@ -794,13 +796,13 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Map<String, String> getTransportConfig(TransportId t)
+	public TransportConfig getTransportConfig(TransportId t)
 	throws DbException {
 		transportLock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				Map<String, String> config = db.getTransportConfig(txn, t);
+				TransportConfig config = db.getTransportConfig(txn, t);
 				db.commitTransaction(txn);
 				return config;
 			} catch(DbException e) {
@@ -1045,7 +1047,7 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					Map<TransportId, Map<String, String>> transports =
+					Map<TransportId, TransportProperties> transports =
 						t.getTransports();
 					db.setTransports(txn, c, transports, t.getTimestamp());
 					if(LOG.isLoggable(Level.FINE))
@@ -1219,14 +1221,14 @@ DatabaseCleaner.Callback {
 					+ indirect + " indirectly");
 	}
 
-	public void setTransportConfig(TransportId t,
-			Map<String, String> config) throws DbException {
+	public void setTransportConfig(TransportId t, TransportConfig config)
+	throws DbException {
 		boolean changed = false;
 		transportLock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				Map<String, String> old = db.getTransportConfig(txn, t);
+				TransportConfig old = db.getTransportConfig(txn, t);
 				if(!config.equals(old)) {
 					db.setTransportConfig(txn, t, config);
 					changed = true;
@@ -1244,15 +1246,15 @@ DatabaseCleaner.Callback {
 	}
 
 	public void setTransportProperties(TransportId t,
-			Map<String, String> properties) throws DbException {
+			TransportProperties properties) throws DbException {
 		boolean changed = false;
 		transportLock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				Map<TransportId, Map<String, String>> transports =
+				Map<TransportId, TransportProperties> transports =
 					db.getLocalTransports(txn);
-				Map<String, String> old = transports.get(t);
+				TransportProperties old = transports.get(t);
 				if(!properties.equals(old)) {
 					db.setTransportProperties(txn, t, properties);
 					changed = true;
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index b6de1ca1bd..aa8f8279bc 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -22,7 +22,9 @@ import java.util.logging.Logger;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
@@ -457,7 +459,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public ContactId addContact(Connection txn,
-			Map<TransportId, Map<String, String>> transports, byte[] secret)
+			Map<TransportId, TransportProperties> transports, byte[] secret)
 	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -488,7 +490,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			int batchSize = 0;
-			for(Entry<TransportId, Map<String, String>> e
+			for(Entry<TransportId, TransportProperties> e
 					: transports.entrySet()) {
 				ps.setInt(2, e.getKey().getInt());
 				for(Entry<String, String> e1 : e.getValue().entrySet()) {
@@ -916,7 +918,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		} else return f.length();
 	}
 
-	public Map<TransportId, Map<String, String>> getLocalTransports(
+	public Map<TransportId, TransportProperties> getLocalTransports(
 			Connection txn) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -925,14 +927,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 				+ " ORDER BY transportId";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
-			Map<TransportId, Map<String, String>> transports =
-				new TreeMap<TransportId, Map<String, String>>();
-			Map<String, String> properties = null;
+			Map<TransportId, TransportProperties> transports =
+				new TreeMap<TransportId, TransportProperties>();
+			TransportProperties properties = null;
 			TransportId lastId = null;
 			while(rs.next()) {
 				TransportId id = new TransportId(rs.getInt(1));
 				if(!id.equals(lastId)) {
-					properties = new TreeMap<String, String>();
+					properties = new TransportProperties();
 					transports.put(id, properties);
 				}
 				properties.put(rs.getString(2), rs.getString(3));
@@ -1212,7 +1214,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<TransportId, Map<ContactId, Map<String, String>>>
+	public Map<TransportId, Map<ContactId, TransportProperties>>
 	getRemoteTransports(Connection txn) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -1222,22 +1224,22 @@ abstract class JdbcDatabase implements Database<Connection> {
 				+ " ORDER BY transportId";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
-			Map<TransportId, Map<ContactId, Map<String, String>>> transports =
-				new TreeMap<TransportId, Map<ContactId, Map<String, String>>>();
-			Map<ContactId, Map<String, String>> contacts = null;
-			Map<String, String> properties = null;
+			Map<TransportId, Map<ContactId, TransportProperties>> transports =
+				new TreeMap<TransportId, Map<ContactId, TransportProperties>>();
+			Map<ContactId, TransportProperties> contacts = null;
+			TransportProperties properties = null;
 			TransportId lastTransportId = null;
 			ContactId lastContactId = null;
 			while(rs.next()) {
 				TransportId transportId = new TransportId(rs.getInt(1));
 				if(!transportId.equals(lastTransportId)) {
-					contacts = new HashMap<ContactId, Map<String, String>>();
+					contacts = new HashMap<ContactId, TransportProperties>();
 					transports.put(transportId, contacts);
 					lastContactId = null;
 				}
 				ContactId contactId = new ContactId(rs.getInt(2));
 				if(!contactId.equals(lastContactId)) {
-					properties = new TreeMap<String, String>();
+					properties = new TransportProperties();
 					contacts.put(contactId, properties);
 				}
 				properties.put(rs.getString(3), rs.getString(4));
@@ -1467,7 +1469,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<String, String> getTransportConfig(Connection txn,
+	public TransportConfig getTransportConfig(Connection txn,
 			TransportId t) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -1477,7 +1479,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, t.getInt());
 			rs = ps.executeQuery();
-			Map<String, String> config = new TreeMap<String, String>();
+			TransportConfig config = new TransportConfig();
 			while(rs.next()) config.put(rs.getString(1), rs.getString(2));
 			rs.close();
 			ps.close();
@@ -2050,7 +2052,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void setTransportConfig(Connection txn, TransportId t,
-			Map<String, String> config) throws DbException {
+			TransportConfig config) throws DbException {
 		setTransportDetails(txn, t, config, "transportConfig");
 	}
 
@@ -2088,12 +2090,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void setTransportProperties(Connection txn, TransportId t,
-			Map<String, String> properties) throws DbException {
+			TransportProperties properties) throws DbException {
 		setTransportDetails(txn, t, properties, "transports");
 	}
 
 	public void setTransports(Connection txn, ContactId c,
-			Map<TransportId, Map<String, String>> transports, long timestamp)
+			Map<TransportId, TransportProperties> transports, long timestamp)
 	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2123,7 +2125,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			int batchSize = 0;
-			for(Entry<TransportId, Map<String, String>> e
+			for(Entry<TransportId, TransportProperties> e
 					: transports.entrySet()) {
 				ps.setInt(2, e.getKey().getInt());
 				for(Entry<String, String> e1 : e.getValue().entrySet()) {
diff --git a/components/net/sf/briar/invitation/InvitationWorker.java b/components/net/sf/briar/invitation/InvitationWorker.java
index 9437f2d706..ddebf35749 100644
--- a/components/net/sf/briar/invitation/InvitationWorker.java
+++ b/components/net/sf/briar/invitation/InvitationWorker.java
@@ -9,6 +9,7 @@ import java.util.List;
 import java.util.Map;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.invitation.InvitationCallback;
@@ -71,7 +72,7 @@ class InvitationWorker implements Runnable {
 		File invitationDat = new File(dir, "invitation.dat");
 		callback.encryptingFile(invitationDat);
 		// FIXME: Create a real invitation
-		Map<TransportId, Map<String, String>> transports;
+		Map<TransportId, TransportProperties> transports;
 		try {
 			transports = db.getLocalTransports();
 		} catch(DbException e) {
diff --git a/components/net/sf/briar/plugins/AbstractPlugin.java b/components/net/sf/briar/plugins/AbstractPlugin.java
index 1408e57d2a..992b89da3e 100644
--- a/components/net/sf/briar/plugins/AbstractPlugin.java
+++ b/components/net/sf/briar/plugins/AbstractPlugin.java
@@ -1,13 +1,12 @@
 package net.sf.briar.plugins;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.concurrent.Executor;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.TransportPlugin;
 
 public abstract class AbstractPlugin implements TransportPlugin {
@@ -15,31 +14,23 @@ public abstract class AbstractPlugin implements TransportPlugin {
 	protected final Executor executor;
 
 	// These fields should be accessed with this's lock held
-	protected Map<String, String> localProperties = null;
-	protected Map<ContactId, Map<String, String>> remoteProperties = null;
-	protected Map<String, String> config = null;
+	protected TransportProperties localProperties = null;
+	protected Map<ContactId, TransportProperties> remoteProperties = null;
+	protected TransportConfig config = null;
 	protected boolean started = false;
 
 	protected AbstractPlugin(Executor executor) {
 		this.executor = executor;
 	}
 
-	public synchronized void start(Map<String, String> localProperties,
-			Map<ContactId, Map<String, String>> remoteProperties,
-			Map<String, String> config) throws IOException {
+	public synchronized void start(TransportProperties localProperties,
+			Map<ContactId, TransportProperties> remoteProperties,
+			TransportConfig config) throws IOException {
 		if(started) throw new IllegalStateException();
 		started = true;
-		this.localProperties = Collections.unmodifiableMap(localProperties);
-		// Copy the remoteProperties map to make its values unmodifiable
-		int size = remoteProperties.size();
-		Map<ContactId, Map<String, String>> m =
-			new HashMap<ContactId, Map<String, String>>(size);
-		for(Entry<ContactId, Map<String, String>> e
-				: remoteProperties.entrySet()) {
-			m.put(e.getKey(), Collections.unmodifiableMap(e.getValue()));
-		}
-		this.remoteProperties = m;
-		this.config = Collections.unmodifiableMap(config);
+		this.localProperties = localProperties;
+		this.remoteProperties = remoteProperties;
+		this.config = config;
 	}
 
 	public synchronized void stop() throws IOException {
@@ -47,20 +38,19 @@ public abstract class AbstractPlugin implements TransportPlugin {
 		started = false;
 	}
 
-	public synchronized void setLocalProperties(
-			Map<String, String> properties) {
+	public synchronized void setLocalProperties(TransportProperties p) {
 		if(!started) throw new IllegalStateException();
-		localProperties = Collections.unmodifiableMap(properties);
+		localProperties = p;
 	}
 
 	public synchronized void setRemoteProperties(ContactId c,
-			Map<String, String> properties) {
+			TransportProperties p) {
 		if(!started) throw new IllegalStateException();
-		remoteProperties.put(c, Collections.unmodifiableMap(properties));
+		remoteProperties.put(c, p);
 	}
 
-	public synchronized void setConfig(Map<String, String> config) {
+	public synchronized void setConfig(TransportConfig c) {
 		if(!started) throw new IllegalStateException();
-		this.config = Collections.unmodifiableMap(config);
+		this.config = c;
 	}
 }
diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
index 25ce8321d9..ac933c783a 100644
--- a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
+++ b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
@@ -7,7 +7,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Random;
-import java.util.TreeMap;
 import java.util.concurrent.Executor;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -20,7 +19,9 @@ import javax.microedition.io.StreamConnection;
 import javax.microedition.io.StreamConnectionNotifier;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.StreamTransportCallback;
 import net.sf.briar.api.plugins.StreamTransportPlugin;
 import net.sf.briar.api.transport.StreamTransportConnection;
@@ -54,9 +55,9 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 	}
 
 	@Override
-	public void start(Map<String, String> localProperties,
-			Map<ContactId, Map<String, String>> remoteProperties,
-			Map<String, String> config) throws IOException {
+	public void start(TransportProperties localProperties,
+			Map<ContactId, TransportProperties> remoteProperties,
+			TransportConfig config) throws IOException {
 		// Initialise the Bluetooth stack
 		try {
 			synchronized(this) {
@@ -133,13 +134,13 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 		byte[] b = new byte[16];
 		new Random().nextBytes(b); // FIXME: Use a SecureRandom?
 		String uuid = StringUtils.toHexString(b);
-		Map<String, String> m;
+		TransportConfig c;
 		synchronized(this) {
 			if(!started) return uuid;
-			m = new TreeMap<String, String>(config);
+			c = new TransportConfig(config);
 		}
-		m.put("uuid", uuid);
-		callback.setConfig(m);
+		c.put("uuid", uuid);
+		callback.setConfig(c);
 		return uuid;
 	}
 
@@ -174,13 +175,13 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 	}
 
 	private void setLocalBluetoothAddress(String address) {
-		Map<String, String> m;
+		TransportProperties p;
 		synchronized(this) {
 			if(!started) return;
-			m = new TreeMap<String, String>(localProperties);
+			p = new TransportProperties(localProperties);
 		}
-		m.put("address", address);
-		callback.setLocalProperties(m);
+		p.put("address", address);
+		callback.setLocalProperties(p);
 	}
 
 	public boolean shouldPoll() {
@@ -224,10 +225,10 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 			discoveryAgent = localDevice.getDiscoveryAgent();
 			addresses = new HashMap<String, ContactId>();
 			uuids = new HashMap<ContactId, String>();
-			for(Entry<ContactId, Map<String, String>> e
+			for(Entry<ContactId, TransportProperties> e
 					: remoteProperties.entrySet()) {
 				ContactId c = e.getKey();
-				Map<String, String> properties = e.getValue();
+				TransportProperties properties = e.getValue();
 				String address = properties.get("address");
 				String uuid = properties.get("uuid");
 				if(address != null && uuid != null) {
diff --git a/components/net/sf/briar/plugins/file/RemovableDriveFinderImpl.java b/components/net/sf/briar/plugins/file/RemovableDriveFinderImpl.java
deleted file mode 100644
index 26060e74fc..0000000000
--- a/components/net/sf/briar/plugins/file/RemovableDriveFinderImpl.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package net.sf.briar.plugins.file;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import net.sf.briar.util.OsUtils;
-
-class RemovableDriveFinderImpl implements RemovableDriveFinder {
-
-	private final LinuxRemovableDriveFinder linux =
-		new LinuxRemovableDriveFinder();
-	private final MacRemovableDriveFinder mac =
-		new MacRemovableDriveFinder();
-	private final WindowsRemovableDriveFinder windows =
-		new WindowsRemovableDriveFinder();
-
-	public List<File> findRemovableDrives() throws IOException {
-		if(OsUtils.isLinux()) return linux.findRemovableDrives();
-		else if(OsUtils.isMac()) return mac.findRemovableDrives();
-		else if(OsUtils.isWindows()) return windows.findRemovableDrives();
-		else return Collections.emptyList();
-	}
-}
diff --git a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
index 70e9078302..f2680f7a12 100644
--- a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
+++ b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
@@ -9,7 +9,9 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.BatchTransportCallback;
 
 class RemovableDrivePlugin extends FilePlugin
@@ -36,9 +38,9 @@ implements RemovableDriveMonitor.Callback {
 	}
 
 	@Override
-	public void start(Map<String, String> localProperties,
-			Map<ContactId, Map<String, String>> remoteProperties,
-			Map<String, String> config) throws IOException {
+	public void start(TransportProperties localProperties,
+			Map<ContactId, TransportProperties> remoteProperties,
+			TransportConfig config) throws IOException {
 		super.start(localProperties, remoteProperties, config);
 		monitor.start(this);
 	}
diff --git a/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java b/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java
index f94317195a..f03cd2ead1 100644
--- a/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java
+++ b/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java
@@ -5,12 +5,11 @@ import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketAddress;
-import java.util.Map;
-import java.util.TreeMap;
 import java.util.concurrent.Executor;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.StreamTransportCallback;
 
 class SimpleSocketPlugin extends SocketPlugin {
@@ -48,12 +47,12 @@ class SimpleSocketPlugin extends SocketPlugin {
 	@Override
 	protected SocketAddress getSocketAddress(ContactId c) {
 		assert started;
-		Map<String, String> properties = remoteProperties.get(c);
+		TransportProperties properties = remoteProperties.get(c);
 		if(properties == null) return null;
 		return createSocketAddress(properties);
 	}
 
-	private SocketAddress createSocketAddress(Map<String, String> properties) {
+	private SocketAddress createSocketAddress(TransportProperties properties) {
 		assert properties != null;
 		String host = properties.get("host");
 		String portString = properties.get("port");
@@ -69,10 +68,10 @@ class SimpleSocketPlugin extends SocketPlugin {
 
 	@Override
 	protected void setLocalSocketAddress(SocketAddress s) {
-		Map<String, String> m;
+		TransportProperties p;
 		synchronized(this) {
 			if(!started) return;
-			m = new TreeMap<String, String>(localProperties);
+			p = new TransportProperties(localProperties);
 		}
 		if(!(s instanceof InetSocketAddress))
 			throw new IllegalArgumentException();
@@ -80,9 +79,9 @@ class SimpleSocketPlugin extends SocketPlugin {
 		String host = i.getAddress().getHostAddress();
 		String port = String.valueOf(i.getPort());
 		// FIXME: Special handling for private IP addresses?
-		m.put("host", host);
-		m.put("port", port);
-		callback.setLocalProperties(m);
+		p.put("host", host);
+		p.put("port", port);
+		callback.setLocalProperties(p);
 	}
 
 	@Override
diff --git a/components/net/sf/briar/plugins/socket/SocketPlugin.java b/components/net/sf/briar/plugins/socket/SocketPlugin.java
index a333bf3bb2..f8158663b2 100644
--- a/components/net/sf/briar/plugins/socket/SocketPlugin.java
+++ b/components/net/sf/briar/plugins/socket/SocketPlugin.java
@@ -10,6 +10,8 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.StreamTransportCallback;
 import net.sf.briar.api.plugins.StreamTransportPlugin;
 import net.sf.briar.api.transport.StreamTransportConnection;
@@ -41,9 +43,9 @@ implements StreamTransportPlugin {
 	}
 
 	@Override
-	public synchronized void start(Map<String, String> localProperties,
-			Map<ContactId, Map<String, String>> remoteProperties,
-			Map<String, String> config) throws IOException {
+	public synchronized void start(TransportProperties localProperties,
+			Map<ContactId, TransportProperties> remoteProperties,
+			TransportConfig config) throws IOException {
 		super.start(localProperties, remoteProperties, config);
 		executor.execute(createBinder());
 	}
@@ -129,9 +131,8 @@ implements StreamTransportPlugin {
 	}
 
 	@Override
-	public synchronized void setLocalProperties(
-			Map<String, String> properties) {
-		super.setLocalProperties(properties);
+	public synchronized void setLocalProperties(TransportProperties p) {
+		super.setLocalProperties(p);
 		// Close and reopen the socket if its address has changed
 		if(socket != null) {
 			SocketAddress addr = socket.getLocalSocketAddress();
diff --git a/components/net/sf/briar/protocol/TransportFactory.java b/components/net/sf/briar/protocol/TransportFactory.java
index 39e9792315..7cec4d4e1d 100644
--- a/components/net/sf/briar/protocol/TransportFactory.java
+++ b/components/net/sf/briar/protocol/TransportFactory.java
@@ -3,10 +3,11 @@ package net.sf.briar.protocol;
 import java.util.Map;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.TransportUpdate;
 
 interface TransportFactory {
 
 	TransportUpdate createTransportUpdate(
-			Map<TransportId, Map<String, String>> transports, long timestamp);
+			Map<TransportId, TransportProperties> transports, long timestamp);
 }
diff --git a/components/net/sf/briar/protocol/TransportFactoryImpl.java b/components/net/sf/briar/protocol/TransportFactoryImpl.java
index 8f8d310c12..25c88070c2 100644
--- a/components/net/sf/briar/protocol/TransportFactoryImpl.java
+++ b/components/net/sf/briar/protocol/TransportFactoryImpl.java
@@ -3,12 +3,13 @@ package net.sf.briar.protocol;
 import java.util.Map;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.TransportUpdate;
 
 class TransportFactoryImpl implements TransportFactory {
 
 	public TransportUpdate createTransportUpdate(
-			Map<TransportId, Map<String, String>> transports, long timestamp) {
+			Map<TransportId, TransportProperties> transports, long timestamp) {
 		return new TransportUpdateImpl(transports, timestamp);
 	}
 }
diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java
index 7041154bfd..196d8560fc 100644
--- a/components/net/sf/briar/protocol/TransportReader.java
+++ b/components/net/sf/briar/protocol/TransportReader.java
@@ -7,6 +7,7 @@ import java.util.TreeMap;
 
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.ProtocolConstants;
 import net.sf.briar.api.protocol.TransportUpdate;
 import net.sf.briar.api.protocol.Types;
@@ -17,11 +18,11 @@ import net.sf.briar.api.serial.Reader;
 class TransportReader implements ObjectReader<TransportUpdate> {
 
 	private final TransportFactory transportFactory;
-	private final ObjectReader<TransportProperties> propertiesReader;
+	private final ObjectReader<Transport> propertiesReader;
 
 	TransportReader(TransportFactory transportFactory) {
 		this.transportFactory = transportFactory;
-		propertiesReader = new TransportPropertiesReader();
+		propertiesReader = new PropertiesReader();
 	}
 
 	public TransportUpdate readObject(Reader r) throws IOException {
@@ -33,14 +34,14 @@ class TransportReader implements ObjectReader<TransportUpdate> {
 		r.readUserDefinedId(Types.TRANSPORT_UPDATE);
 		r.addObjectReader(Types.TRANSPORT_PROPERTIES, propertiesReader);
 		r.setMaxStringLength(ProtocolConstants.MAX_PACKET_LENGTH);
-		List<TransportProperties> l = r.readList(TransportProperties.class);
+		List<Transport> l = r.readList(Transport.class);
 		r.resetMaxStringLength();
 		r.removeObjectReader(Types.TRANSPORT_PROPERTIES);
 		if(l.size() > TransportUpdate.MAX_PLUGINS_PER_UPDATE)
 			throw new FormatException();
-		Map<TransportId, Map<String, String>> transports =
-			new TreeMap<TransportId, Map<String, String>>();
-		for(TransportProperties t : l) {
+		Map<TransportId, TransportProperties> transports =
+			new TreeMap<TransportId, TransportProperties>();
+		for(Transport t : l) {
 			if(transports.put(t.id, t.properties) != null)
 				throw new FormatException(); // Duplicate transport ID
 		}
@@ -50,33 +51,31 @@ class TransportReader implements ObjectReader<TransportUpdate> {
 		return transportFactory.createTransportUpdate(transports, timestamp);
 	}
 
-	private static class TransportProperties {
+	private static class Transport {
 
 		private final TransportId id;
-		private final Map<String, String> properties;
+		private final TransportProperties properties;
 
-		TransportProperties(TransportId id, Map<String, String> properties) {
+		Transport(TransportId id, TransportProperties properties) {
 			this.id = id;
 			this.properties = properties;
 		}
 	}
 
-	private static class TransportPropertiesReader
-	implements ObjectReader<TransportProperties> {
+	private static class PropertiesReader implements ObjectReader<Transport> {
 
-		public TransportProperties readObject(Reader r) throws IOException {
+		public Transport readObject(Reader r) throws IOException {
 			r.readUserDefinedId(Types.TRANSPORT_PROPERTIES);
 			int i = r.readInt32();
 			if(i < TransportId.MIN_ID || i > TransportId.MAX_ID)
 				throw new FormatException();
 			TransportId id = new TransportId(i);
 			r.setMaxStringLength(TransportUpdate.MAX_KEY_OR_VALUE_LENGTH);
-			Map<String, String> properties =
-				r.readMap(String.class, String.class);
+			Map<String, String> m = r.readMap(String.class, String.class);
 			r.resetMaxStringLength();
-			if(properties.size() > TransportUpdate.MAX_PROPERTIES_PER_PLUGIN)
+			if(m.size() > TransportUpdate.MAX_PROPERTIES_PER_PLUGIN)
 				throw new FormatException();
-			return new TransportProperties(id, properties);
+			return new Transport(id, new TransportProperties(m));
 		}
 	}
 }
diff --git a/components/net/sf/briar/protocol/TransportUpdateImpl.java b/components/net/sf/briar/protocol/TransportUpdateImpl.java
index 98cf0465b9..330275ca0c 100644
--- a/components/net/sf/briar/protocol/TransportUpdateImpl.java
+++ b/components/net/sf/briar/protocol/TransportUpdateImpl.java
@@ -3,20 +3,21 @@ package net.sf.briar.protocol;
 import java.util.Map;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.TransportUpdate;
 
 class TransportUpdateImpl implements TransportUpdate {
 
-	private final Map<TransportId, Map<String, String>> transports;
+	private final Map<TransportId, TransportProperties> transports;
 	private final long timestamp;
 
-	TransportUpdateImpl(Map<TransportId, Map<String, String>> transports,
+	TransportUpdateImpl(Map<TransportId, TransportProperties> transports,
 			long timestamp) {
 		this.transports = transports;
 		this.timestamp = timestamp;
 	}
 
-	public Map<TransportId, Map<String, String>> getTransports() {
+	public Map<TransportId, TransportProperties> getTransports() {
 		return transports;
 	}
 
diff --git a/components/net/sf/briar/protocol/writers/TransportWriterImpl.java b/components/net/sf/briar/protocol/writers/TransportWriterImpl.java
index f89528c472..6e3ca629b7 100644
--- a/components/net/sf/briar/protocol/writers/TransportWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/TransportWriterImpl.java
@@ -6,6 +6,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.writers.TransportWriter;
 import net.sf.briar.api.serial.Writer;
@@ -22,11 +23,11 @@ class TransportWriterImpl implements TransportWriter {
 	}
 
 	public void writeTransports(
-			Map<TransportId, Map<String, String>> transports, long timestamp)
+			Map<TransportId, TransportProperties> transports, long timestamp)
 	throws IOException {
 		w.writeUserDefinedId(Types.TRANSPORT_UPDATE);
 		w.writeListStart();
-		for(Entry<TransportId, Map<String, String>> e : transports.entrySet()) {
+		for(Entry<TransportId, TransportProperties> e : transports.entrySet()) {
 			w.writeUserDefinedId(Types.TRANSPORT_PROPERTIES);
 			w.writeInt32(e.getKey().getInt());
 			w.writeMap(e.getValue());
diff --git a/test/net/sf/briar/ProtocolIntegrationTest.java b/test/net/sf/briar/ProtocolIntegrationTest.java
index 8d47d3f874..374ce206e4 100644
--- a/test/net/sf/briar/ProtocolIntegrationTest.java
+++ b/test/net/sf/briar/ProtocolIntegrationTest.java
@@ -16,6 +16,7 @@ import java.util.Map;
 
 import junit.framework.TestCase;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.Author;
@@ -75,7 +76,7 @@ public class ProtocolIntegrationTest extends TestCase {
 	private final Message message, message1, message2, message3;
 	private final String authorName = "Alice";
 	private final String messageBody = "Hello world";
-	private final Map<TransportId, Map<String, String>> transports;
+	private final Map<TransportId, TransportProperties> transports;
 
 	public ProtocolIntegrationTest() throws Exception {
 		super();
@@ -116,8 +117,9 @@ public class ProtocolIntegrationTest extends TestCase {
 		message3 = messageEncoder.encodeMessage(null, group1,
 				groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
 				messageBody.getBytes("UTF-8"));
-		transports = Collections.singletonMap(transportId,
-				Collections.singletonMap("bar", "baz"));
+		TransportProperties p =
+			new TransportProperties(Collections.singletonMap("bar", "baz"));
+		transports = Collections.singletonMap(transportId, p);
 	}
 
 	@Test
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 158e43731d..facb84dd6e 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -10,7 +10,9 @@ import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseListener;
 import net.sf.briar.api.db.DatabaseListener.Event;
@@ -54,8 +56,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 	private final Message message, privateMessage;
 	private final Group group;
 	private final TransportId transportId;
-	private final Map<TransportId, Map<String, String>> transports;
-	private final Map<TransportId, Map<ContactId, Map<String, String>>>
+	private final Map<TransportId, TransportProperties> transports;
+	private final Map<TransportId, Map<ContactId, TransportProperties>>
 	remoteTransports;
 	private final byte[] secret;
 
@@ -76,10 +78,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			new TestMessage(messageId, null, null, null, timestamp, raw);
 		group = new TestGroup(groupId, "The really exciting group", null);
 		transportId = new TransportId(123);
-		Map<String, String> properties = Collections.singletonMap("foo", "bar");
-		transports = Collections.singletonMap(transportId, properties);
+		TransportProperties p =
+			new TransportProperties(Collections.singletonMap("foo", "bar"));
+		transports = Collections.singletonMap(transportId, p);
 		remoteTransports = Collections.singletonMap(transportId,
-				Collections.singletonMap(contactId, properties));
+				Collections.singletonMap(contactId, p));
 		secret = new byte[123];
 	}
 
@@ -1271,10 +1274,10 @@ public abstract class DatabaseComponentTest extends TestCase {
 	@Test
 	public void testTransportPropertiesChangedCallsListeners()
 	throws Exception {
-		final Map<String, String> properties =
-			Collections.singletonMap("bar", "baz");
-		final Map<String, String> properties1 =
-			Collections.singletonMap("baz", "bam");
+		final TransportProperties properties =
+			new TransportProperties(Collections.singletonMap("bar", "baz"));
+		final TransportProperties properties1 =
+			new TransportProperties(Collections.singletonMap("baz", "bam"));
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
@@ -1302,8 +1305,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 	@Test
 	public void testTransportPropertiesUnchangedDoesNotCallListeners()
 	throws Exception {
-		final Map<String, String> properties =
-			Collections.singletonMap("bar", "baz");
+		final TransportProperties properties =
+			new TransportProperties(Collections.singletonMap("bar", "baz"));
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
@@ -1327,10 +1330,10 @@ public abstract class DatabaseComponentTest extends TestCase {
 
 	@Test
 	public void testTransportConfigChangedCallsListeners() throws Exception {
-		final Map<String, String> config =
-			Collections.singletonMap("bar", "baz");
-		final Map<String, String> config1 =
-			Collections.singletonMap("baz", "bam");
+		final TransportConfig config =
+			new TransportConfig(Collections.singletonMap("bar", "baz"));
+		final TransportConfig config1 =
+			new TransportConfig(Collections.singletonMap("baz", "bam"));
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
@@ -1356,8 +1359,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 	@Test
 	public void testTransportConfigUnchangedDoesNotCallListeners()
 	throws Exception {
-		final Map<String, String> config =
-			Collections.singletonMap("bar", "baz");
+		final TransportConfig config =
+			new TransportConfig(Collections.singletonMap("bar", "baz"));
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 343757ef34..fb3fe636cc 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -21,7 +21,9 @@ import net.sf.briar.TestDatabaseModule;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.crypto.Password;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
@@ -71,8 +73,8 @@ public class H2DatabaseTest extends TestCase {
 	private final Message message, privateMessage;
 	private final Group group;
 	private final TransportId transportId;
-	private final Map<TransportId, Map<String, String>> transports;
-	private final Map<TransportId, Map<ContactId, Map<String, String>>>
+	private final Map<TransportId, TransportProperties> transports;
+	private final Map<TransportId, Map<ContactId, TransportProperties>>
 	remoteTransports;
 	private final Map<Group, Long> subscriptions;
 	private final byte[] secret;
@@ -100,10 +102,11 @@ public class H2DatabaseTest extends TestCase {
 			new TestMessage(privateMessageId, null, null, null, timestamp, raw);
 		group = groupFactory.createGroup(groupId, "Group name", null);
 		transportId = new TransportId(0);
-		Map<String, String> properties = Collections.singletonMap("foo", "bar");
-		transports = Collections.singletonMap(transportId, properties);
+		TransportProperties p =
+			new TransportProperties(Collections.singletonMap("foo", "bar"));
+		transports = Collections.singletonMap(transportId, p);
 		remoteTransports = Collections.singletonMap(transportId,
-				Collections.singletonMap(contactId, properties));
+				Collections.singletonMap(contactId, p));
 		subscriptions = Collections.singletonMap(group, 0L);
 		secret = new byte[123];
 	}
@@ -988,9 +991,10 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testUpdateTransportProperties() throws Exception {
-		Map<String, String> properties = Collections.singletonMap("foo", "bar");
-		Map<String, String> properties1 =
-			Collections.singletonMap("baz", "bam");
+		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();
@@ -1001,12 +1005,12 @@ public class H2DatabaseTest extends TestCase {
 
 		// Replace the transport properties
 		TransportId transportId1 = new TransportId(1);
-		Map<TransportId, Map<String, String>> transports1 =
-			new TreeMap<TransportId, Map<String, String>>();
+		Map<TransportId, TransportProperties> transports1 =
+			new TreeMap<TransportId, TransportProperties>();
 		transports1.put(transportId, properties);
 		transports1.put(transportId1, properties1);
-		Map<TransportId, Map<ContactId, Map<String, String>>> remoteTransports1
-		= new TreeMap<TransportId, Map<ContactId, Map<String, String>>>();
+		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,
@@ -1016,19 +1020,18 @@ public class H2DatabaseTest extends TestCase {
 
 		// Remove the transport properties
 		db.setTransports(txn, contactId,
-				Collections.<TransportId, Map<String, String>>emptyMap(), 2);
+				Collections.<TransportId, TransportProperties>emptyMap(), 2);
 		assertEquals(Collections.emptyMap(), db.getRemoteTransports(txn));
 
 		// Set the local transport properties
-		for(Entry<TransportId, Map<String, String>> e : transports.entrySet()) {
+		for(Entry<TransportId, TransportProperties> e : transports.entrySet()) {
 			db.setTransportProperties(txn, e.getKey(), e.getValue());
 		}
 		assertEquals(transports, db.getLocalTransports(txn));
 
 		// Remove the local transport properties
 		for(TransportId t : transports.keySet()) {
-			db.setTransportProperties(txn, t,
-					Collections.<String, String>emptyMap());
+			db.setTransportProperties(txn, t, new TransportProperties());
 		}
 		assertEquals(Collections.emptyMap(), db.getLocalTransports(txn));
 
@@ -1039,8 +1042,10 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testUpdateTransportConfig() throws Exception {
-		Map<String, String> config = Collections.singletonMap("foo", "bar");
-		Map<String, String> config1 = Collections.singletonMap("baz", "bam");
+		TransportConfig config =
+			new TransportConfig(Collections.singletonMap("foo", "bar"));
+		TransportConfig config1 =
+			new TransportConfig(Collections.singletonMap("baz", "bam"));
 
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
@@ -1054,8 +1059,7 @@ public class H2DatabaseTest extends TestCase {
 		assertEquals(config1, db.getTransportConfig(txn, transportId));
 
 		// Remove the transport config
-		db.setTransportConfig(txn, transportId,
-				Collections.<String, String>emptyMap());
+		db.setTransportConfig(txn, transportId, new TransportConfig());
 		assertEquals(Collections.emptyMap(),
 				db.getTransportConfig(txn, transportId));
 
@@ -1065,11 +1069,12 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testTransportsNotUpdatedIfTimestampIsOld() throws Exception {
-		Map<String, String> properties = Collections.singletonMap("foo", "bar");
-		Map<String, String> properties1 =
-			Collections.singletonMap("baz", "bam");
-		Map<String, String> properties2 =
-			Collections.singletonMap("quux", "etc");
+		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();
@@ -1080,12 +1085,12 @@ public class H2DatabaseTest extends TestCase {
 
 		// Replace the transport properties using a timestamp of 2
 		TransportId transportId1 = new TransportId(1);
-		Map<TransportId, Map<String, String>> transports1 =
-			new TreeMap<TransportId, Map<String, String>>();
+		Map<TransportId, TransportProperties> transports1 =
+			new TreeMap<TransportId, TransportProperties>();
 		transports1.put(transportId, properties);
 		transports1.put(transportId1, properties1);
-		Map<TransportId, Map<ContactId, Map<String, String>>> remoteTransports1
-		= new TreeMap<TransportId, Map<ContactId, Map<String, String>>>();
+		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,
@@ -1095,8 +1100,8 @@ public class H2DatabaseTest extends TestCase {
 
 		// Try to replace the transport properties using a timestamp of 1
 		TransportId transportId2 = new TransportId(2);
-		Map<TransportId, Map<String, String>> transports2 =
-			new TreeMap<TransportId, Map<String, String>>();
+		Map<TransportId, TransportProperties> transports2 =
+			new TreeMap<TransportId, TransportProperties>();
 		transports2.put(transportId1, properties1);
 		transports2.put(transportId2, properties2);
 		db.setTransports(txn, contactId, transports2, 1);
diff --git a/test/net/sf/briar/invitation/InvitationWorkerTest.java b/test/net/sf/briar/invitation/InvitationWorkerTest.java
index a6495143fb..524412b0e6 100644
--- a/test/net/sf/briar/invitation/InvitationWorkerTest.java
+++ b/test/net/sf/briar/invitation/InvitationWorkerTest.java
@@ -10,6 +10,8 @@ import java.util.Map;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
+import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.invitation.InvitationCallback;
@@ -107,8 +109,11 @@ public class InvitationWorkerTest extends TestCase {
 
 	private void testInstallerCreation(final boolean createExe,
 			final boolean createJar) throws IOException, DbException {
-		final Map<String, String> transports =
-			Collections.singletonMap("foo", "bar");
+		TransportProperties properties =
+			new TransportProperties(Collections.singletonMap("foo", "bar"));
+		TransportId transportId = new TransportId(123);
+		final Map<TransportId, TransportProperties> transports =
+			Collections.singletonMap(transportId, properties);
 		final File setup = new File(testDir, "setup.dat");
 		TestUtils.createFile(setup, "foo bar baz");
 		final File invitation = new File(testDir, "invitation.dat");
diff --git a/test/net/sf/briar/plugins/StubStreamCallback.java b/test/net/sf/briar/plugins/StubStreamCallback.java
deleted file mode 100644
index 765ac73fc6..0000000000
--- a/test/net/sf/briar/plugins/StubStreamCallback.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package net.sf.briar.plugins;
-
-import java.util.Map;
-
-import net.sf.briar.api.ContactId;
-import net.sf.briar.api.plugins.StreamTransportCallback;
-import net.sf.briar.api.transport.StreamTransportConnection;
-
-public class StubStreamCallback implements StreamTransportCallback {
-
-	public Map<String, String> localProperties = null;
-	public volatile int incomingConnections = 0;
-
-	public void setLocalProperties(Map<String, String> properties) {
-		localProperties = properties;
-	}
-
-	public void setConfig(Map<String, String> config) {
-	}
-
-	public void showMessage(String... message) {
-	}
-
-	public boolean showConfirmationMessage(String... message) {
-		return false;
-	}
-
-	public int showChoice(String[] choices, String... message) {
-		return -1;
-	}
-
-	public void incomingConnectionCreated(StreamTransportConnection c) {
-		incomingConnections++;
-	}
-
-	public void outgoingConnectionCreated(ContactId contactId,
-			StreamTransportConnection c) {
-	}
-}
diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java
index 7840aa34f6..4e23ee9dce 100644
--- a/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java
+++ b/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java
@@ -1,13 +1,13 @@
 package net.sf.briar.plugins.bluetooth;
 
 import java.io.PrintStream;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Scanner;
-import java.util.TreeMap;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.StreamTransportCallback;
 import net.sf.briar.api.transport.StreamTransportConnection;
 import net.sf.briar.plugins.ImmediateExecutor;
@@ -24,16 +24,16 @@ public class BluetoothClientTest {
 			System.exit(1);
 		}
 		ContactId contactId = new ContactId(0);
-		Map<String, String> localProperties = Collections.emptyMap();
-		Map<ContactId, Map<String, String>> remoteProperties =
-			new HashMap<ContactId, Map<String, String>>();
-		Map<String, String> config = Collections.emptyMap();
+		TransportProperties localProperties = new TransportProperties();
+		Map<ContactId, TransportProperties> remoteProperties =
+			new HashMap<ContactId, TransportProperties>();
+		TransportConfig config = new TransportConfig();
 		StreamTransportCallback callback = new ClientCallback();
 		// Store the server's Bluetooth address and UUID
-		Map<String, String> properties = new TreeMap<String, String>();
-		properties.put("address", args[0]);
-		properties.put("uuid", BluetoothServerTest.UUID);
-		remoteProperties.put(contactId, properties);
+		TransportProperties p = new TransportProperties();
+		p.put("address", args[0]);
+		p.put("uuid", BluetoothServerTest.UUID);
+		remoteProperties.put(contactId, p);
 		// Create the plugin
 		BluetoothPlugin plugin =
 			new BluetoothPlugin(new ImmediateExecutor(), callback, 0L);
@@ -66,9 +66,9 @@ public class BluetoothClientTest {
 
 	private static class ClientCallback implements StreamTransportCallback {
 
-		public void setLocalProperties(Map<String, String> properties) {}
+		public void setLocalProperties(TransportProperties p) {}
 
-		public void setConfig(Map<String, String> config) {}
+		public void setConfig(TransportConfig c) {}
 
 		public void showMessage(String... message) {}
 
diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java
index 17bf3d94a2..8ccc1b3e4c 100644
--- a/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java
+++ b/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java
@@ -5,9 +5,10 @@ import java.io.PrintStream;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Scanner;
-import java.util.TreeMap;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.StreamTransportCallback;
 import net.sf.briar.api.transport.StreamTransportConnection;
 import net.sf.briar.plugins.ImmediateExecutor;
@@ -20,10 +21,10 @@ public class BluetoothServerTest {
 	public static final String CHALLENGE = "Potatoes!";
 
 	public static void main(String[] args) throws Exception {
-		Map<String, String> localProperties = Collections.emptyMap();
-		Map<ContactId, Map<String, String>> remoteProperties =
+		TransportProperties localProperties = new TransportProperties();
+		Map<ContactId, TransportProperties> remoteProperties =
 			Collections.emptyMap();
-		Map<String, String> config = new TreeMap<String, String>();
+		TransportConfig config = new TransportConfig();
 		StreamTransportCallback callback = new ServerCallback();
 		// Store the UUID
 		config.put("uuid", UUID);
@@ -45,9 +46,9 @@ public class BluetoothServerTest {
 
 	private static class ServerCallback implements StreamTransportCallback {
 
-		public void setLocalProperties(Map<String, String> properties) {}
+		public void setLocalProperties(TransportProperties p) {}
 
-		public void setConfig(Map<String, String> config) {}
+		public void setConfig(TransportConfig c) {}
 
 		public void showMessage(String... message) {}
 
diff --git a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
index 691c924123..5b4e524697 100644
--- a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
+++ b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
@@ -8,12 +8,13 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 import java.util.concurrent.Executor;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.plugins.BatchTransportCallback;
 import net.sf.briar.api.transport.BatchTransportWriter;
 import net.sf.briar.api.transport.TransportConstants;
@@ -31,16 +32,16 @@ public class RemovableDrivePluginTest extends TestCase {
 	private final File testDir = TestUtils.getTestDirectory();
 	private final ContactId contactId = new ContactId(0);
 
-	private Map<String, String> localProperties = null;
-	private Map<ContactId, Map<String, String>> remoteProperties = null;
-	private Map<String, String> config = null;
+	private TransportProperties localProperties = null;
+	private Map<ContactId, TransportProperties> remoteProperties = null;
+	private TransportConfig config = null;
 
 	@Before
 	public void setUp() {
-		localProperties = new TreeMap<String, String>();
-		remoteProperties = new HashMap<ContactId, Map<String, String>>();
-		remoteProperties.put(contactId, new TreeMap<String, String>());
-		config = new TreeMap<String, String>();
+		localProperties = new TransportProperties();
+		remoteProperties = new HashMap<ContactId, TransportProperties>();
+		remoteProperties.put(contactId, new TransportProperties());
+		config = new TransportConfig();
 		testDir.mkdirs();
 	}
 
diff --git a/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java b/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java
index 702d4ea554..16d8ebd6c3 100644
--- a/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java
+++ b/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java
@@ -6,16 +6,17 @@ import java.net.ServerSocket;
 import java.net.Socket;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.TreeMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import junit.framework.TestCase;
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportConfig;
+import net.sf.briar.api.TransportProperties;
+import net.sf.briar.api.plugins.StreamTransportCallback;
 import net.sf.briar.api.transport.StreamTransportConnection;
 import net.sf.briar.plugins.ImmediateExecutor;
-import net.sf.briar.plugins.StubStreamCallback;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -24,21 +25,21 @@ public class SimpleSocketPluginTest extends TestCase {
 
 	private final ContactId contactId = new ContactId(0);
 
-	private Map<String, String> localProperties = null;
-	private Map<ContactId, Map<String, String>> remoteProperties = null;
-	private Map<String, String> config = null;
+	private TransportProperties localProperties = null;
+	private Map<ContactId, TransportProperties> remoteProperties = null;
+	private TransportConfig config = null;
 
 	@Before
 	public void setUp() {
-		localProperties = new TreeMap<String, String>();
-		remoteProperties = new HashMap<ContactId, Map<String, String>>();
-		remoteProperties.put(contactId, new TreeMap<String, String>());
-		config = new TreeMap<String, String>();
+		localProperties = new TransportProperties();
+		remoteProperties = new HashMap<ContactId, TransportProperties>();
+		remoteProperties.put(contactId, new TransportProperties());
+		config = new TransportConfig();
 	}
 
 	@Test
 	public void testIncomingConnection() throws Exception {
-		StubStreamCallback callback = new StubStreamCallback();
+		StubCallback callback = new StubCallback();
 		localProperties.put("host", "127.0.0.1");
 		localProperties.put("port", "0");
 		SimpleSocketPlugin plugin =
@@ -74,7 +75,7 @@ public class SimpleSocketPluginTest extends TestCase {
 
 	@Test
 	public void testOutgoingConnection() throws Exception {
-		StubStreamCallback callback = new StubStreamCallback();
+		StubCallback callback = new StubCallback();
 		SimpleSocketPlugin plugin =
 			new SimpleSocketPlugin(new ImmediateExecutor(), callback, 0L);
 		plugin.start(localProperties, remoteProperties, config);
@@ -96,7 +97,7 @@ public class SimpleSocketPluginTest extends TestCase {
 			}
 		}.start();
 		// Tell the plugin about the port
-		Map<String, String> properties = new TreeMap<String, String>();
+		TransportProperties properties = new TransportProperties();
 		properties.put("host", "127.0.0.1");
 		properties.put("port", String.valueOf(port));
 		plugin.setRemoteProperties(contactId, properties);
@@ -114,7 +115,7 @@ public class SimpleSocketPluginTest extends TestCase {
 
 	@Test
 	public void testUpdatingPropertiesReopensSocket() throws Exception {
-		StubStreamCallback callback = new StubStreamCallback();
+		StubCallback callback = new StubCallback();
 		localProperties.put("host", "127.0.0.1");
 		localProperties.put("port", "0");
 		SimpleSocketPlugin plugin =
@@ -169,4 +170,36 @@ public class SimpleSocketPluginTest extends TestCase {
 			fail();
 		} catch(IOException expected) {}
 	}
+
+	private static class StubCallback implements StreamTransportCallback {
+
+		public TransportProperties localProperties = null;
+		public volatile int incomingConnections = 0;
+
+		public void setLocalProperties(TransportProperties properties) {
+			localProperties = properties;
+		}
+
+		public void setConfig(TransportConfig config) {
+		}
+
+		public void showMessage(String... message) {
+		}
+
+		public boolean showConfirmationMessage(String... message) {
+			return false;
+		}
+
+		public int showChoice(String[] choices, String... message) {
+			return -1;
+		}
+
+		public void incomingConnectionCreated(StreamTransportConnection c) {
+			incomingConnections++;
+		}
+
+		public void outgoingConnectionCreated(ContactId contactId,
+				StreamTransportConnection c) {
+		}
+	}
 }
diff --git a/test/net/sf/briar/protocol/ProtocolReadWriteTest.java b/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
index 2938af94cd..f98a61504c 100644
--- a/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
+++ b/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
@@ -9,6 +9,7 @@ import java.util.Map;
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
@@ -49,7 +50,7 @@ public class ProtocolReadWriteTest extends TestCase {
 	private final BitSet bitSet;
 	private final Map<Group, Long> subscriptions;
 	private final TransportId transportId;
-	private final Map<TransportId, Map<String, String>> transports;
+	private final Map<TransportId, TransportProperties> transports;
 	private final long timestamp = System.currentTimeMillis();
 
 	public ProtocolReadWriteTest() throws Exception {
@@ -70,8 +71,9 @@ public class ProtocolReadWriteTest extends TestCase {
 		bitSet.set(7);
 		subscriptions = Collections.singletonMap(group, 123L);
 		transportId = new TransportId(123);
-		transports = Collections.singletonMap(transportId,
-				Collections.singletonMap("bar", "baz"));
+		TransportProperties p =
+			new TransportProperties(Collections.singletonMap("bar", "baz"));
+		transports = Collections.singletonMap(transportId, p);
 	}
 
 	@Test
diff --git a/test/net/sf/briar/protocol/writers/ConstantsTest.java b/test/net/sf/briar/protocol/writers/ConstantsTest.java
index 24b0b02db7..73d58333b8 100644
--- a/test/net/sf/briar/protocol/writers/ConstantsTest.java
+++ b/test/net/sf/briar/protocol/writers/ConstantsTest.java
@@ -9,6 +9,7 @@ import java.util.TreeMap;
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.protocol.Author;
 import net.sf.briar.api.protocol.AuthorFactory;
@@ -191,18 +192,18 @@ public class ConstantsTest extends TestCase {
 	public void testTransportsFitIntoUpdate() throws Exception {
 		// Create the maximum number of plugins, each with the maximum number
 		// of maximum-length properties
-		Map<TransportId, Map<String, String>> transports =
-			new TreeMap<TransportId, Map<String, String>>();
+		Map<TransportId, TransportProperties> transports =
+			new TreeMap<TransportId, TransportProperties>();
 		for(int i = 0; i < TransportUpdate.MAX_PLUGINS_PER_UPDATE; i++) {
-			Map<String, String> properties = new TreeMap<String, String>();
+			TransportProperties p = new TransportProperties();
 			for(int j = 0; j < TransportUpdate.MAX_PROPERTIES_PER_PLUGIN; j++) {
 				String key = createRandomString(
 						TransportUpdate.MAX_KEY_OR_VALUE_LENGTH);
 				String value = createRandomString(
 						TransportUpdate.MAX_KEY_OR_VALUE_LENGTH);
-				properties.put(key, value);
+				p.put(key, value);
 			}
-			transports.put(new TransportId(i), properties);
+			transports.put(new TransportId(i), p);
 		}
 		// Add the transports to an update
 		ByteArrayOutputStream out = new ByteArrayOutputStream(
diff --git a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
index adf43ad701..4755ba44fa 100644
--- a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
+++ b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
@@ -13,6 +13,7 @@ import net.sf.briar.TestDatabaseModule;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseListener;
 import net.sf.briar.api.protocol.Message;
@@ -45,7 +46,7 @@ public class BatchConnectionReadWriteTest extends TestCase {
 	private final File aliceDir = new File(testDir, "alice");
 	private final File bobDir = new File(testDir, "bob");
 	private final TransportId transportId = new TransportId(123);
-	private final Map<TransportId, Map<String, String>> transports =
+	private final Map<TransportId, TransportProperties> transports =
 		Collections.emptyMap();
 	private final byte[] aliceSecret, bobSecret;
 
-- 
GitLab