diff --git a/api/net/sf/briar/api/db/ContactId.java b/api/net/sf/briar/api/ContactId.java
similarity index 94%
rename from api/net/sf/briar/api/db/ContactId.java
rename to api/net/sf/briar/api/ContactId.java
index 39d362a3a97eb2f843be2aac283fadc87638e3b3..0dbdc140fbd7b8d98a0c056704b5853db7898660 100644
--- a/api/net/sf/briar/api/db/ContactId.java
+++ b/api/net/sf/briar/api/ContactId.java
@@ -1,4 +1,4 @@
-package net.sf.briar.api.db;
+package net.sf.briar.api;
 
 /** Type-safe wrapper for an integer that uniquely identifies a contact. */
 public class ContactId {
diff --git a/api/net/sf/briar/api/db/Rating.java b/api/net/sf/briar/api/Rating.java
similarity index 80%
rename from api/net/sf/briar/api/db/Rating.java
rename to api/net/sf/briar/api/Rating.java
index 2a61dbfde26d10ecf3143b69ea2253978c6dfd4d..a3ddb57c4d5595216a0cefaccc7f51cf2cec3e42 100644
--- a/api/net/sf/briar/api/db/Rating.java
+++ b/api/net/sf/briar/api/Rating.java
@@ -1,4 +1,4 @@
-package net.sf.briar.api.db;
+package net.sf.briar.api;
 
 /** The ratings that may be applied to an author in peer moderation. */
 public enum Rating {
diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index ca142d39df885c9c6a97f15e9a3e87a4010626c3..9d3d75b08bfde09dc56daaf6836278a77d0a4c29 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -1,7 +1,10 @@
 package net.sf.briar.api.db;
 
+import java.util.Map;
 import java.util.Set;
 
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Bundle;
 import net.sf.briar.api.protocol.GroupId;
@@ -32,8 +35,11 @@ public interface DatabaseComponent {
 	/** Waits for any open transactions to finish and closes the database. */
 	void close() throws DbException;
 
-	/** Adds a new contact to the database and returns an ID for the contact. */
-	ContactId addContact() throws DbException;
+	/**
+	 * Adds a new contact to the database with the given transport details and
+	 * returns an ID for the contact.
+	 */
+	ContactId addContact(Map<String, String> transports) throws DbException;
 
 	/** Adds a locally generated message to the database. */
 	void addLocallyGeneratedMessage(Message m) throws DbException;
@@ -53,6 +59,9 @@ public interface DatabaseComponent {
 	/** Returns the set of groups to which the user subscribes. */
 	Set<GroupId> getSubscriptions() throws DbException;
 
+	/** Returns the transport details for the given contact. */
+	Map<String, String> getTransports(ContactId c) throws DbException;
+
 	/**
 	 * Processes a bundle of acknowledgements, subscriptions, and batches of
 	 * messages received from the given contact. Some or all of the messages
@@ -66,6 +75,12 @@ public interface DatabaseComponent {
 	/** Records the user's rating for the given author. */
 	void setRating(AuthorId a, Rating r) throws DbException;
 
+	/**
+	 * Records the transport details for the given contact, replacing any
+	 * existing transport details.
+	 */
+	void setTransports(ContactId c, Map<String, String> transports) throws DbException;
+
 	/** Subscribes to the given group. */
 	void subscribe(GroupId g) throws DbException;
 
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index a0e611a8ef3a784c18e46c4d09f6069910b6460b..ca2cdb0b7b8d9dd6905ac716d89b317968f1e307 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -1,10 +1,11 @@
 package net.sf.briar.db;
 
+import java.util.Map;
 import java.util.Set;
 
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.ContactId;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
@@ -28,6 +29,7 @@ import net.sf.briar.api.protocol.MessageId;
  * <li> messageStatuses
  * <li> ratings
  * <li> subscriptions
+ * <li> transports
  * </ul>
  */
 interface Database<T> {
@@ -49,7 +51,11 @@ interface Database<T> {
 	 */
 	void open(boolean resume) throws DbException;
 
-	/** Waits for all open transactions to finish and closes the database. */
+	/**
+	 * Waits for all open transactions to finish and closes the database.
+	 * <p>
+	 * Locking: all locks write.
+	 */
 	void close() throws DbException;
 
 	/** Starts a new transaction and returns an object representing it. */
@@ -75,11 +81,12 @@ interface Database<T> {
 	void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException;
 
 	/**
-	 * Adds a new contact to the database and returns an ID for the contact.
+	 * Adds a new contact to the database with the given transport details and
+	 * returns an ID for the contact.
 	 * <p>
-	 * Locking: contacts write, messageStatuses write.
+	 * Locking: contacts write, transports write.
 	 */
-	ContactId addContact(T txn) throws DbException;
+	ContactId addContact(T txn, Map<String, String> transports) throws DbException;
 
 	/**
 	 * Returns false if the given message is already in the database. Otherwise
@@ -115,14 +122,14 @@ interface Database<T> {
 	/**
 	 * Records a contact's subscription to a group.
 	 * <p>
-	 * Locking: contacts read, messageStatuses write.
+	 * Locking: contacts read, subscriptions write.
 	 */
 	void addSubscription(T txn, ContactId c, GroupId g) throws DbException;
 
 	/**
 	 * Removes all recorded subscriptions for the given contact.
 	 * <p>
-	 * Locking: contacts read, messageStatuses write.
+	 * Locking: contacts read, subscriptions write.
 	 */
 	void clearSubscriptions(T txn, ContactId c) throws DbException;
 
@@ -150,7 +157,7 @@ interface Database<T> {
 	/**
 	 * Returns the IDs of all contacts.
 	 * <p>
-	 * Locking: contacts read, messageStatuses read.
+	 * Locking: contacts read.
 	 */
 	Set<ContactId> getContacts(T txn) throws DbException;
 
@@ -238,6 +245,13 @@ interface Database<T> {
 	 */
 	Set<GroupId> getSubscriptions(T txn) throws DbException;
 
+	/**
+	 * Returns the transport details for the given contact.
+	 * <p>
+	 * Locking: contacts read, transports read.
+	 */
+	Map<String, String> getTransports(T txn, ContactId c) throws DbException;
+
 	/**
 	 * Removes an outstanding batch that has been acknowledged. Any messages in
 	 * the batch that are still considered outstanding (Status.SENT) with
@@ -258,7 +272,8 @@ interface Database<T> {
 	/**
 	 * Removes a contact (and all associated state) from the database.
 	 * <p>
-	 * Locking: contacts write, messageStatuses write.
+	 * Locking: contacts write, messageStatuses write, subscriptions write,
+	 * transports write.
 	 */
 	void removeContact(T txn, ContactId c) throws DbException;
 
@@ -282,20 +297,20 @@ interface Database<T> {
 	 * Unsubscribes from the given group. Any messages belonging to the group
 	 * are deleted from the database.
 	 * <p>
-	 * Locking: contacts read, subscriptions write, messages write,
-	 * messageStatuses write.
+	 * Locking: contacts read, messages write, messageStatuses write,
+	 * subscriptions write.
 	 */
 	void removeSubscription(T txn, GroupId g) throws DbException;
 
 	/**
-	 * Records the user's rating for the given author.
+	 * Sets the user's rating for the given author.
 	 * <p>
 	 * Locking: ratings write.
 	 */
 	Rating setRating(T txn, AuthorId a, Rating r) throws DbException;
 
 	/**
-	 * Records the sendability score of the given message.
+	 * Sets the sendability score of the given message.
 	 * <p>
 	 * Locking: messages write.
 	 */
@@ -307,4 +322,12 @@ interface Database<T> {
 	 * Locking: contacts read, messages read, messageStatuses write.
 	 */
 	void setStatus(T txn, ContactId c, MessageId m, Status s) throws DbException;
+
+	/**
+	 * Sets the transport details for the given contact, replacing any existing
+	 * transport details.
+	 * <p>
+	 * Locking: contacts read, transports write.
+	 */
+	void setTransports(T txn, ContactId c, Map<String, String> transports) throws DbException;
 }
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index edd4fe55661c895eb4fac16f12f51a7e30870f71..b2cfa0bdaab4eb6f257da12e37216e90c4032e53 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -3,10 +3,10 @@ package net.sf.briar.db;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.sf.briar.api.db.ContactId;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index e0440e5ce168334869546ed8170123cc828f4382..dc6b73259f2399d2dfc1cd9d51688fb012ad592e 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -13,13 +13,16 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.sf.briar.api.db.ContactId;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
@@ -147,6 +150,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_STATUSES_BY_CONTACT =
 		"CREATE INDEX statusesByContact ON statuses (contactId)";
 
+	private static final String CREATE_TRANSPORTS =
+		"CREATE TABLE transports"
+		+ " (contactId INT NOT NULL,"
+		+ " detailKey VARCHAR NOT NULL,"
+		+ " detailValue VARCHAR NOT NULL,"
+		+ " PRIMARY KEY (contactId, detailKey),"
+		+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+		+ " ON DELETE CASCADE)";
+
 	private static final Logger LOG =
 		Logger.getLogger(JdbcDatabase.class.getName());
 
@@ -236,6 +248,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			s.executeUpdate(insertHashType(CREATE_STATUSES));
 			s.executeUpdate(INDEX_STATUSES_BY_MESSAGE);
 			s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
+			if(LOG.isLoggable(Level.FINE))
+				LOG.fine("Creating transports table");
+			s.executeUpdate(insertHashType(CREATE_TRANSPORTS));
 			s.close();
 		} catch(SQLException e) {
 			tryToClose(s);
@@ -363,7 +378,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public ContactId addContact(Connection txn) throws DbException {
+	public ContactId addContact(Connection txn, Map<String, String> transports)
+	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -400,6 +416,25 @@ abstract class JdbcDatabase implements Database<Connection> {
 			rowsAffected = ps.executeUpdate();
 			assert rowsAffected == 1;
 			ps.close();
+			// Store the contact's transport details
+			if(transports != null) {
+				sql = "INSERT INTO transports"
+					+ " (contactId, detailKey, detailValue)"
+					+ " VALUES (?, ?, ?)";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				for(Entry<String, String> e : transports.entrySet()) {
+					ps.setString(2, e.getKey());
+					ps.setString(3, e.getValue());
+					ps.addBatch();
+				}
+				int[] rowsAffectedArray = ps.executeBatch();
+				assert rowsAffectedArray.length == transports.size();
+				for(int i = 0; i < rowsAffectedArray.length; i++) {
+					assert rowsAffectedArray[i] == 1;
+				}
+				ps.close();
+			}
 			return c;
 		} catch(SQLException e) {
 			tryToClose(ps);
@@ -476,10 +511,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 				ps.setBytes(3, m.getBytes());
 				ps.addBatch();
 			}
-			int[] rowsAffected1 = ps.executeBatch();
-			assert rowsAffected1.length == sent.size();
-			for(int i = 0; i < rowsAffected1.length; i++) {
-				assert rowsAffected1[i] == 1;
+			int[] rowsAffectedArray = ps.executeBatch();
+			assert rowsAffectedArray.length == sent.size();
+			for(int i = 0; i < rowsAffectedArray.length; i++) {
+				assert rowsAffectedArray[i] == 1;
 			}
 			ps.close();
 			// Set the status of each message in the batch to SENT
@@ -493,10 +528,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 				ps.setBytes(2, m.getBytes());
 				ps.addBatch();
 			}
-			rowsAffected1 = ps.executeBatch();
-			assert rowsAffected1.length == sent.size();
-			for(int i = 0; i < rowsAffected1.length; i++) {
-				assert rowsAffected1[i] <= 1;
+			rowsAffectedArray = ps.executeBatch();
+			assert rowsAffectedArray.length == sent.size();
+			for(int i = 0; i < rowsAffectedArray.length; i++) {
+				assert rowsAffectedArray[i] <= 1;
 			}
 			ps.close();
 		} catch(SQLException e) {
@@ -1067,6 +1102,29 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public Map<String, String> getTransports(Connection txn, ContactId c)
+	throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT detailKey, detailValue FROM transports"
+				+ " WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			rs = ps.executeQuery();
+			Map<String, String> transports = new TreeMap<String, String>();
+			while(rs.next()) transports.put(rs.getString(1), rs.getString(2));
+			rs.close();
+			ps.close();
+			return transports;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			tryToClose(txn);
+			throw new DbException(e);
+		}
+	}
+
 	public void removeAckedBatch(Connection txn, ContactId c, BatchId b)
 	throws DbException {
 		removeBatch(txn, c, b, Status.SEEN);
@@ -1097,18 +1155,18 @@ abstract class JdbcDatabase implements Database<Connection> {
 			}
 			rs.close();
 			ps.close();
-			int[] rowsAffected = ps1.executeBatch();
-			assert rowsAffected.length == messages;
-			for(int i = 0; i < rowsAffected.length; i++) {
-				assert rowsAffected[i] <= 1;
+			int[] rowsAffectedArray = ps1.executeBatch();
+			assert rowsAffectedArray.length == messages;
+			for(int i = 0; i < rowsAffectedArray.length; i++) {
+				assert rowsAffectedArray[i] <= 1;
 			}
 			ps1.close();
 			// Cascade on delete
 			sql = "DELETE FROM outstandingBatches WHERE batchId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, b.getBytes());
-			int rowsAffected1 = ps.executeUpdate();
-			assert rowsAffected1 <= 1;
+			int rowsAffected = ps.executeUpdate();
+			assert rowsAffected <= 1;
 			ps.close();
 		} catch(SQLException e) {
 			tryToClose(rs);
@@ -1314,4 +1372,40 @@ abstract class JdbcDatabase implements Database<Connection> {
 			throw new DbException(e);
 		}
 	}
+
+	public void setTransports(Connection txn, ContactId c,
+			Map<String, String> transports) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			// Delete any existing transports
+			String sql = "DELETE FROM transports WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.executeUpdate();
+			ps.close();
+			// Store the new transports
+			if(transports != null) {
+				sql = "INSERT INTO transports"
+					+ " (contactId, detailKey, detailValue)"
+					+ " VALUES (?, ?, ?)";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				for(Entry<String, String> e : transports.entrySet()) {
+					ps.setString(2, e.getKey());
+					ps.setString(3, e.getValue());
+					ps.addBatch();
+				}
+				int[] rowsAffectedArray = ps.executeBatch();
+				assert rowsAffectedArray.length == transports.size();
+				for(int i = 0; i < rowsAffectedArray.length; i++) {
+					assert rowsAffectedArray[i] == 1;
+				}
+				ps.close();
+			}
+		} catch(SQLException e) {
+			tryToClose(ps);
+			tryToClose(txn);
+			throw new DbException(e);
+		}
+	}
 }
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index eb984b59aac4be2a58367a27ca0f9ebe9ef731af..93c24ab6d46496e5557cebd59279e952aa08414f 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -2,15 +2,16 @@ package net.sf.briar.db;
 
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.sf.briar.api.db.ContactId;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchContactException;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
@@ -46,6 +47,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock subscriptionLock =
 		new ReentrantReadWriteLock(true);
+	private final ReentrantReadWriteLock transportLock =
+		new ReentrantReadWriteLock(true);
 
 	@Inject
 	ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
@@ -83,15 +86,16 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public ContactId addContact() throws DbException {
+	public ContactId addContact(Map<String, String> transports)
+	throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
 		contactLock.writeLock().lock();
 		try {
-			messageStatusLock.writeLock().lock();
+			transportLock.writeLock().lock();
 			try {
 				Txn txn = db.startTransaction();
 				try {
-					ContactId c = db.addContact(txn);
+					ContactId c = db.addContact(txn, transports);
 					db.commitTransaction(txn);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added contact " + c);
@@ -101,7 +105,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 					throw e;
 				}
 			} finally {
-				messageStatusLock.writeLock().unlock();
+				transportLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.writeLock().unlock();
@@ -313,19 +317,14 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 	public Set<ContactId> getContacts() throws DbException {
 		contactLock.readLock().lock();
 		try {
-			messageStatusLock.readLock().lock();
+			Txn txn = db.startTransaction();
 			try {
-				Txn txn = db.startTransaction();
-				try {
-					Set<ContactId> contacts = db.getContacts(txn);
-					db.commitTransaction(txn);
-					return contacts;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				messageStatusLock.readLock().unlock();
+				Set<ContactId> contacts = db.getContacts(txn);
+				db.commitTransaction(txn);
+				return contacts;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -366,6 +365,29 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public Map<String, String> getTransports(ContactId c) throws DbException {
+		contactLock.readLock().lock();
+		try {
+			if(!containsContact(c)) throw new NoSuchContactException();
+			transportLock.readLock().lock();
+			try {
+				Txn txn = db.startTransaction();
+				try {
+					Map<String, String> transports = db.getTransports(txn, c);
+					db.commitTransaction(txn);
+					return transports;
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				transportLock.readLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+	}
+
 	public void receiveBundle(ContactId c, Bundle b) throws DbException {
 		if(LOG.isLoggable(Level.FINE))
 			LOG.fine("Received bundle from " + c + ", "
@@ -405,7 +427,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		contactLock.readLock().lock();
 		try {
 			if(!containsContact(c)) throw new NoSuchContactException();
-			messageStatusLock.writeLock().lock();
+			subscriptionLock.writeLock().lock();
 			try {
 				Txn txn = db.startTransaction();
 				try {
@@ -423,7 +445,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 					throw e;
 				}
 			} finally {
-				messageStatusLock.writeLock().unlock();
+				subscriptionLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().lock();
@@ -539,13 +561,23 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		try {
 			messageStatusLock.writeLock().lock();
 			try {
-				Txn txn = db.startTransaction();
+				subscriptionLock.writeLock().lock();
 				try {
-					db.removeContact(txn, c);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+					transportLock.writeLock().lock();
+					try {
+						Txn txn = db.startTransaction();
+						try {
+							db.removeContact(txn, c);
+							db.commitTransaction(txn);
+						} catch(DbException e) {
+							db.abortTransaction(txn);
+							throw e;
+						}
+					} finally {
+						transportLock.writeLock().unlock();
+					}
+				} finally {
+					subscriptionLock.writeLock().unlock();
 				}
 			} finally {
 				messageStatusLock.writeLock().unlock();
@@ -581,6 +613,29 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public void setTransports(ContactId c, Map<String, String> transports)
+	throws DbException {
+		contactLock.readLock().lock();
+		try {
+			if(!containsContact(c)) throw new NoSuchContactException();
+			transportLock.writeLock().lock();
+			try {
+				Txn txn = db.startTransaction();
+				try {
+					db.setTransports(txn, c, transports);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				transportLock.writeLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+	}
+
 	public void subscribe(GroupId g) throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
 		subscriptionLock.writeLock().lock();
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index ba57834fa2e4a65074517c096e93e5af87d8fc56..55900ec225093245cd461fba4cf77532a672fcbf 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -2,14 +2,15 @@ package net.sf.briar.db;
 
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.ContactId;
 import net.sf.briar.api.db.NoSuchContactException;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
@@ -40,6 +41,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 	private final Object messageStatusLock = new Object();
 	private final Object ratingLock = new Object();
 	private final Object subscriptionLock = new Object();
+	private final Object transportLock = new Object();
 
 	@Inject
 	SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
@@ -54,7 +56,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 				synchronized(messageStatusLock) {
 					synchronized(ratingLock) {
 						synchronized(subscriptionLock) {
-							db.close();
+							synchronized(transportLock) {
+								db.close();
+							}
 						}
 					}
 				}
@@ -62,13 +66,14 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public ContactId addContact() throws DbException {
+	public ContactId addContact(Map<String, String> transports)
+	throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
 		synchronized(contactLock) {
-			synchronized(messageStatusLock) {
+			synchronized(transportLock) {
 				Txn txn = db.startTransaction();
 				try {
-					ContactId c = db.addContact(txn);
+					ContactId c = db.addContact(txn, transports);
 					db.commitTransaction(txn);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added contact " + c);
@@ -229,16 +234,14 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 
 	public Set<ContactId> getContacts() throws DbException {
 		synchronized(contactLock) {
-			synchronized(messageStatusLock) {
-				Txn txn = db.startTransaction();
-				try {
-					Set<ContactId> contacts = db.getContacts(txn);
-					db.commitTransaction(txn);
-					return contacts;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
+			Txn txn = db.startTransaction();
+			try {
+				Set<ContactId> contacts = db.getContacts(txn);
+				db.commitTransaction(txn);
+				return contacts;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		}
 	}
@@ -271,6 +274,23 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public Map<String, String> getTransports(ContactId c) throws DbException {
+		synchronized(contactLock) {
+			if(!containsContact(c)) throw new NoSuchContactException();
+			synchronized(transportLock) {
+				Txn txn = db.startTransaction();
+				try {
+					Map<String, String> transports = db.getTransports(txn, c);
+					db.commitTransaction(txn);
+					return transports;
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			}
+		}
+	}
+
 	public void receiveBundle(ContactId c, Bundle b) throws DbException {
 		if(LOG.isLoggable(Level.FINE))
 			LOG.fine("Received bundle from " + c + ", "
@@ -300,7 +320,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		// Update the contact's subscriptions
 		synchronized(contactLock) {
 			if(!containsContact(c)) throw new NoSuchContactException();
-			synchronized(messageStatusLock) {
+			synchronized(subscriptionLock) {
 				Txn txn = db.startTransaction();
 				try {
 					db.clearSubscriptions(txn, c);
@@ -397,13 +417,17 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Removing contact " + c);
 		synchronized(contactLock) {
 			synchronized(messageStatusLock) {
-				Txn txn = db.startTransaction();
-				try {
-					db.removeContact(txn, c);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+				synchronized(subscriptionLock) {
+					synchronized(transportLock) {
+						Txn txn = db.startTransaction();
+						try {
+							db.removeContact(txn, c);
+							db.commitTransaction(txn);
+						} catch(DbException e) {
+							db.abortTransaction(txn);
+							throw e;
+						}
+					}
 				}
 			}
 		}
@@ -429,6 +453,23 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public void setTransports(ContactId c, Map<String, String> transports)
+	throws DbException {
+		synchronized(contactLock) {
+			if(!containsContact(c)) throw new NoSuchContactException();
+			synchronized(transportLock) {
+				Txn txn = db.startTransaction();
+				try {
+					db.setTransports(txn, c, transports);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			}
+		}
+	}
+
 	public void subscribe(GroupId g) throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
 		synchronized(subscriptionLock) {
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index dbab966d3f0013cd023fd57a6f0e433ad2a51561..ba6c808e13c48544cc5b28885ff1e3e2984f650e 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -1,15 +1,16 @@
 package net.sf.briar.db;
 
 import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
-import net.sf.briar.api.db.ContactId;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchContactException;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -65,6 +66,10 @@ public abstract class DatabaseComponentTest extends TestCase {
 
 	@Test
 	public void testSimpleCalls() throws DbException {
+		final Map<String, String> transports =
+			Collections.singletonMap("foo", "bar");
+		final Map<String, String> transports1 =
+			Collections.singletonMap("foo", "bar baz");
 		final Set<GroupId> subs = Collections.singleton(groupId);
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
@@ -82,12 +87,21 @@ public abstract class DatabaseComponentTest extends TestCase {
 			// getRating(authorId)
 			oneOf(database).getRating(txn, authorId);
 			will(returnValue(Rating.UNRATED));
-			// addContact(contactId)
-			oneOf(database).addContact(txn);
+			// addContact(transports)
+			oneOf(database).addContact(txn, transports);
 			will(returnValue(contactId));
 			// getContacts()
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singleton(contactId)));
+			// getTransports(contactId)
+			oneOf(database).containsContact(txn, contactId);
+			will(returnValue(true));
+			oneOf(database).getTransports(txn, contactId);
+			will(returnValue(transports));
+			// setTransports(contactId, transports1)
+			oneOf(database).containsContact(txn, contactId);
+			will(returnValue(true));
+			oneOf(database).setTransports(txn, contactId, transports1);
 			// subscribe(groupId)
 			oneOf(database).addSubscription(txn, groupId);
 			// getSubscriptions()
@@ -106,8 +120,10 @@ public abstract class DatabaseComponentTest extends TestCase {
 
 		db.open(false);
 		assertEquals(Rating.UNRATED, db.getRating(authorId));
-		assertEquals(contactId, db.addContact());
+		assertEquals(contactId, db.addContact(transports));
 		assertEquals(Collections.singleton(contactId), db.getContacts());
+		assertEquals(transports, db.getTransports(contactId));
+		db.setTransports(contactId, transports1);
 		db.subscribe(groupId);
 		assertEquals(Collections.singleton(groupId), db.getSubscriptions());
 		db.unsubscribe(groupId);
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 7827da8524f1964b883ab48e38e542d91ff68a68..7e499e1ada5ac8fbcf0683126b38eba6c0acac45 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -6,16 +6,18 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 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.crypto.Password;
-import net.sf.briar.api.db.ContactId;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
@@ -80,7 +82,8 @@ public class H2DatabaseTest extends TestCase {
 		// Store some records
 		Connection txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
-		assertEquals(contactId, db.addContact(txn));
+		Map<String, String> transports = Collections.singletonMap("foo", "bar");
+		assertEquals(contactId, db.addContact(txn, transports));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsSubscription(txn, groupId));
 		db.addSubscription(txn, groupId);
@@ -96,6 +99,8 @@ public class H2DatabaseTest extends TestCase {
 		// Check that the records are still there
 		txn = db.startTransaction();
 		assertTrue(db.containsContact(txn, contactId));
+		transports = db.getTransports(txn, contactId);
+		assertEquals(Collections.singletonMap("foo", "bar"), transports);
 		assertTrue(db.containsSubscription(txn, groupId));
 		assertTrue(db.containsMessage(txn, messageId));
 		Message m1 = db.getMessage(txn, messageId);
@@ -118,6 +123,7 @@ public class H2DatabaseTest extends TestCase {
 		// Check that the records are gone
 		txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
+		assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
 		assertFalse(db.containsSubscription(txn, groupId));
 		assertFalse(db.containsMessage(txn, messageId));
 		db.commitTransaction(txn);
@@ -130,28 +136,25 @@ public class H2DatabaseTest extends TestCase {
 		ContactId contactId2 = new ContactId(3);
 		ContactId contactId3 = new ContactId(4);
 		MessageFactory messageFactory = new TestMessageFactory();
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Create three contacts
 		Connection txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsContact(txn, contactId1));
-		assertEquals(contactId1, db.addContact(txn));
+		assertEquals(contactId1, db.addContact(txn, null));
 		assertTrue(db.containsContact(txn, contactId1));
 		assertFalse(db.containsContact(txn, contactId2));
-		assertEquals(contactId2, db.addContact(txn));
+		assertEquals(contactId2, db.addContact(txn, null));
 		assertTrue(db.containsContact(txn, contactId2));
-		/*
 		// Delete one of the contacts
 		db.removeContact(txn, contactId1);
 		assertFalse(db.containsContact(txn, contactId1));
-		*/
 		// Add another contact - a new ID should be created
 		assertFalse(db.containsContact(txn, contactId3));
-		assertEquals(contactId3, db.addContact(txn));
+		assertEquals(contactId3, db.addContact(txn, null));
 		assertTrue(db.containsContact(txn, contactId3));
 		db.commitTransaction(txn);
 		db.close();
@@ -180,9 +183,8 @@ public class H2DatabaseTest extends TestCase {
 	public void testUnsubscribingRemovesMessage() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Subscribe to a group and store a message
 		Connection txn = db.startTransaction();
 		db.addSubscription(txn, groupId);
@@ -204,12 +206,11 @@ public class H2DatabaseTest extends TestCase {
 	public void testSendableMessagesMustBeSendable() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -247,12 +248,11 @@ public class H2DatabaseTest extends TestCase {
 	public void testSendableMessagesMustBeNew() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -296,12 +296,11 @@ public class H2DatabaseTest extends TestCase {
 	public void testSendableMessagesMustBeSubscribed() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addSubscription(txn, groupId);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
@@ -338,12 +337,11 @@ public class H2DatabaseTest extends TestCase {
 	public void testSendableMessagesMustFitCapacity() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -374,12 +372,11 @@ public class H2DatabaseTest extends TestCase {
 		BatchId batchId1 = new BatchId(TestUtils.getRandomId());
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact and some batches to ack
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addBatchToAck(txn, contactId, batchId);
 		db.addBatchToAck(txn, contactId, batchId1);
 		db.commitTransaction(txn);
@@ -406,12 +403,11 @@ public class H2DatabaseTest extends TestCase {
 	public void testRemoveAckedBatch() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -450,12 +446,11 @@ public class H2DatabaseTest extends TestCase {
 	public void testRemoveLostBatch() throws DbException {
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -504,12 +499,11 @@ public class H2DatabaseTest extends TestCase {
 		Set<MessageId> empty = Collections.emptySet();
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Add a contact
 		Connection txn = db.startTransaction();
-		assertEquals(contactId, db.addContact(txn));
+		assertEquals(contactId, db.addContact(txn, null));
 		// Add an oustanding batch (associated with BundleId.NONE)
 		db.addOutstandingBatch(txn, contactId, batchId, empty);
 		// Receive a bundle
@@ -535,6 +529,7 @@ public class H2DatabaseTest extends TestCase {
 		lost = db.addReceivedBundle(txn, contactId, bundleId4);
 		assertTrue(lost.isEmpty());
 		db.commitTransaction(txn);
+
 		db.close();
 		context.assertIsSatisfied();
 	}
@@ -547,9 +542,8 @@ public class H2DatabaseTest extends TestCase {
 				authorId1, timestamp, body);
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Subscribe to a group and store two messages
 		Connection txn = db.startTransaction();
 		db.addSubscription(txn, groupId);
@@ -589,9 +583,8 @@ public class H2DatabaseTest extends TestCase {
 				authorId, timestamp, body);
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Subscribe to the groups and store the messages
 		Connection txn = db.startTransaction();
 		db.addSubscription(txn, groupId);
@@ -626,9 +619,8 @@ public class H2DatabaseTest extends TestCase {
 				authorId, timestamp + 1000, body);
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Subscribe to a group and store two messages
 		Connection txn = db.startTransaction();
 		db.addSubscription(txn, groupId);
@@ -665,9 +657,8 @@ public class H2DatabaseTest extends TestCase {
 				authorId, timestamp, largeBody);
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
-
-		// Create a new database
 		Database<Connection> db = open(false, messageFactory);
+
 		// Sanity check: there should be enough space on disk for this test
 		assertTrue(testDir.getFreeSpace() > MAX_SIZE);
 		// The free space should not be more than the allowed maximum size
@@ -692,9 +683,8 @@ public class H2DatabaseTest extends TestCase {
 		final AtomicBoolean transactionFinished = new AtomicBoolean(false);
 		final AtomicBoolean closed = new AtomicBoolean(false);
 		final AtomicBoolean error = new AtomicBoolean(false);
-
-		// Create a new database
 		final Database<Connection> db = open(false, messageFactory);
+
 		// Start a transaction
 		Connection txn = db.startTransaction();
 		// In another thread, close the database
@@ -733,9 +723,8 @@ public class H2DatabaseTest extends TestCase {
 		final AtomicBoolean transactionFinished = new AtomicBoolean(false);
 		final AtomicBoolean closed = new AtomicBoolean(false);
 		final AtomicBoolean error = new AtomicBoolean(false);
-
-		// Create a new database
 		final Database<Connection> db = open(false, messageFactory);
+
 		// Start a transaction
 		Connection txn = db.startTransaction();
 		// In another thread, close the database
@@ -767,6 +756,32 @@ public class H2DatabaseTest extends TestCase {
 		assertFalse(error.get());
 	}
 
+	@Test
+	public void testUpdateTransports() throws DbException {
+		Mockery context = new Mockery();
+		MessageFactory messageFactory = context.mock(MessageFactory.class);
+		Database<Connection> db = open(false, messageFactory);
+
+		// Add a contact with some transport details
+		Connection txn = db.startTransaction();
+		Map<String, String> transports = Collections.singletonMap("foo", "bar");
+		assertEquals(contactId, db.addContact(txn, transports));
+		assertEquals(transports, db.getTransports(txn, contactId));
+		// Replace the transport details
+		transports = new TreeMap<String, String>();
+		transports.put("foo", "bar baz");
+		transports.put("bar", "baz quux");
+		db.setTransports(txn, contactId, transports);
+		assertEquals(transports, db.getTransports(txn, contactId));
+		// Remove the transport details
+		db.setTransports(txn, contactId, null);
+		assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
+		db.commitTransaction(txn);
+
+		db.close();
+		context.assertIsSatisfied();
+	}
+
 	private Database<Connection> open(boolean resume,
 			MessageFactory messageFactory) throws DbException {
 		final char[] passwordArray = passwordString.toCharArray();