diff --git a/briar-android/res/values/roboguice.xml b/briar-android/res/values/roboguice.xml
index fb5c3911216040c4c2b36ba76db6ceb99e0cf1b9..5c62fc7df0dbc16ea794a8ab6bd58dba557b20e4 100644
--- a/briar-android/res/values/roboguice.xml
+++ b/briar-android/res/values/roboguice.xml
@@ -13,7 +13,7 @@
 		<item>org.briarproject.lifecycle.LifecycleModule</item>
 		<item>org.briarproject.messaging.MessagingModule</item>
 		<item>org.briarproject.plugins.AndroidPluginsModule</item>
-		<item>org.briarproject.property.PropertyModule</item>
+		<item>org.briarproject.properties.PropertiesModule</item>
 		<item>org.briarproject.sync.SyncModule</item>
 		<item>org.briarproject.system.AndroidSystemModule</item>
 		<item>org.briarproject.transport.TransportModule</item>
diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
index a355f6b7375f59055fbb989ca989dc9d5e2cbf17..fac266a436570c888694adafd5fd7ee82f91e8fd 100644
--- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -11,7 +11,6 @@ import android.support.v4.app.TaskStackBuilder;
 import org.briarproject.R;
 import org.briarproject.android.contact.ConversationActivity;
 import org.briarproject.android.forum.ForumActivity;
-import org.briarproject.api.Settings;
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.android.AndroidNotificationManager;
 import org.briarproject.api.db.DatabaseComponent;
@@ -24,6 +23,7 @@ import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.messaging.MessagingManager;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.util.StringUtils;
diff --git a/briar-android/src/org/briarproject/android/TestingActivity.java b/briar-android/src/org/briarproject/android/TestingActivity.java
index 797188eba2d8c30f891013d67dda1f0f47745b74..ce770e7d9150925b4c596be6b437a216cc360c92 100644
--- a/briar-android/src/org/briarproject/android/TestingActivity.java
+++ b/briar-android/src/org/briarproject/android/TestingActivity.java
@@ -29,13 +29,13 @@ import org.briarproject.android.util.HorizontalBorder;
 import org.briarproject.android.util.LayoutUtils;
 import org.briarproject.android.util.ListLoadingProgressBar;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginManager;
-import org.briarproject.api.property.TransportPropertyManager;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.util.StringUtils;
 
 import java.io.File;
diff --git a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
index b3c5d946dad971797da44ad1c7dcbabc89a6369d..71f70d09c26eda400b8af18de0fbebd8578c6f8c 100644
--- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
+++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
@@ -25,12 +25,12 @@ import org.briarproject.android.util.FixedVerticalSpace;
 import org.briarproject.android.util.HorizontalBorder;
 import org.briarproject.android.util.LayoutUtils;
 import org.briarproject.android.util.ListLoadingProgressBar;
-import org.briarproject.api.Settings;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.SettingsUpdatedEvent;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.settings.SettingsManager;
 import org.briarproject.util.StringUtils;
 
diff --git a/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java b/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java
index d548b4cfbfa6e776211642ad210a2ad437b5b83a..3ee032a58f0f38b324c2ecebb40a3f038157c1b9 100644
--- a/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java
+++ b/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java
@@ -1,15 +1,11 @@
 package org.briarproject.android.invitation;
 
-import android.bluetooth.BluetoothAdapter;
 import android.content.Intent;
 import android.os.Bundle;
 import android.widget.Toast;
 
 import org.briarproject.R;
 import org.briarproject.android.BriarActivity;
-import org.briarproject.android.util.AndroidUtils;
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportId;
 import org.briarproject.api.android.ReferenceManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.db.DatabaseComponent;
@@ -60,8 +56,6 @@ implements InvitationListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject private volatile IdentityManager identityManager;
-	private volatile boolean bluetoothWasEnabled = false;
-	private volatile boolean leaveBluetoothEnabled = false;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -69,9 +63,6 @@ implements InvitationListener {
 		if (state == null) {
 			// This is a new activity
 			setView(new ChooseIdentityView(this));
-
-			BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-			if (adapter != null) bluetoothWasEnabled = adapter.isEnabled();
 		} else {
 			// Restore the activity's state
 			byte[] b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
@@ -79,8 +70,6 @@ implements InvitationListener {
 			taskHandle = state.getLong("briar.TASK_HANDLE", -1);
 			task = referenceManager.getReference(taskHandle,
 					InvitationTask.class);
-			bluetoothWasEnabled =
-					state.getBoolean("briar.BLUETOOTH_WAS_ENABLED");
 
 			if (task == null) {
 				// No background task - we must be in an initial or final state
@@ -162,26 +151,6 @@ implements InvitationListener {
 	public void onResume() {
 		super.onResume();
 		view.populate();
-		loadBluetoothSetting();
-	}
-
-	private void loadBluetoothSetting() {
-		runOnDbThread(new Runnable() {
-			public void run() {
-				try {
-					long now = System.currentTimeMillis();
-					Settings s = db.getSettings("bt");
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Loading setting took " + duration + " ms");
-					leaveBluetoothEnabled = bluetoothWasEnabled
-							|| s.getBoolean("enable", false);
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
 	}
 
 	@Override
@@ -196,15 +165,12 @@ implements InvitationListener {
 		state.putBoolean("briar.FAILED", connectionFailed);
 		state.putString("briar.CONTACT_NAME", contactName);
 		if (task != null) state.putLong("briar.TASK_HANDLE", taskHandle);
-		state.putBoolean("briar.BLUETOOTH_WAS_ENABLED", bluetoothWasEnabled);
 	}
 
 	@Override
 	public void onDestroy() {
 		super.onDestroy();
-
 		if (task != null) task.removeListener(this);
-		if (isFinishing()) disableBluetooth();
 	}
 
 	@Override
@@ -295,7 +261,7 @@ implements InvitationListener {
 		setView(new InvitationCodeView(this, true));
 
 		task = invitationTaskFactory.createTask(localAuthorId,
-				localInvitationCode, code, leaveBluetoothEnabled);
+				localInvitationCode, code);
 		taskHandle = referenceManager.putReference(task, InvitationTask.class);
 		task.addListener(AddContactActivity.this);
 		// Add a second listener so we can remove the first in onDestroy(),
@@ -329,15 +295,6 @@ implements InvitationListener {
 		}
 	}
 
-	public void disableBluetooth() {
-		if (!leaveBluetoothEnabled) {
-			if (LOG.isLoggable(INFO)) LOG.info("Turning off Bluetooth again");
-
-			BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-			if (adapter != null) AndroidUtils.setBluetooth(adapter, false);
-		}
-	}
-
 	public void connectionSucceeded() {
 		runOnUiThread(new Runnable() {
 			public void run() {
diff --git a/briar-android/src/org/briarproject/android/invitation/ErrorView.java b/briar-android/src/org/briarproject/android/invitation/ErrorView.java
index f87966d66f5eb25d93f063c316058a15249a39f2..fa0f8fc33e6323b477ffb2db3de819f34653ee18 100644
--- a/briar-android/src/org/briarproject/android/invitation/ErrorView.java
+++ b/briar-android/src/org/briarproject/android/invitation/ErrorView.java
@@ -1,23 +1,19 @@
 package org.briarproject.android.invitation;
 
-import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
-import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
-import static android.view.Gravity.CENTER;
-import static org.briarproject.android.invitation.AddContactActivity.REQUEST_BLUETOOTH;
-import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
-
-import org.briarproject.R;
-
 import android.content.Context;
 import android.content.Intent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import org.briarproject.R;
+
+import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
+import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
+import static org.briarproject.android.invitation.AddContactActivity.REQUEST_BLUETOOTH;
+
 class ErrorView extends AddContactView implements OnClickListener {
 
 	private final int error;
@@ -39,8 +35,6 @@ class ErrorView extends AddContactView implements OnClickListener {
 		removeAllViews();
 		Context ctx = getContext();
 
-		container.disableBluetooth();
-
 		LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
 				(Context.LAYOUT_INFLATER_SERVICE);
 		View view = inflater.inflate(R.layout.invitation_error, this);
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index 7bbbe7a15e28a283b32fd2236fdc8647204875c7..646e498d90cdf25511a249e2e8e2d3195356cc39 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -10,13 +10,13 @@ import android.content.Intent;
 import android.content.IntentFilter;
 
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.api.system.Clock;
 import org.briarproject.util.LatchedReference;
 import org.briarproject.util.StringUtils;
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index 149c8d36b4203301dcae36b22ebe6e8dd2daae8f..b6c2f345ade8651a31c2e12bf12743bae5142516 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -13,9 +13,7 @@ import net.freehaven.tor.control.TorControlConnection;
 import net.sourceforge.jsocks.socks.Socks5Proxy;
 import net.sourceforge.jsocks.socks.SocksSocket;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.event.Event;
@@ -24,6 +22,8 @@ import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.system.LocationUtils;
 import org.briarproject.util.StringUtils;
 
diff --git a/briar-api/src/org/briarproject/api/StringMap.java b/briar-api/src/org/briarproject/api/StringMap.java
index 7b897f45e69ae4449a91530f9bc936dbc686d6d6..299f8f3f125e10cb393e0fd550221e1e42d770bc 100644
--- a/briar-api/src/org/briarproject/api/StringMap.java
+++ b/briar-api/src/org/briarproject/api/StringMap.java
@@ -3,7 +3,7 @@ package org.briarproject.api;
 import java.util.Hashtable;
 import java.util.Map;
 
-abstract class StringMap extends Hashtable<String, String> {
+public abstract class StringMap extends Hashtable<String, String> {
 
 	private static final long serialVersionUID = 2497176435908100448L;
 
diff --git a/briar-api/src/org/briarproject/api/TransportId.java b/briar-api/src/org/briarproject/api/TransportId.java
index 9f123ab83141ddbd7594ae9b90cd06f1c93a2d2b..f7644891c08c6128f630679dec3200877e6b32a6 100644
--- a/briar-api/src/org/briarproject/api/TransportId.java
+++ b/briar-api/src/org/briarproject/api/TransportId.java
@@ -1,12 +1,13 @@
 package org.briarproject.api;
 
-import static org.briarproject.api.TransportPropertyConstants.MAX_TRANSPORT_ID_LENGTH;
-
 /**
  * Type-safe wrapper for a string that uniquely identifies a transport plugin.
  */
 public class TransportId {
 
+	/** The maximum length of transport identifier in UTF-8 bytes. */
+	public static int MAX_TRANSPORT_ID_LENGTH = 10;
+
 	private final String id;
 
 	public TransportId(String id) {
@@ -21,8 +22,7 @@ public class TransportId {
 
 	@Override
 	public boolean equals(Object o) {
-		if (o instanceof TransportId) return id.equals(((TransportId) o).id);
-		return false;
+		return o instanceof TransportId && id.equals(((TransportId) o).id);
 	}
 
 	@Override
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 33f89394990eae130346a5788f2bae17340d5222..dd165c481223218ce5cea273a3a3efb372babd89 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -1,12 +1,12 @@
 package org.briarproject.api.db;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
diff --git a/briar-api/src/org/briarproject/api/event/LocalTransportsUpdatedEvent.java b/briar-api/src/org/briarproject/api/event/LocalTransportsUpdatedEvent.java
deleted file mode 100644
index c8cc743e908292168f8849bb0f961c5571cfd0d4..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/event/LocalTransportsUpdatedEvent.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.briarproject.api.event;
-
-/**
- * An event that is broadcast when the local transport properties are
- * updated.
- */
-public class LocalTransportsUpdatedEvent extends Event {
-
-}
diff --git a/briar-api/src/org/briarproject/api/event/RemoteTransportsUpdatedEvent.java b/briar-api/src/org/briarproject/api/event/RemoteTransportsUpdatedEvent.java
deleted file mode 100644
index 52d36961bfbc843f9051deda7f9c1d1945c5185e..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/event/RemoteTransportsUpdatedEvent.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.briarproject.api.event;
-
-import org.briarproject.api.TransportId;
-import org.briarproject.api.contact.ContactId;
-
-/**
- * An event that is broadcast when a contact's remote transport properties
- * are updated.
- */
-public class RemoteTransportsUpdatedEvent extends Event {
-
-	private final ContactId contactId;
-	private final TransportId transportId;
-
-	public RemoteTransportsUpdatedEvent(ContactId contactId,
-			TransportId transportId) {
-		this.contactId = contactId;
-		this.transportId = transportId;
-	}
-
-	public ContactId getContactId() {
-		return contactId;
-	}
-
-	public TransportId getTransportId() {
-		return transportId;
-	}
-}
diff --git a/briar-api/src/org/briarproject/api/invitation/InvitationTaskFactory.java b/briar-api/src/org/briarproject/api/invitation/InvitationTaskFactory.java
index 85b4f882d2668a3c435bbeea1a7c0ca9ea712721..35053ff6157b6e75129c42e979757e1d1511507b 100644
--- a/briar-api/src/org/briarproject/api/invitation/InvitationTaskFactory.java
+++ b/briar-api/src/org/briarproject/api/invitation/InvitationTaskFactory.java
@@ -7,5 +7,5 @@ public interface InvitationTaskFactory {
 
 	/** Creates a task using the given pseudonym and invitation codes. */
 	InvitationTask createTask(AuthorId localAuthorId, int localCode,
-			int remoteCode, boolean reuseConnection);
+			int remoteCode);
 }
diff --git a/briar-api/src/org/briarproject/api/plugins/PluginCallback.java b/briar-api/src/org/briarproject/api/plugins/PluginCallback.java
index 8216cafdffc463be6fc9d97d5bfb699cbf6e91ba..6e3d0e4fa61e57ac04b0e561937feefb634bdd34 100644
--- a/briar-api/src/org/briarproject/api/plugins/PluginCallback.java
+++ b/briar-api/src/org/briarproject/api/plugins/PluginCallback.java
@@ -1,8 +1,8 @@
 package org.briarproject.api.plugins;
 
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.Settings;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 
 import java.util.Map;
 
diff --git a/briar-api/src/org/briarproject/api/TransportProperties.java b/briar-api/src/org/briarproject/api/properties/TransportProperties.java
similarity index 76%
rename from briar-api/src/org/briarproject/api/TransportProperties.java
rename to briar-api/src/org/briarproject/api/properties/TransportProperties.java
index 7d05515e8e92502ac423a38c9e8a38849394e869..512f3917c92df931ed5414955e53d699540176a9 100644
--- a/briar-api/src/org/briarproject/api/TransportProperties.java
+++ b/briar-api/src/org/briarproject/api/properties/TransportProperties.java
@@ -1,4 +1,6 @@
-package org.briarproject.api;
+package org.briarproject.api.properties;
+
+import org.briarproject.api.StringMap;
 
 import java.util.Map;
 
diff --git a/briar-api/src/org/briarproject/api/TransportPropertyConstants.java b/briar-api/src/org/briarproject/api/properties/TransportPropertyConstants.java
similarity index 61%
rename from briar-api/src/org/briarproject/api/TransportPropertyConstants.java
rename to briar-api/src/org/briarproject/api/properties/TransportPropertyConstants.java
index cd2e106e30ea6b5025c12cbe1f2d15c898f9bbba..fd0171094b7c6eca5140cd6937467625fb09f165 100644
--- a/briar-api/src/org/briarproject/api/TransportPropertyConstants.java
+++ b/briar-api/src/org/briarproject/api/properties/TransportPropertyConstants.java
@@ -1,13 +1,7 @@
-package org.briarproject.api;
+package org.briarproject.api.properties;
 
 public interface TransportPropertyConstants {
 
-	/**
-	 * The maximum length of a string that uniquely identifies a transport
-	 * plugin.
-	 */
-	int MAX_TRANSPORT_ID_LENGTH = 10;
-
 	/** The maximum number of properties per transport. */
 	int MAX_PROPERTIES_PER_TRANSPORT = 100;
 
diff --git a/briar-api/src/org/briarproject/api/property/TransportPropertyManager.java b/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java
similarity index 73%
rename from briar-api/src/org/briarproject/api/property/TransportPropertyManager.java
rename to briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java
index ce7bc96e1814c9ee95ae3e1869efe0c5cec51c5a..0e8b76d68f9281b89391913a3324385db23ab431 100644
--- a/briar-api/src/org/briarproject/api/property/TransportPropertyManager.java
+++ b/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java
@@ -1,7 +1,6 @@
-package org.briarproject.api.property;
+package org.briarproject.api.properties;
 
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
 
@@ -26,11 +25,4 @@ public interface TransportPropertyManager {
 	 */
 	void mergeLocalProperties(TransportId t, TransportProperties p)
 			throws DbException;
-
-	/**
-	 * Sets the remote transport properties for the given contact, replacing
-	 * any existing properties.
-	 */
-	void setRemoteProperties(ContactId c,
-			Map<TransportId, TransportProperties> p) throws DbException;
 }
diff --git a/briar-api/src/org/briarproject/api/Settings.java b/briar-api/src/org/briarproject/api/settings/Settings.java
similarity index 59%
rename from briar-api/src/org/briarproject/api/Settings.java
rename to briar-api/src/org/briarproject/api/settings/Settings.java
index 465a0c300cd4cc56bee477d7464e70ed08c00c2a..43fe1e56663002948000023d058e5600bab179c7 100644
--- a/briar-api/src/org/briarproject/api/Settings.java
+++ b/briar-api/src/org/briarproject/api/settings/Settings.java
@@ -1,4 +1,6 @@
-package org.briarproject.api;
+package org.briarproject.api.settings;
+
+import org.briarproject.api.StringMap;
 
 public class Settings extends StringMap {
 
diff --git a/briar-api/src/org/briarproject/api/settings/SettingsManager.java b/briar-api/src/org/briarproject/api/settings/SettingsManager.java
index 33c36e4ef193b88907a2118c6ea01c101ac8a32b..05166529b71984d59b83e488ab51db4a2fc40ee4 100644
--- a/briar-api/src/org/briarproject/api/settings/SettingsManager.java
+++ b/briar-api/src/org/briarproject/api/settings/SettingsManager.java
@@ -1,6 +1,5 @@
 package org.briarproject.api.settings;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.db.DbException;
 
 public interface SettingsManager {
diff --git a/briar-api/src/org/briarproject/api/transport/KeyManager.java b/briar-api/src/org/briarproject/api/transport/KeyManager.java
index 32800d7af410448fff4e9b8679349f091d217a4e..1d6ece09af2dff8690ddd42654ddaccfed5d414a 100644
--- a/briar-api/src/org/briarproject/api/transport/KeyManager.java
+++ b/briar-api/src/org/briarproject/api/transport/KeyManager.java
@@ -4,8 +4,6 @@ import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
 
-import java.util.Collection;
-
 /**
  * Responsible for managing transport keys and recognising the pseudo-random
  * tags of incoming streams.
@@ -18,8 +16,8 @@ public interface KeyManager {
 	 * {@link StreamContext StreamContexts} for the contact can be created
 	 * after this method has returned.
 	 */
-	void addContact(ContactId c, Collection<TransportId> transports,
-			SecretKey master, long timestamp, boolean alice);
+	void addContact(ContactId c, SecretKey master, long timestamp,
+			boolean alice);
 
 	/**
 	 * Returns a {@link StreamContext} for sending a stream to the given
diff --git a/briar-core/src/org/briarproject/crypto/CryptoModule.java b/briar-core/src/org/briarproject/crypto/CryptoModule.java
index 35a4b0136035e8951fa0475e3b64b7ee444c7932..5df7e38f0e9c185fe3e5cf04c4fbc09a539e5c1f 100644
--- a/briar-core/src/org/briarproject/crypto/CryptoModule.java
+++ b/briar-core/src/org/briarproject/crypto/CryptoModule.java
@@ -10,6 +10,7 @@ import org.briarproject.api.crypto.StreamDecrypterFactory;
 import org.briarproject.api.crypto.StreamEncrypterFactory;
 import org.briarproject.api.lifecycle.LifecycleManager;
 
+import java.security.SecureRandom;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -57,4 +58,9 @@ public class CryptoModule extends AbstractModule {
 		lifecycleManager.registerForShutdown(cryptoExecutor);
 		return cryptoExecutor;
 	}
+
+	@Provides
+	SecureRandom getSecureRandom(CryptoComponent crypto) {
+		return crypto.getSecureRandom();
+	}
 }
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index e539d8d4cf834e30efe0290b23ed65af9b757634..9236c6e296142f7f2a306b594e217146a434aeb8 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -1,6 +1,5 @@
 package org.briarproject.db;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
@@ -10,6 +9,7 @@ import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 2b01c7e6f5fb96398b0d987a5cac237007db8f49..ccfa60eccf441d6c939b1bdd1afa383be444b336 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -1,6 +1,5 @@
 package org.briarproject.db;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
@@ -35,6 +34,7 @@ import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.lifecycle.ShutdownManager;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
diff --git a/briar-core/src/org/briarproject/db/DatabaseConstants.java b/briar-core/src/org/briarproject/db/DatabaseConstants.java
index 2c06feca19f68c5806fdc9f95c5214634fb9af94..ddeb138dbc70585921a8a23b2f172bdbea535eba 100644
--- a/briar-core/src/org/briarproject/db/DatabaseConstants.java
+++ b/briar-core/src/org/briarproject/db/DatabaseConstants.java
@@ -1,5 +1,7 @@
 package org.briarproject.db;
 
+import org.briarproject.api.settings.Settings;
+
 interface DatabaseConstants {
 
 	/**
@@ -8,4 +10,34 @@ interface DatabaseConstants {
 	 * limit is reached, additional offers will not be stored.
 	 */
 	int MAX_OFFERED_MESSAGES = 1000;
+
+	/**
+	 * The namespace of the {@link Settings Settings}
+	 * where the database schema version is stored.
+	 */
+	String DB_SETTINGS_NAMESPACE = "db";
+
+	/**
+	 * The {@link Settings Settings} key under which the
+	 * database schema version is stored.
+	 */
+	String SCHEMA_VERSION_KEY = "schemaVersion";
+
+	/**
+	 * The {@link Settings Settings} key under which the
+	 * minimum supported database schema version is stored.
+	 */
+	String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
+
+	/**
+	 * The namespace of the {@link Settings Settings}
+	 * where the unique device ID is stored.
+	 */
+	String DEVICE_SETTINGS_NAMESPACE = "device";
+
+	/**
+	 * The {@link Settings Settings} key under which the
+	 * unique device ID is stored.
+	 */
+	String DEVICE_ID_KEY = "deviceId";
 }
diff --git a/briar-core/src/org/briarproject/db/DatabaseModule.java b/briar-core/src/org/briarproject/db/DatabaseModule.java
index a76155bdb6b0e91deb2090e57204b46991a1578c..9ac531c0775312fd735d2127144f616646e1394d 100644
--- a/briar-core/src/org/briarproject/db/DatabaseModule.java
+++ b/briar-core/src/org/briarproject/db/DatabaseModule.java
@@ -9,8 +9,9 @@ import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.ShutdownManager;
-import org.briarproject.system.SystemClock;
+import org.briarproject.api.system.Clock;
 
+import java.security.SecureRandom;
 import java.sql.Connection;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
@@ -39,13 +40,12 @@ public class DatabaseModule extends AbstractModule {
 	}
 
 	@Override
-	protected void configure() {
-		// Nothing to bind
-	}
+	protected void configure() {}
 
 	@Provides @Singleton
-	Database<Connection> getDatabase(DatabaseConfig config) {
-		return new H2Database(config, new SystemClock());
+	Database<Connection> getDatabase(DatabaseConfig config,
+			SecureRandom random, Clock clock) {
+		return new H2Database(config, random, clock);
 	}
 
 	@Provides @Singleton
diff --git a/briar-core/src/org/briarproject/db/H2Database.java b/briar-core/src/org/briarproject/db/H2Database.java
index dd8c8d8c13bff5f65d5ae08f8d168a630b7057e0..f671530857d1d83a4b66b7bba3c6676ea45d5726 100644
--- a/briar-core/src/org/briarproject/db/H2Database.java
+++ b/briar-core/src/org/briarproject/db/H2Database.java
@@ -6,6 +6,7 @@ import org.briarproject.api.system.Clock;
 import org.briarproject.util.StringUtils;
 
 import java.io.File;
+import java.security.SecureRandom;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
@@ -25,13 +26,13 @@ class H2Database extends JdbcDatabase {
 	private final String url;
 
 	@Inject
-	H2Database(DatabaseConfig config, Clock clock) {
-		super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
+	H2Database(DatabaseConfig config, SecureRandom random, Clock clock) {
+		super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, random, clock);
 		this.config = config;
 		File dir = config.getDatabaseDirectory();
 		String path = new File(dir, "db").getAbsolutePath();
 		url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
-				+ ";DB_CLOSE_ON_EXIT=false";
+				+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
 	}
 
 	public boolean open() throws DbException {
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 05b9d2ce24f500e189db584f33296b094c374d1a..63b6aa18618012da2d65e444e9cdaed0a642e601 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -1,7 +1,7 @@
 package org.briarproject.db;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
+import org.briarproject.api.UniqueId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
@@ -12,6 +12,7 @@ import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
@@ -24,7 +25,9 @@ import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.IncomingKeys;
 import org.briarproject.api.transport.OutgoingKeys;
 import org.briarproject.api.transport.TransportKeys;
+import org.briarproject.util.StringUtils;
 
+import java.security.SecureRandom;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -53,6 +56,11 @@ import static org.briarproject.api.sync.SyncConstants.MAX_SUBSCRIPTIONS;
 import static org.briarproject.api.sync.ValidationManager.Status.INVALID;
 import static org.briarproject.api.sync.ValidationManager.Status.UNKNOWN;
 import static org.briarproject.api.sync.ValidationManager.Status.VALID;
+import static org.briarproject.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
+import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY;
+import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE;
+import static org.briarproject.db.DatabaseConstants.MIN_SCHEMA_VERSION_KEY;
+import static org.briarproject.db.DatabaseConstants.SCHEMA_VERSION_KEY;
 import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
 
 /**
@@ -61,8 +69,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
  */
 abstract class JdbcDatabase implements Database<Connection> {
 
-	private static final int SCHEMA_VERSION = 17;
-	private static final int MIN_SCHEMA_VERSION = 17;
+	private static final int SCHEMA_VERSION = 18;
+	private static final int MIN_SCHEMA_VERSION = 18;
 
 	private static final String CREATE_SETTINGS =
 			"CREATE TABLE settings"
@@ -244,6 +252,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	// Different database libraries use different names for certain types
 	private final String hashType, binaryType, counterType, secretType;
+	private final SecureRandom random;
 	private final Clock clock;
 
 	private final LinkedList<Connection> connections =
@@ -260,11 +269,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private final Condition connectionsChanged = connectionsLock.newCondition();
 
 	JdbcDatabase(String hashType, String binaryType, String counterType,
-			String secretType, Clock clock) {
+			String secretType, SecureRandom random, Clock clock) {
 		this.hashType = hashType;
 		this.binaryType = binaryType;
 		this.counterType = counterType;
 		this.secretType = secretType;
+		this.random = random;
 		this.clock = clock;
 	}
 
@@ -283,6 +293,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			} else {
 				createTables(txn);
 				storeSchemaVersion(txn);
+				storeDeviceId(txn);
 			}
 			commitTransaction(txn);
 		} catch (DbException e) {
@@ -292,19 +303,27 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	private boolean checkSchemaVersion(Connection txn) throws DbException {
-		Settings s = getSettings(txn, "db");
-		int schemaVersion = s.getInt("schemaVersion", -1);
+		Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
+		int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
 		if (schemaVersion == SCHEMA_VERSION) return true;
 		if (schemaVersion < MIN_SCHEMA_VERSION) return false;
-		int minSchemaVersion = s.getInt("minSchemaVersion", -1);
+		int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
 		return SCHEMA_VERSION >= minSchemaVersion;
 	}
 
 	private void storeSchemaVersion(Connection txn) throws DbException {
 		Settings s = new Settings();
-		s.putInt("schemaVersion", SCHEMA_VERSION);
-		s.putInt("minSchemaVersion", MIN_SCHEMA_VERSION);
-		mergeSettings(txn, s, "db");
+		s.putInt(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
+		s.putInt(MIN_SCHEMA_VERSION_KEY, MIN_SCHEMA_VERSION);
+		mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
+	}
+
+	private void storeDeviceId(Connection txn) throws DbException {
+		byte[] deviceId = new byte[UniqueId.LENGTH];
+		random.nextBytes(deviceId);
+		Settings s = new Settings();
+		s.put(DEVICE_ID_KEY, StringUtils.toHexString(deviceId));
+		mergeSettings(txn, s, DEVICE_SETTINGS_NAMESPACE);
 	}
 
 	private void tryToClose(ResultSet rs) {
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index 3d82cdfe44bc5b13b69e5a82c0b7e49b75cc6b64..9dd05f22d69963fb7d209bfa106e63e490b40263 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -16,6 +16,8 @@ import org.briarproject.api.system.Clock;
 
 import javax.inject.Singleton;
 
+import static org.briarproject.forum.ForumManagerImpl.CLIENT_ID;
+
 public class ForumModule extends AbstractModule {
 
 	@Override
@@ -26,16 +28,14 @@ public class ForumModule extends AbstractModule {
 
 	@Provides @Singleton
 	ForumPostValidator getValidator(ValidationManager validationManager,
-			ForumManager forumManager, CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
+			CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
 			ObjectReader<Author> authorReader, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		ForumPostValidator validator = new ForumPostValidator(crypto,
 				bdfReaderFactory, bdfWriterFactory, authorReader,
 				metadataEncoder, clock);
-		validationManager.registerMessageValidator(forumManager.getClientId(),
-				validator);
+		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java
index 590fc54582717cf6e7c4e6e345c22d5f7c032853..7d7807b80f14d433d8ae89cfe394e4b33c445cd5 100644
--- a/briar-core/src/org/briarproject/invitation/AliceConnector.java
+++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java
@@ -1,7 +1,5 @@
 package org.briarproject.invitation;
 
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
@@ -17,7 +15,6 @@ import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -28,7 +25,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
-import java.util.Map;
 import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
@@ -47,17 +43,12 @@ class AliceConnector extends Connector {
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			ContactManager contactManager,
-			TransportPropertyManager transportPropertyManager, Clock clock,
-			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
-			LocalAuthor localAuthor,
-			Map<TransportId, TransportProperties> localProps,
-			PseudoRandom random) {
+			ContactManager contactManager, Clock clock, ConnectorGroup group,
+			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
 		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
-				streamWriterFactory, authorFactory, groupFactory,
-				keyManager, connectionManager, contactManager,
-				transportPropertyManager, clock, reuseConnection, group,
-				plugin, localAuthor, localProps, random);
+				streamWriterFactory, authorFactory, groupFactory, keyManager,
+				connectionManager, contactManager, clock, group, plugin,
+				localAuthor, random);
 	}
 
 	@Override
@@ -152,20 +143,14 @@ class AliceConnector extends Connector {
 		// Derive the invitation nonces
 		byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
 		byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
-		// Exchange pseudonyms, signed nonces, timestamps and transports
+		// Exchange pseudonyms, signed nonces, and timestamps
 		Author remoteAuthor;
 		long remoteTimestamp;
-		Map<TransportId, TransportProperties> remoteProps;
-		boolean remoteReuseConnection;
 		try {
 			sendPseudonym(w, aliceNonce);
 			sendTimestamp(w, localTimestamp);
-			sendTransportProperties(w);
-			sendConfirmation(w, reuseConnection);
 			remoteAuthor = receivePseudonym(r, bobNonce);
 			remoteTimestamp = receiveTimestamp(r);
-			remoteProps = receiveTransportProperties(r);
-			remoteReuseConnection = receiveConfirmation(r);
 			// Close the outgoing stream and expect EOF on the incoming stream
 			w.close();
 			if (!r.eof()) LOG.warning("Unexpected data at end of connection");
@@ -182,18 +167,17 @@ class AliceConnector extends Connector {
 		}
 		// The agreed timestamp is the minimum of the peers' timestamps
 		long timestamp = Math.min(localTimestamp, remoteTimestamp);
-		// Add the contact and store the transports
+		// Add the contact
 		try {
-			addContact(remoteAuthor, remoteProps, master, timestamp, true);
+			addContact(remoteAuthor, master, timestamp, true);
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			tryToClose(conn, true);
 			group.pseudonymExchangeFailed();
 			return;
 		}
-		// Reuse the connection as a transport connection if both peers agree
-		if (reuseConnection && remoteReuseConnection) reuseConnection(conn);
-		else tryToClose(conn, false);
+		// Reuse the connection as a transport connection
+		reuseConnection(conn);
 		// Pseudonym exchange succeeded
 		if (LOG.isLoggable(INFO))
 			LOG.info(pluginName + " pseudonym exchange succeeded");
diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java
index 584a06a3da458d72dacb515dce588b5cd11a1db4..84e0f2987957bfc76c64d7fae4840bcee72f6260 100644
--- a/briar-core/src/org/briarproject/invitation/BobConnector.java
+++ b/briar-core/src/org/briarproject/invitation/BobConnector.java
@@ -1,7 +1,5 @@
 package org.briarproject.invitation;
 
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
@@ -17,7 +15,6 @@ import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -28,7 +25,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
-import java.util.Map;
 import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
@@ -47,17 +43,12 @@ class BobConnector extends Connector {
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			ContactManager contactManager,
-			TransportPropertyManager transportPropertyManager, Clock clock,
-			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
-			LocalAuthor localAuthor,
-			Map<TransportId, TransportProperties> localProps,
-			PseudoRandom random) {
+			ContactManager contactManager, Clock clock, ConnectorGroup group,
+			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
 		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
-				streamWriterFactory, authorFactory, groupFactory,
-				keyManager, connectionManager, contactManager,
-				transportPropertyManager, clock, reuseConnection, group,
-				plugin, localAuthor, localProps, random);
+				streamWriterFactory, authorFactory, groupFactory, keyManager,
+				connectionManager, contactManager, clock, group, plugin,
+				localAuthor, random);
 	}
 
 	@Override
@@ -152,20 +143,14 @@ class BobConnector extends Connector {
 		// Derive the nonces
 		byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
 		byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
-		// Exchange pseudonyms, signed nonces, timestamps and transports
+		// Exchange pseudonyms, signed nonces and timestamps
 		Author remoteAuthor;
 		long remoteTimestamp;
-		Map<TransportId, TransportProperties> remoteProps;
-		boolean remoteReuseConnection;
 		try {
 			remoteAuthor = receivePseudonym(r, aliceNonce);
 			remoteTimestamp = receiveTimestamp(r);
-			remoteProps = receiveTransportProperties(r);
-			remoteReuseConnection = receiveConfirmation(r);
 			sendPseudonym(w, bobNonce);
 			sendTimestamp(w, localTimestamp);
-			sendTransportProperties(w);
-			sendConfirmation(w, reuseConnection);
 			// Close the outgoing stream and expect EOF on the incoming stream
 			w.close();
 			if (!r.eof()) LOG.warning("Unexpected data at end of connection");
@@ -182,18 +167,17 @@ class BobConnector extends Connector {
 		}
 		// The agreed timestamp is the minimum of the peers' timestamps
 		long timestamp = Math.min(localTimestamp, remoteTimestamp);
-		// Add the contact and store the transports
+		// Add the contact
 		try {
-			addContact(remoteAuthor, remoteProps, master, timestamp, false);
+			addContact(remoteAuthor, master, timestamp, false);
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			tryToClose(conn, true);
 			group.pseudonymExchangeFailed();
 			return;
 		}
-		// Reuse the connection as a transport connection if both peers agree
-		if (reuseConnection && remoteReuseConnection) reuseConnection(conn);
-		else tryToClose(conn, false);
+		// Reuse the connection as a transport connection
+		reuseConnection(conn);
 		// Pseudonym exchange succeeded
 		if (LOG.isLoggable(INFO))
 			LOG.info(pluginName + " pseudonym exchange succeeded");
diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java
index 21f84ef2f466503f16d8e624788461fa00cf2521..a89aef88a5b50fa9ec1d7d15a7646f4d50adc9c3 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -2,7 +2,6 @@ package org.briarproject.invitation;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
@@ -23,7 +22,6 @@ import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -33,16 +31,10 @@ import org.briarproject.api.transport.StreamWriterFactory;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
 import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
-import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
-import static org.briarproject.api.TransportPropertyConstants.MAX_TRANSPORT_ID_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
@@ -64,13 +56,10 @@ abstract class Connector extends Thread {
 	protected final KeyManager keyManager;
 	protected final ConnectionManager connectionManager;
 	protected final ContactManager contactManager;
-	protected final TransportPropertyManager transportPropertyManager;
 	protected final Clock clock;
-	protected final boolean reuseConnection;
 	protected final ConnectorGroup group;
 	protected final DuplexPlugin plugin;
 	protected final LocalAuthor localAuthor;
-	protected final Map<TransportId, TransportProperties> localProps;
 	protected final PseudoRandom random;
 	protected final String pluginName;
 
@@ -87,12 +76,8 @@ abstract class Connector extends Thread {
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			ContactManager contactManager,
-			TransportPropertyManager transportPropertyManager, Clock clock,
-			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
-			LocalAuthor localAuthor,
-			Map<TransportId, TransportProperties> localProps,
-			PseudoRandom random) {
+			ContactManager contactManager, Clock clock, ConnectorGroup group,
+			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
 		super("Connector");
 		this.crypto = crypto;
 		this.bdfReaderFactory = bdfReaderFactory;
@@ -104,13 +89,10 @@ abstract class Connector extends Thread {
 		this.keyManager = keyManager;
 		this.connectionManager = connectionManager;
 		this.contactManager = contactManager;
-		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
-		this.reuseConnection = reuseConnection;
 		this.group = group;
 		this.plugin = plugin;
 		this.localAuthor = localAuthor;
-		this.localProps = localProps;
 		this.random = random;
 		pluginName = plugin.getClass().getName();
 		keyPair = crypto.generateAgreementKeyPair();
@@ -233,57 +215,14 @@ abstract class Connector extends Thread {
 		return timestamp;
 	}
 
-	protected void sendTransportProperties(BdfWriter w) throws IOException {
-		w.writeListStart();
-		for (Entry<TransportId, TransportProperties> e :
-				localProps.entrySet()) {
-			w.writeString(e.getKey().getString());
-			w.writeDictionary(e.getValue());
-		}
-		w.writeListEnd();
-		w.flush();
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " sent transport properties");
-	}
-
-	protected Map<TransportId, TransportProperties> receiveTransportProperties(
-			BdfReader r) throws IOException {
-		Map<TransportId, TransportProperties> remoteProps =
-				new HashMap<TransportId, TransportProperties>();
-		r.readListStart();
-		while (!r.hasListEnd()) {
-			String idString = r.readString(MAX_TRANSPORT_ID_LENGTH);
-			if (idString.length() == 0) throw new FormatException();
-			TransportId id = new TransportId(idString);
-			Map<String, String> p = new HashMap<String, String>();
-			r.readDictionaryStart();
-			for (int i = 0; !r.hasDictionaryEnd(); i++) {
-				if (i == MAX_PROPERTIES_PER_TRANSPORT)
-					throw new FormatException();
-				String key = r.readString(MAX_PROPERTY_LENGTH);
-				String value = r.readString(MAX_PROPERTY_LENGTH);
-				p.put(key, value);
-			}
-			r.readDictionaryEnd();
-			remoteProps.put(id, new TransportProperties(p));
-		}
-		r.readListEnd();
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " received transport properties");
-		return remoteProps;
-	}
-
-	protected void addContact(Author remoteAuthor,
-			Map<TransportId, TransportProperties> remoteProps, SecretKey master,
+	protected ContactId addContact(Author remoteAuthor, SecretKey master,
 			long timestamp, boolean alice) throws DbException {
 		// Add the contact to the database
 		contactId = contactManager.addContact(remoteAuthor,
 				localAuthor.getId());
-		// Store the remote transport properties
-		transportPropertyManager.setRemoteProperties(contactId, remoteProps);
-		// Derive transport keys for each transport shared with the contact
-		keyManager.addContact(contactId, remoteProps.keySet(), master,
-				timestamp, alice);
+		// Derive transport keys
+		keyManager.addContact(contactId, master, timestamp, alice);
+		return contactId;
 	}
 
 	protected void tryToClose(DuplexTransportConnection conn,
diff --git a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
index 1a24b782c659fe07bdafba3c772c188a0df381a8..9025c9dff47388a652eeebb7e18c315b6435c7d1 100644
--- a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
+++ b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
@@ -1,7 +1,5 @@
 package org.briarproject.invitation;
 
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
@@ -19,7 +17,6 @@ import org.briarproject.api.invitation.InvitationTask;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.PluginManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
-import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -28,7 +25,6 @@ import org.briarproject.api.transport.StreamWriterFactory;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,12 +53,10 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	private final ConnectionManager connectionManager;
 	private final IdentityManager identityManager;
 	private final ContactManager contactManager;
-	private final TransportPropertyManager transportPropertyManager;
 	private final Clock clock;
 	private final PluginManager pluginManager;
 	private final AuthorId localAuthorId;
 	private final int localInvitationCode, remoteInvitationCode;
-	private final boolean reuseConnection;
 	private final Collection<InvitationListener> listeners;
 	private final AtomicBoolean connected;
 	private final CountDownLatch localConfirmationLatch;
@@ -83,10 +77,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
 			IdentityManager identityManager, ContactManager contactManager,
-			TransportPropertyManager transportPropertyManager, Clock clock,
-			PluginManager pluginManager, AuthorId localAuthorId,
-			int localInvitationCode, int remoteInvitationCode,
-			boolean reuseConnection) {
+			Clock clock, PluginManager pluginManager, AuthorId localAuthorId,
+			int localInvitationCode, int remoteInvitationCode) {
 		super("ConnectorGroup");
 		this.crypto = crypto;
 		this.bdfReaderFactory = bdfReaderFactory;
@@ -99,13 +91,11 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		this.connectionManager = connectionManager;
 		this.identityManager = identityManager;
 		this.contactManager = contactManager;
-		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.pluginManager = pluginManager;
 		this.localAuthorId = localAuthorId;
 		this.localInvitationCode = localInvitationCode;
 		this.remoteInvitationCode = remoteInvitationCode;
-		this.reuseConnection = reuseConnection;
 		listeners = new CopyOnWriteArrayList<InvitationListener>();
 		connected = new AtomicBoolean(false);
 		localConfirmationLatch = new CountDownLatch(1);
@@ -136,11 +126,9 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	@Override
 	public void run() {
 		LocalAuthor localAuthor;
-		Map<TransportId, TransportProperties> localProps;
-		// Load the local pseudonym and transport properties
+		// Load the local pseudonym
 		try {
 			localAuthor = identityManager.getLocalAuthor(localAuthorId);
-			localProps = transportPropertyManager.getLocalProperties();
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			lock.lock();
@@ -157,15 +145,13 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		// Alice is the party with the smaller invitation code
 		if (localInvitationCode < remoteInvitationCode) {
 			for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
-				Connector c = createAliceConnector(plugin, localAuthor,
-						localProps);
+				Connector c = createAliceConnector(plugin, localAuthor);
 				connectors.add(c);
 				c.start();
 			}
 		} else {
 			for (DuplexPlugin plugin: pluginManager.getInvitationPlugins()) {
-				Connector c = createBobConnector(plugin, localAuthor,
-						localProps);
+				Connector c = createBobConnector(plugin, localAuthor);
 				connectors.add(c);
 				c.start();
 			}
@@ -190,27 +176,23 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	}
 
 	private Connector createAliceConnector(DuplexPlugin plugin,
-			LocalAuthor localAuthor,
-			Map<TransportId, TransportProperties> localProps) {
+			LocalAuthor localAuthor) {
 		PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
 				remoteInvitationCode);
 		return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, contactManager,
-				transportPropertyManager, clock, reuseConnection, this, plugin,
-				localAuthor, localProps, random);
+				clock, this, plugin, localAuthor, random);
 	}
 
 	private Connector createBobConnector(DuplexPlugin plugin,
-			LocalAuthor localAuthor,
-			Map<TransportId, TransportProperties> localProps) {
+			LocalAuthor localAuthor) {
 		PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
 				localInvitationCode);
 		return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, contactManager,
-				transportPropertyManager, clock, reuseConnection, this, plugin,
-				localAuthor, localProps, random);
+				clock, this, plugin, localAuthor, random);
 	}
 
 	public void localConfirmationSucceeded() {
diff --git a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
index db131b37657976d1a434d2ee80e7e70ecb5d9804..0b6b4cdf827ae44eed3be07640a2c204e1fa47eb 100644
--- a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
+++ b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
@@ -11,7 +11,6 @@ import org.briarproject.api.invitation.InvitationTask;
 import org.briarproject.api.invitation.InvitationTaskFactory;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.PluginManager;
-import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -33,7 +32,6 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 	private final ConnectionManager connectionManager;
 	private final IdentityManager identityManager;
 	private final ContactManager contactManager;
-	private final TransportPropertyManager transportPropertyManager;
 	private final Clock clock;
 	private final PluginManager pluginManager;
 
@@ -45,7 +43,6 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
 			IdentityManager identityManager, ContactManager contactManager,
-			TransportPropertyManager transportPropertyManager,
 			Clock clock, PluginManager pluginManager) {
 		this.crypto = crypto;
 		this.bdfReaderFactory = bdfReaderFactory;
@@ -58,17 +55,16 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 		this.connectionManager = connectionManager;
 		this.identityManager = identityManager;
 		this.contactManager = contactManager;
-		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.pluginManager = pluginManager;
 	}
 
 	public InvitationTask createTask(AuthorId localAuthorId, int localCode,
-			int remoteCode, boolean reuseConnection) {
+			int remoteCode) {
 		return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, identityManager,
-				contactManager, transportPropertyManager, clock, pluginManager,
-				localAuthorId, localCode, remoteCode, reuseConnection);
+				contactManager, clock, pluginManager, localAuthorId, localCode,
+				remoteCode);
 	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/MessagingModule.java b/briar-core/src/org/briarproject/messaging/MessagingModule.java
index 0e55193e049a175b52d34f755d2b1bbfc5138aca..fe531f715e84e9d51561c85ed7742c918fba0e35 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingModule.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingModule.java
@@ -13,6 +13,8 @@ import org.briarproject.api.system.Clock;
 
 import javax.inject.Singleton;
 
+import static org.briarproject.messaging.MessagingManagerImpl.CLIENT_ID;
+
 public class MessagingModule extends AbstractModule {
 
 	@Override
@@ -22,14 +24,11 @@ public class MessagingModule extends AbstractModule {
 
 	@Provides @Singleton
 	PrivateMessageValidator getValidator(ValidationManager validationManager,
-			MessagingManager messagingManager,
 			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		PrivateMessageValidator validator = new PrivateMessageValidator(
 				bdfReaderFactory, metadataEncoder, clock);
-		validationManager.registerMessageValidator(
-				messagingManager.getClientId(),
-				validator);
+		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
 
diff --git a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
index eeda6bbd17aeb43623e27a55b412c466d1edd012..0b6fe073d310186b53c0fac4208846407e85d707 100644
--- a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
@@ -73,8 +73,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 		ioExecutor.execute(new ManageOutgoingDuplexConnection(c, t, d));
 	}
 
-	private byte[] readTag(TransportId t, TransportConnectionReader r)
-			throws IOException {
+	private byte[] readTag(TransportConnectionReader r) throws IOException {
 		// Read the tag
 		byte[] tag = new byte[TAG_LENGTH];
 		InputStream in = r.getInputStream();
@@ -128,7 +127,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 			// Read and recognise the tag
 			StreamContext ctx;
 			try {
-				byte[] tag = readTag(transportId, reader);
+				byte[] tag = readTag(reader);
 				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -228,7 +227,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 			// Read and recognise the tag
 			StreamContext ctx;
 			try {
-				byte[] tag = readTag(transportId, reader);
+				byte[] tag = readTag(reader);
 				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -353,7 +352,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 			// Read and recognise the tag
 			StreamContext ctx;
 			try {
-				byte[] tag = readTag(transportId, reader);
+				byte[] tag = readTag(reader);
 				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index 0625b2d2461c69635a7b347d577cad0b76cac0d9..66987c4fde649b5e11671d0727fb001ac1589619 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -1,8 +1,6 @@
 package org.briarproject.plugins;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
@@ -26,7 +24,9 @@ import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
-import org.briarproject.api.property.TransportPropertyManager;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.properties.TransportPropertyManager;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.ui.UiCallback;
 
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
index 182c81e25ac186e914cd5b8688662a365e0b3091..01b377f0fc407c92bed18a9e9da60180a8c9e22c 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
@@ -1,5 +1,9 @@
 package org.briarproject.plugins.tcp;
 
+import org.briarproject.api.TransportId;
+import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
+import org.briarproject.api.properties.TransportProperties;
+
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -8,10 +12,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
-import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-
 class LanTcpPlugin extends TcpPlugin {
 
 	static final TransportId ID = new TransportId("lan");
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index 3388de5f13252128d13255806d97f3ba2877a2c6..a7a0b201d72ed1f603ffd74a347d13adbda58729 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -1,11 +1,11 @@
 package org.briarproject.plugins.tcp;
 
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.util.StringUtils;
 
 import java.io.IOException;
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
index d28997c515205886e9b7e7e68ddbe461df18f08d..64c924e4e3d6d89b6f36c4ee3a35935e85d6ab27 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
@@ -1,5 +1,9 @@
 package org.briarproject.plugins.tcp;
 
+import org.briarproject.api.TransportId;
+import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
+import org.briarproject.api.properties.TransportProperties;
+
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -8,10 +12,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
-import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-
 class WanTcpPlugin extends TcpPlugin {
 
 	static final TransportId ID = new TransportId("wan");
diff --git a/briar-core/src/org/briarproject/properties/PropertiesModule.java b/briar-core/src/org/briarproject/properties/PropertiesModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..9edbf7e16a8e76edc7b18f46447f05d65eeb8642
--- /dev/null
+++ b/briar-core/src/org/briarproject/properties/PropertiesModule.java
@@ -0,0 +1,40 @@
+package org.briarproject.properties;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.properties.TransportPropertyManager;
+import org.briarproject.api.sync.ValidationManager;
+import org.briarproject.api.system.Clock;
+
+import javax.inject.Singleton;
+
+import static org.briarproject.properties.TransportPropertyManagerImpl.CLIENT_ID;
+
+public class PropertiesModule extends AbstractModule {
+
+	@Override
+	protected void configure() {}
+
+	@Provides @Singleton
+	TransportPropertyValidator getValidator(ValidationManager validationManager,
+			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
+			Clock clock) {
+		TransportPropertyValidator validator = new TransportPropertyValidator(
+				bdfReaderFactory, metadataEncoder, clock);
+		validationManager.registerMessageValidator(CLIENT_ID, validator);
+		return validator;
+	}
+
+	@Provides @Singleton
+	TransportPropertyManager getTransportPropertyManager(
+			ContactManager contactManager,
+			TransportPropertyManagerImpl transportPropertyManager) {
+		contactManager.registerAddContactHook(transportPropertyManager);
+		contactManager.registerRemoveContactHook(transportPropertyManager);
+		return transportPropertyManager;
+	}
+}
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d4fc5bcc1779e3758150fe73745a5f9cf70a1a3
--- /dev/null
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -0,0 +1,339 @@
+package org.briarproject.properties;
+
+import com.google.inject.Inject;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.TransportId;
+import org.briarproject.api.UniqueId;
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager.AddContactHook;
+import org.briarproject.api.contact.ContactManager.RemoveContactHook;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfWriter;
+import org.briarproject.api.data.BdfWriterFactory;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Metadata;
+import org.briarproject.api.db.NoSuchSubscriptionException;
+import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.properties.TransportPropertyManager;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupFactory;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageFactory;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
+import org.briarproject.util.StringUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+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.locks.ReentrantReadWriteLock;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
+import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
+
+class TransportPropertyManagerImpl implements TransportPropertyManager,
+		AddContactHook, RemoveContactHook {
+
+	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
+			"673ea091673561e28f70122f6a8ea8f4"
+					+ "97c3624b86fa07f785bb15f09fb87b4b"));
+
+	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
+
+	private static final Logger LOG =
+			Logger.getLogger(TransportPropertyManagerImpl.class.getName());
+
+	private final DatabaseComponent db;
+	private final GroupFactory groupFactory;
+	private final MessageFactory messageFactory;
+	private final BdfReaderFactory bdfReaderFactory;
+	private final BdfWriterFactory bdfWriterFactory;
+	private final MetadataEncoder metadataEncoder;
+	private final MetadataParser metadataParser;
+	private final Clock clock;
+	private final Group localGroup;
+
+	/** Ensures isolation between database reads and writes. */
+	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+	@Inject
+	TransportPropertyManagerImpl(DatabaseComponent db, GroupFactory groupFactory,
+			MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
+			BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
+			MetadataParser metadataParser, Clock clock) {
+		this.db = db;
+		this.groupFactory = groupFactory;
+		this.messageFactory = messageFactory;
+		this.bdfReaderFactory = bdfReaderFactory;
+		this.bdfWriterFactory = bdfWriterFactory;
+		this.metadataEncoder = metadataEncoder;
+		this.metadataParser = metadataParser;
+		this.clock = clock;
+		localGroup = groupFactory.createGroup(CLIENT_ID,
+				LOCAL_GROUP_DESCRIPTOR);
+	}
+
+	@Override
+	public void addingContact(ContactId c) {
+		lock.writeLock().lock();
+		try {
+			// Create a group to share with the contact
+			Group g = getContactGroup(db.getContact(c));
+			// Subscribe to the group and share it with the contact
+			db.addGroup(g);
+			db.addContactGroup(c, g);
+			db.setVisibility(g.getId(), Collections.singletonList(c));
+			// Copy the latest local properties into the group
+			Map<TransportId, TransportProperties> local = getLocalProperties();
+			for (Entry<TransportId, TransportProperties> e : local.entrySet())
+				storeMessage(g.getId(), e.getKey(), e.getValue(), 0);
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch (FormatException e) {
+			throw new RuntimeException(e);
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	private Group getContactGroup(Contact c) {
+		AuthorId local = c.getLocalAuthorId();
+		AuthorId remote = c.getAuthor().getId();
+		byte[] descriptor = encodeGroupDescriptor(local, remote);
+		return groupFactory.createGroup(CLIENT_ID, descriptor);
+	}
+
+	private byte[] encodeGroupDescriptor(AuthorId local, AuthorId remote) {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		BdfWriter w = bdfWriterFactory.createWriter(out);
+		try {
+			w.writeListStart();
+			if (UniqueId.IdComparator.INSTANCE.compare(local, remote) < 0) {
+				w.writeRaw(local.getBytes());
+				w.writeRaw(remote.getBytes());
+			} else {
+				w.writeRaw(remote.getBytes());
+				w.writeRaw(local.getBytes());
+			}
+			w.writeListEnd();
+		} catch (IOException e) {
+			// Shouldn't happen with ByteArrayOutputStream
+			throw new RuntimeException(e);
+		}
+		return out.toByteArray();
+	}
+
+	private void storeMessage(GroupId g, TransportId t, TransportProperties p,
+			long version) throws DbException, IOException {
+		byte[] body = encodeProperties(t, p, version);
+		long now = clock.currentTimeMillis();
+		Message m = messageFactory.createMessage(g, now, body);
+		BdfDictionary d = new BdfDictionary();
+		d.put("transportId", t.getString());
+		d.put("version", version);
+		d.put("local", true);
+		db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d));
+	}
+
+	private byte[] encodeProperties(TransportId t, TransportProperties p,
+			long version) {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		BdfWriter w = bdfWriterFactory.createWriter(out);
+		try {
+			// TODO: Device ID
+			w.writeListStart();
+			w.writeString(t.getString());
+			w.writeInteger(version);
+			w.writeDictionary(p);
+			w.writeListEnd();
+		} catch (IOException e) {
+			// Shouldn't happen with ByteArrayOutputStream
+			throw new RuntimeException(e);
+		}
+		return out.toByteArray();
+	}
+
+	@Override
+	public void removingContact(ContactId c) {
+		lock.writeLock().lock();
+		try {
+			db.removeGroup(getContactGroup(db.getContact(c)));
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	@Override
+	public Map<TransportId, TransportProperties> getLocalProperties()
+			throws DbException {
+		lock.readLock().lock();
+		try {
+			// Find the latest local version for each transport
+			Map<TransportId, Latest> latest =
+					findLatest(localGroup.getId(), true);
+			// Retrieve and decode the latest local properties
+			Map<TransportId, TransportProperties> local =
+					new HashMap<TransportId, TransportProperties>();
+			for (Entry<TransportId, Latest> e : latest.entrySet()) {
+				byte[] raw = db.getRawMessage(e.getValue().messageId);
+				local.put(e.getKey(), decodeProperties(raw));
+			}
+			return Collections.unmodifiableMap(local);
+		} catch (NoSuchSubscriptionException e) {
+			// Local group doesn't exist - there are no local properties
+			return Collections.emptyMap();
+		} catch (IOException e) {
+			throw new DbException(e);
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	private Map<TransportId, Latest> findLatest(GroupId g, boolean local)
+			throws DbException, FormatException {
+		// TODO: Use metadata queries
+		Map<TransportId, Latest> latest = new HashMap<TransportId, Latest>();
+		Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
+		for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
+			BdfDictionary mm = metadataParser.parse(e.getValue());
+			if (mm.getBoolean("local") != local) continue;
+			TransportId t = new TransportId(mm.getString("transportId"));
+			long version = mm.getInteger("version");
+			Latest l = latest.get(t);
+			if (l == null || version > l.version)
+				latest.put(t, new Latest(e.getKey(), version));
+		}
+		return latest;
+	}
+
+	private TransportProperties decodeProperties(byte[] raw)
+			throws IOException {
+		TransportProperties p = new TransportProperties();
+		ByteArrayInputStream in = new ByteArrayInputStream(raw,
+				MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
+		BdfReader r = bdfReaderFactory.createReader(in);
+		// TODO: Device ID
+		r.readListStart();
+		r.skipString(); // Transport ID
+		r.skipInteger(); // Version
+		r.readDictionaryStart();
+		while (!r.hasDictionaryEnd()) {
+			String key = r.readString(MAX_PROPERTY_LENGTH);
+			String value = r.readString(MAX_PROPERTY_LENGTH);
+			p.put(key, value);
+		}
+		r.readDictionaryEnd();
+		r.readListEnd();
+		if (!r.eof()) throw new FormatException();
+		return p;
+	}
+
+	@Override
+	public TransportProperties getLocalProperties(TransportId t)
+			throws DbException {
+		lock.readLock().lock();
+		try {
+			// Find the latest local version
+			Latest latest = findLatest(localGroup.getId(), true).get(t);
+			if (latest == null) return null;
+			// Retrieve and decode the latest local properties
+			return decodeProperties(db.getRawMessage(latest.messageId));
+		} catch (NoSuchSubscriptionException e) {
+			// Local group doesn't exist - there are no local properties
+			return null;
+		} catch (IOException e) {
+			throw new DbException(e);
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	@Override
+	public Map<ContactId, TransportProperties> getRemoteProperties(
+			TransportId t) throws DbException {
+		lock.readLock().lock();
+		try {
+			Map<ContactId, TransportProperties> remote =
+					new HashMap<ContactId, TransportProperties>();
+			for (Contact c : db.getContacts())  {
+				Group g = getContactGroup(c);
+				// Find the latest remote version
+				Latest latest = findLatest(g.getId(), false).get(t);
+				if (latest != null) {
+					// Retrieve and decode the latest remote properties
+					byte[] raw = db.getRawMessage(latest.messageId);
+					remote.put(c.getId(), decodeProperties(raw));
+				}
+			}
+			return Collections.unmodifiableMap(remote);
+		} catch (IOException e) {
+			throw new DbException(e);
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	@Override
+	public void mergeLocalProperties(TransportId t, TransportProperties p)
+			throws DbException {
+		lock.writeLock().lock();
+		try {
+			// Create the local group if necessary
+			db.addGroup(localGroup);
+			// Merge the new properties with any existing properties
+			Latest latest = findLatest(localGroup.getId(), true).get(t);
+			if (latest != null) {
+				byte[] raw = db.getRawMessage(latest.messageId);
+				TransportProperties old = decodeProperties(raw);
+				if (old.equals(p)) return; // Unchanged
+				old.putAll(p);
+				p = old;
+			}
+			// Store the merged properties in the local group
+			long version = latest == null ? 0 : latest.version + 1;
+			storeMessage(localGroup.getId(), t, p, version);
+			// Store the merged properties in each contact's group
+			for (Contact c : db.getContacts()) {
+				Group g = getContactGroup(c);
+				latest = findLatest(g.getId(), true).get(t);
+				version = latest == null ? 0 : latest.version + 1;
+				storeMessage(g.getId(), t, p, version);
+			}
+		} catch (IOException e) {
+			throw new DbException(e);
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	private static class Latest {
+
+		private final MessageId messageId;
+		private final long version;
+
+		private Latest(MessageId messageId, long version) {
+			this.messageId = messageId;
+			this.version = version;
+		}
+	}
+}
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a140dd5bacc5734c4e57f2e7f8b5b5b005df57a
--- /dev/null
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
@@ -0,0 +1,83 @@
+package org.briarproject.properties;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfReader;
+import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.db.Metadata;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageValidator;
+import org.briarproject.api.system.Clock;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
+import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
+import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
+import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+
+class TransportPropertyValidator implements MessageValidator {
+
+	private static final Logger LOG =
+			Logger.getLogger(TransportPropertyValidator.class.getName());
+
+	private final BdfReaderFactory bdfReaderFactory;
+	private final MetadataEncoder metadataEncoder;
+	private final Clock clock;
+
+	@Inject
+	TransportPropertyValidator(BdfReaderFactory bdfReaderFactory,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		this.bdfReaderFactory = bdfReaderFactory;
+		this.metadataEncoder = metadataEncoder;
+		this.clock = clock;
+	}
+
+	@Override
+	public Metadata validateMessage(Message m) {
+		// Reject the message if it's too far in the future
+		long now = clock.currentTimeMillis();
+		if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
+			LOG.info("Timestamp is too far in the future");
+			return null;
+		}
+		try {
+			// Parse the message body
+			byte[] raw = m.getRaw();
+			ByteArrayInputStream in = new ByteArrayInputStream(raw,
+					MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
+			BdfReader r = bdfReaderFactory.createReader(in);
+			// TODO: Device ID
+			r.readListStart();
+			String id = r.readString(MAX_TRANSPORT_ID_LENGTH);
+			if (id.length() == 0) throw new FormatException();
+			long version = r.readInteger();
+			if (version < 0) throw new FormatException();
+			r.readDictionaryStart();
+			for (int i = 0; !r.hasDictionaryEnd(); i++) {
+				if (i == MAX_PROPERTIES_PER_TRANSPORT)
+					throw new FormatException();
+				r.readString(MAX_PROPERTY_LENGTH);
+				r.readString(MAX_PROPERTY_LENGTH);
+			}
+			r.readDictionaryEnd();
+			r.readListEnd();
+			if (!r.eof()) throw new FormatException();
+			// Return the metadata
+			BdfDictionary d = new BdfDictionary();
+			d.put("transportId", id);
+			d.put("version", version);
+			d.put("local", false);
+			return metadataEncoder.encode(d);
+		} catch (IOException e) {
+			LOG.info("Invalid transport update");
+			return null;
+		}
+	}
+}
diff --git a/briar-core/src/org/briarproject/property/PropertyModule.java b/briar-core/src/org/briarproject/property/PropertyModule.java
deleted file mode 100644
index 23c3499538aaee749c7050b21fb1ddf41a7fb5a2..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/property/PropertyModule.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.briarproject.property;
-
-import com.google.inject.AbstractModule;
-
-import org.briarproject.api.property.TransportPropertyManager;
-
-public class PropertyModule extends AbstractModule {
-
-	@Override
-	protected void configure() {
-		bind(TransportPropertyManager.class).to(
-				TransportPropertyManagerImpl.class);
-	}
-}
diff --git a/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java
deleted file mode 100644
index 1264591e6c86f3436ffe41f198ad70c18ded6346..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.briarproject.property;
-
-import com.google.inject.Inject;
-
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.property.TransportPropertyManager;
-
-import java.util.Collections;
-import java.util.Map;
-
-// Temporary facade during sync protocol refactoring
-class TransportPropertyManagerImpl implements TransportPropertyManager {
-
-	private final DatabaseComponent db;
-
-	@Inject
-	TransportPropertyManagerImpl(DatabaseComponent db) {
-		this.db = db;
-	}
-
-	@Override
-	public Map<TransportId, TransportProperties> getLocalProperties()
-			throws DbException {
-		// TODO
-		return Collections.emptyMap();
-	}
-
-	@Override
-	public TransportProperties getLocalProperties(TransportId t)
-			throws DbException {
-		// TODO
-		return new TransportProperties();
-	}
-
-	@Override
-	public Map<ContactId, TransportProperties> getRemoteProperties(
-			TransportId t) throws DbException {
-		// TODO
-		return Collections.emptyMap();
-	}
-
-	@Override
-	public void mergeLocalProperties(TransportId t, TransportProperties p)
-			throws DbException {
-		// TODO
-	}
-
-	@Override
-	public void setRemoteProperties(ContactId c,
-			Map<TransportId, TransportProperties> p) throws DbException {
-		// TODO
-	}
-}
diff --git a/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java b/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java
index d003f9e35d6fb99d7f00ee85e72572aba7c02ef5..ee7a3ed681ae900589170f53b03176932482e431 100644
--- a/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java
+++ b/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java
@@ -2,9 +2,9 @@ package org.briarproject.settings;
 
 import com.google.inject.Inject;
 
-import org.briarproject.api.Settings;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.settings.SettingsManager;
 
 class SettingsManagerImpl implements SettingsManager {
diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
index 9804cab617f39db1b14bc6df07053f1dbe62a078..9c99e2819c3b3cfbc5773e0a9041cfd69db21c79 100644
--- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
+++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
@@ -18,7 +18,6 @@ import org.briarproject.api.system.Timer;
 import org.briarproject.api.transport.KeyManager;
 import org.briarproject.api.transport.StreamContext;
 
-import java.util.Collection;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
@@ -71,12 +70,10 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 		return true;
 	}
 
-	public void addContact(ContactId c, Collection<TransportId> transports,
-			SecretKey master, long timestamp, boolean alice) {
-		for (TransportId t : transports) {
-			TransportKeyManager m = managers.get(t);
-			if (m != null) m.addContact(c, master, timestamp, alice);
-		}
+	public void addContact(ContactId c, SecretKey master, long timestamp,
+			boolean alice) {
+		for (TransportKeyManager m : managers.values())
+			m.addContact(c, master, timestamp, alice);
 	}
 
 	public StreamContext getStreamContext(ContactId c, TransportId t) {
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
index 1796d9a6a79da5e8e7f56be3dfb99ec72deaa6ad..4eeccc5677b8a53de2c45c9dd9b6e945398641cc 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
@@ -1,12 +1,12 @@
 package org.briarproject.plugins.bluetooth;
 
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.api.system.Clock;
 import org.briarproject.util.LatchedReference;
 import org.briarproject.util.OsUtils;
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index fdfeb52c30b0d473b4efb1e3649a4846c7970983..0245ad17d37fb8ba7f6ca5b0a8158d18e11ad851 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -1,7 +1,6 @@
 package org.briarproject.plugins.modem;
 
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.TransportConnectionReader;
@@ -9,6 +8,7 @@ import org.briarproject.api.plugins.TransportConnectionWriter;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.util.StringUtils;
 
 import java.io.IOException;
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index 069b7ed8a756f0ea13c3137728168218ee7a83d5..5dd1208986b9edbfecd0f191385263bbf5d7336a 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -3,7 +3,6 @@ package org.briarproject.db;
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestDatabaseConfig;
 import org.briarproject.TestUtils;
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
@@ -13,6 +12,7 @@ import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
@@ -28,6 +28,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.io.File;
+import java.security.SecureRandom;
 import java.sql.Connection;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1121,7 +1122,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	private Database<Connection> open(boolean resume) throws Exception {
 		Database<Connection> db = new H2Database(new TestDatabaseConfig(testDir,
-				MAX_SIZE), new SystemClock());
+				MAX_SIZE), new SecureRandom(), new SystemClock());
 		if (!resume) TestUtils.deleteTestDirectory(testDir);
 		db.open();
 		return db;
diff --git a/briar-tests/src/org/briarproject/forum/ForumManagerImplTest.java b/briar-tests/src/org/briarproject/forum/ForumManagerImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e72451229636910d491aea96840cdf547e56ae4
--- /dev/null
+++ b/briar-tests/src/org/briarproject/forum/ForumManagerImplTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.forum;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class ForumManagerImplTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java b/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..089cce54d979cdc067eba10510e9dcdcd4494bbd
--- /dev/null
+++ b/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.forum;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class ForumPostValidatorTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/messaging/MessagingManagerImplTest.java b/briar-tests/src/org/briarproject/messaging/MessagingManagerImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e9227806157ff5d9425367fb43f174c011f0bd4
--- /dev/null
+++ b/briar-tests/src/org/briarproject/messaging/MessagingManagerImplTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.messaging;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class MessagingManagerImplTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/messaging/PrivateMessageValidatorTest.java b/briar-tests/src/org/briarproject/messaging/PrivateMessageValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..28668520ff6ba9a821226a93e24d50cc4131494c
--- /dev/null
+++ b/briar-tests/src/org/briarproject/messaging/PrivateMessageValidatorTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.messaging;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class PrivateMessageValidatorTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java b/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
index ebfedddbdff8889d8a45d68b3e85c7643a4ee95c..b241ec4e8fcdb0b99aacd003e5e4263e5e8514cb 100644
--- a/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
+++ b/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
@@ -1,11 +1,11 @@
 package org.briarproject.plugins;
 
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 
 import java.io.IOException;
 import java.util.Map;
diff --git a/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java b/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
index fe2323c1b96e9d1ed83a3b9622e4d15a2d9da235..ab60c9272549849861c9ddefe754ff497d3a79fc 100644
--- a/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
+++ b/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
@@ -1,10 +1,10 @@
 package org.briarproject.plugins;
 
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index ddfd3b150b9243095dea5943e9aabf9ed1038c50..4ec79282e5fe0d955b8e7ee32829ce1abdff1e6d 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -13,7 +13,7 @@ import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
-import org.briarproject.api.property.TransportPropertyManager;
+import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.ui.UiCallback;
 import org.briarproject.system.SystemClock;
diff --git a/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothClientTest.java b/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothClientTest.java
index caf7f75142cb25fc1c97565b26cbbc84ce1c8632..35102e1c951a6380e1bd7556c043bd82008c32f9 100644
--- a/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothClientTest.java
+++ b/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothClientTest.java
@@ -1,8 +1,8 @@
 package org.briarproject.plugins.bluetooth;
 
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.plugins.DuplexClientTest;
 import org.briarproject.system.SystemClock;
 
diff --git a/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothServerTest.java b/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothServerTest.java
index ca71e8cf083627cd03134075c8ace09925f24302..105519c423c9379869012d7bb73da711fc355d41 100644
--- a/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothServerTest.java
+++ b/briar-tests/src/org/briarproject/plugins/bluetooth/BluetoothServerTest.java
@@ -1,7 +1,7 @@
 package org.briarproject.plugins.bluetooth;
 
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.plugins.DuplexServerTest;
 import org.briarproject.system.SystemClock;
 
diff --git a/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java b/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
index 10f885130f522fc70c18241f92e1e3e18646c7e3..af9c3298367498cf36f3246b72714b30fce38619 100644
--- a/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
+++ b/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
@@ -1,9 +1,9 @@
 package org.briarproject.plugins.modem;
 
 import org.briarproject.BriarTestCase;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
+import org.briarproject.api.properties.TransportProperties;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Test;
diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java
index 8ad17b59ce7821f80c5bc9b7b026630ca006bacc..c8bfb3118682ce294d4112cbd817eaeacc728eb0 100644
--- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java
+++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java
@@ -1,8 +1,8 @@
 package org.briarproject.plugins.tcp;
 
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.plugins.DuplexClientTest;
 
 import java.util.Collections;
diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java
index cd52f2622eb2e3acff26cac143f964458caec7a7..cef6fb7ad269a125efef6d6f41a8a9bb8d7494fd 100644
--- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java
+++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java
@@ -1,12 +1,12 @@
 package org.briarproject.plugins.tcp;
 
 import org.briarproject.BriarTestCase;
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 import org.junit.Test;
 
 import java.io.IOException;
diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java
index b415cc194fd62a860b87f5551f065d4fd3232879..51d7b2ff3d1efe49a840a46e176f526b4c8dada2 100644
--- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java
+++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java
@@ -1,7 +1,7 @@
 package org.briarproject.plugins.tcp;
 
-import org.briarproject.api.Settings;
-import org.briarproject.api.TransportProperties;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.settings.Settings;
 import org.briarproject.plugins.DuplexServerTest;
 
 import java.util.Collections;
diff --git a/briar-tests/src/org/briarproject/properties/TransportPropertyManagerImplTest.java b/briar-tests/src/org/briarproject/properties/TransportPropertyManagerImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5f69eee718ced237f8fe7dcd0b68e48cd12c7b3
--- /dev/null
+++ b/briar-tests/src/org/briarproject/properties/TransportPropertyManagerImplTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.properties;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class TransportPropertyManagerImplTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java b/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3dbb5d5ee83f55223c13f6860fe6e738cf00caed
--- /dev/null
+++ b/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java
@@ -0,0 +1,14 @@
+package org.briarproject.properties;
+
+import org.briarproject.BriarTestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+public class TransportPropertyValidatorTest extends BriarTestCase {
+
+	@Test
+	public void testUnitTestsExist() {
+		fail(); // FIXME: Write tests
+	}
+}
diff --git a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
index 198ac61a33ac967be0fe4f6730f430b7e7b35263..a4bd5d20e44c644504e3449fd919e72c54ab8c99 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
@@ -54,7 +54,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Collections;
 
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
@@ -132,8 +131,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = contactManager.addContact(bobAuthor, aliceId);
 		// Derive and store the transport keys
-		keyManager.addContact(contactId, Collections.singletonList(transportId),
-				master, timestamp, true);
+		keyManager.addContact(contactId, master, timestamp, true);
 
 		// Send Bob a message
 		GroupId groupId = messagingManager.getConversationId(contactId);
@@ -198,8 +196,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = contactManager.addContact(aliceAuthor, bobId);
 		// Derive and store the transport keys
-		keyManager.addContact(contactId, Collections.singletonList(transportId),
-				master, timestamp, false);
+		keyManager.addContact(contactId, master, timestamp, false);
 
 		// Set up an event listener
 		MessageListener listener = new MessageListener();