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/DeviceId.java b/briar-api/src/org/briarproject/api/DeviceId.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b72d681dea60d9c234e954609537f7516494ad4
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/DeviceId.java
@@ -0,0 +1,16 @@
+package org.briarproject.api;
+
+import java.util.Arrays;
+
+/** Type-safe wrapper for a byte array that uniquely identifies a device. */
+public class DeviceId extends UniqueId {
+
+	public DeviceId(byte[] id) {
+		super(id);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return o instanceof DeviceId && Arrays.equals(id, ((DeviceId) o).id);
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/StringMap.java b/briar-api/src/org/briarproject/api/StringMap.java
index e0480bbfa8279f44bfe47fa308a5463879dc98b6..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;
 
@@ -26,4 +26,18 @@ abstract class StringMap extends Hashtable<String, String> {
 	public void putBoolean(String key, boolean value) {
 		put(key, String.valueOf(value));
 	}
+
+	public int getInt(String key, int defaultValue) {
+		String s = get(key);
+		if (s == null) return defaultValue;
+		try {
+			return Integer.valueOf(s);
+		} catch (NumberFormatException e) {
+			return defaultValue;
+		}
+	}
+
+	public void putInt(String key, int value) {
+		put(key, String.valueOf(value));
+	}
 }
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 c1dfc0072419a3b80b1ead10f4222926de3fb0d4..852affa2db17b4edef84ff581f5bd8fdedefd0a7 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -1,13 +1,13 @@
 package org.briarproject.api.db;
 
-import org.briarproject.api.Settings;
+import org.briarproject.api.DeviceId;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 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;
@@ -19,8 +19,6 @@ import org.briarproject.api.sync.Offer;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.api.transport.TransportKeys;
 
 import java.io.IOException;
@@ -34,7 +32,7 @@ import java.util.Map;
 public interface DatabaseComponent {
 
 	/** Opens the database and returns true if the database already existed. */
-	boolean open() throws DbException, IOException;
+	boolean open() throws DbException;
 
 	/** Waits for any open transactions to finish and closes the database. */
 	void close() throws DbException, IOException;
@@ -125,21 +123,6 @@ public interface DatabaseComponent {
 	SubscriptionUpdate generateSubscriptionUpdate(ContactId c, int maxLatency)
 			throws DbException;
 
-	/**
-	 * Returns a batch of transport acks for the given contact, or null if no
-	 * transport acks are due.
-	 */
-	Collection<TransportAck> generateTransportAcks(ContactId c)
-			throws DbException;
-
-	/**
-	 * Returns a batch of transport updates for the given contact, for
-	 * transmission over a transport with the given latency. Returns null if no
-	 * updates are due.
-	 */
-	Collection<TransportUpdate> generateTransportUpdates(ContactId c,
-			int maxLatency) throws DbException;
-
 	/**
 	 * Returns all groups belonging to the given client to which the user could
 	 * subscribe.
@@ -155,6 +138,9 @@ public interface DatabaseComponent {
 	/** Returns all contacts associated with the given local pseudonym. */
 	Collection<ContactId> getContacts(AuthorId a) throws DbException;
 
+	/** Returns the unique ID for this device. */
+	DeviceId getDeviceId() throws DbException;
+
 	/** Returns the group with the given ID, if the user subscribes to it. */
 	Group getGroup(GroupId g) throws DbException;
 
@@ -173,13 +159,6 @@ public interface DatabaseComponent {
 	/** Returns all local pseudonyms. */
 	Collection<LocalAuthor> getLocalAuthors() throws DbException;
 
-	/** Returns the local transport properties for all transports. */
-	Map<TransportId, TransportProperties> getLocalProperties()
-			throws DbException;
-
-	/** Returns the local transport properties for the given transport. */
-	TransportProperties getLocalProperties(TransportId t) throws DbException;
-
 	/**
 	 * Returns the IDs of any messages that need to be validated by the given
 	 * client.
@@ -210,10 +189,6 @@ public interface DatabaseComponent {
 	MessageStatus getMessageStatus(ContactId c, MessageId m)
 			throws DbException;
 
-	/** Returns all remote transport properties for the given transport. */
-	Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
-			throws DbException;
-
 	/** Returns all settings in the given namespace. */
 	Settings getSettings(String namespace) throws DbException;
 
@@ -243,13 +218,6 @@ public interface DatabaseComponent {
 	 */
 	void mergeGroupMetadata(GroupId g, Metadata meta) throws DbException;
 
-	/**
-	 * Merges the given properties with the existing local properties for the
-	 * given transport.
-	 */
-	void mergeLocalProperties(TransportId t, TransportProperties p)
-			throws DbException;
-
 	/**
 	 * Merges the given metadata with the existing metadata for the given
 	 * message.
@@ -282,13 +250,6 @@ public interface DatabaseComponent {
 	void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u)
 			throws DbException;
 
-	/** Processes a transport ack from the given contact. */
-	void receiveTransportAck(ContactId c, TransportAck a) throws DbException;
-
-	/** Processes a transport update from the given contact. */
-	void receiveTransportUpdate(ContactId c, TransportUpdate u)
-			throws DbException;
-
 	/** Removes a contact (and all associated state) from the database. */
 	void removeContact(ContactId c) throws DbException;
 
@@ -320,13 +281,6 @@ public interface DatabaseComponent {
 	void setMessageValidity(Message m, ClientId c, boolean valid)
 			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;
-
 	/**
 	 * Sets the reordering window for the given contact and transport in the
 	 * given rotation period.
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/sync/PacketReader.java b/briar-api/src/org/briarproject/api/sync/PacketReader.java
index a942adc7cacd88c6c3c4cc66da03dce2e123d745..8986dd81612383ed9471c17e68e8c98a584f99f4 100644
--- a/briar-api/src/org/briarproject/api/sync/PacketReader.java
+++ b/briar-api/src/org/briarproject/api/sync/PacketReader.java
@@ -23,10 +23,4 @@ public interface PacketReader {
 
 	boolean hasSubscriptionUpdate() throws IOException;
 	SubscriptionUpdate readSubscriptionUpdate() throws IOException;
-
-	boolean hasTransportAck() throws IOException;
-	TransportAck readTransportAck() throws IOException;
-
-	boolean hasTransportUpdate() throws IOException;
-	TransportUpdate readTransportUpdate() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/sync/PacketTypes.java b/briar-api/src/org/briarproject/api/sync/PacketTypes.java
index 2bbeeae06b008bc38e7df96b8731826c05ae8897..2bdc1b66af9c26e7e9b987ef80ec22d8d7f4ca10 100644
--- a/briar-api/src/org/briarproject/api/sync/PacketTypes.java
+++ b/briar-api/src/org/briarproject/api/sync/PacketTypes.java
@@ -9,6 +9,4 @@ public interface PacketTypes {
 	byte REQUEST = 3;
 	byte SUBSCRIPTION_ACK = 6;
 	byte SUBSCRIPTION_UPDATE = 7;
-	byte TRANSPORT_ACK = 8;
-	byte TRANSPORT_UPDATE = 9;
 }
diff --git a/briar-api/src/org/briarproject/api/sync/PacketWriter.java b/briar-api/src/org/briarproject/api/sync/PacketWriter.java
index 896b661f45ec484c2edeada512e62c5ccb70bb94..7777a42943433e89e029372324f0acb34d92986f 100644
--- a/briar-api/src/org/briarproject/api/sync/PacketWriter.java
+++ b/briar-api/src/org/briarproject/api/sync/PacketWriter.java
@@ -22,9 +22,5 @@ public interface PacketWriter {
 
 	void writeSubscriptionUpdate(SubscriptionUpdate u) throws IOException;
 
-	void writeTransportAck(TransportAck a) throws IOException;
-
-	void writeTransportUpdate(TransportUpdate u) throws IOException;
-
 	void flush() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/sync/TransportAck.java b/briar-api/src/org/briarproject/api/sync/TransportAck.java
deleted file mode 100644
index 160d24dfff062fe6fe838153c8596f35c7a21dee..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/sync/TransportAck.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.briarproject.api.sync;
-
-import org.briarproject.api.TransportId;
-
-/** A packet acknowledging a {@link TransportUpdate}. */
-public class TransportAck {
-
-	private final TransportId id;
-	private final long version;
-
-	public TransportAck(TransportId id, long version) {
-		this.id = id;
-		this.version = version;
-	}
-
-	/** Returns the identifier of the updated transport. */
-	public TransportId getId() {
-		return id;
-	}
-
-	/** Returns the version number of the acknowledged update. */
-	public long getVersion() {
-		return version;
-	}
-}
diff --git a/briar-api/src/org/briarproject/api/sync/TransportUpdate.java b/briar-api/src/org/briarproject/api/sync/TransportUpdate.java
deleted file mode 100644
index ea403ba342955f285b4d3baa49f12831de16a0d7..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/sync/TransportUpdate.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.briarproject.api.sync;
-
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
-
-/**
- * A packet updating the recipient's view of the sender's transport properties.
- */
-public class TransportUpdate {
-
-	private final TransportId id;
-	private final TransportProperties properties;
-	private final long version;
-
-	public TransportUpdate(TransportId id, TransportProperties properties,
-			long version) {
-		this.id = id;
-		this.properties = properties;
-		this.version = version;
-	}
-
-	/** Returns the identifier of the updated transport. */
-	public TransportId getId() {
-		return id;
-	}
-
-	/** Returns the transport's updated properties. */
-	public TransportProperties getProperties() {
-		return properties;
-	}
-
-	/** Returns the update's version number. */
-	public long getVersion() {
-		return version;
-	}
-}
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 01a7b54bfcfcd1399e6798a4e4afdb15d865b5cb..e912671b536bdef0b3a7f2d73a405e9aa8ca06db 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -1,8 +1,7 @@
 package org.briarproject.db;
 
-import org.briarproject.api.Settings;
+import org.briarproject.api.DeviceId;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
@@ -11,6 +10,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;
@@ -19,8 +19,6 @@ import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.MessageStatus;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.api.transport.TransportKeys;
 
 import java.io.IOException;
@@ -43,7 +41,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: write.
 	 */
-	boolean open() throws DbException, IOException;
+	boolean open() throws DbException;
 
 	/**
 	 * Prevents new transactions from starting, waits for all current
@@ -259,6 +257,13 @@ interface Database<T> {
 	 */
 	Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
 
+	/**
+	 * Returns the unique ID for this device.
+	 * <p>
+	 * Locking: read.
+	 */
+	DeviceId getDeviceId(T txn) throws DbException;
+
 	/**
 	 * Returns the amount of free storage space available to the database, in
 	 * bytes. This is based on the minimum of the space available on the device
@@ -302,22 +307,6 @@ interface Database<T> {
 	 */
 	Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
 
-	/**
-	 * Returns the local transport properties for all transports.
-	 * <p>
-	 * Locking: read.
-	 */
-	Map<TransportId, TransportProperties> getLocalProperties(T txn)
-			throws DbException;
-
-	/**
-	 * Returns the local transport properties for the given transport.
-	 * <p>
-	 * Locking: read.
-	 */
-	TransportProperties getLocalProperties(T txn, TransportId t)
-			throws DbException;
-
 	/**
 	 * Returns the metadata for all messages in the given group.
 	 * <p>
@@ -403,14 +392,6 @@ interface Database<T> {
 	 */
 	byte[] getRawMessage(T txn, MessageId m) throws DbException;
 
-	/**
-	 * Returns all remote properties for the given transport.
-	 * <p>
-	 * Locking: read.
-	 */
-	Map<ContactId, TransportProperties> getRemoteProperties(T txn,
-			TransportId t) throws DbException;
-
 	/**
 	 * Returns the IDs of some messages that are eligible to be sent to the
 	 * given contact and have been requested by the contact, up to the given
@@ -452,15 +433,6 @@ interface Database<T> {
 	SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
 			int maxLatency) throws DbException;
 
-	/**
-	 * Returns a collection of transport acks for the given contact, or null if
-	 * no acks are due.
-	 * <p>
-	 * Locking: write.
-	 */
-	Collection<TransportAck> getTransportAcks(T txn, ContactId c)
-			throws DbException;
-
 	/**
 	 * Returns all transport keys for the given transport.
 	 * <p>
@@ -476,16 +448,6 @@ interface Database<T> {
 	 */
 	Map<TransportId, Integer> getTransportLatencies(T txn) throws DbException;
 
-	/**
-	 * Returns a collection of transport updates for the given contact and
-	 * updates their expiry times using the given latency, or returns null if
-	 * no updates are due.
-	 * <p>
-	 * Locking: write.
-	 */
-	Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c,
-			int maxLatency) throws DbException;
-
 	/**
 	 * Returns the IDs of all contacts to which the given group is visible.
 	 * <p>
@@ -529,15 +491,6 @@ interface Database<T> {
 	void mergeGroupMetadata(T txn, GroupId g, Metadata meta)
 			throws DbException;
 
-	/**
-	 * Merges the given properties with the existing local properties for the
-	 * given transport.
-	 * <p>
-	 * Locking: write.
-	 */
-	void mergeLocalProperties(T txn, TransportId t, TransportProperties p)
-			throws DbException;
-
 	/*
 	 * Merges the given metadata with the existing metadata for the given
 	 * message.
@@ -689,26 +642,6 @@ interface Database<T> {
 	boolean setGroups(T txn, ContactId c, Collection<Group> groups,
 			long version) throws DbException;
 
-	/**
-	 * Sets the remote transport properties for the given contact, replacing
-	 * any existing properties.
-	 * <p>
-	 * Locking: write.
-	 */
-	void setRemoteProperties(T txn, ContactId c,
-			Map<TransportId, TransportProperties> p) throws DbException;
-
-	/**
-	 * Updates the remote transport properties for the given contact and the
-	 * given transport, replacing any existing properties, and returns true,
-	 * unless an update with an equal or higher version number has already been
-	 * received from the contact.
-	 * <p>
-	 * Locking: write.
-	 */
-	boolean setRemoteProperties(T txn, ContactId c, TransportId t,
-			TransportProperties p, long version) throws DbException;
-
 	/**
 	 * Records a subscription ack from the given contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
@@ -718,15 +651,6 @@ interface Database<T> {
 	void setSubscriptionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
 
-	/**
-	 * Records a transport ack from the give contact for the given version,
-	 * unless the contact has already acked an equal or higher version.
-	 * <p>
-	 * Locking: write.
-	 */
-	void setTransportUpdateAcked(T txn, ContactId c, TransportId t,
-			long version) throws DbException;
-
 	/**
 	 * Makes a group visible or invisible to future contacts by default.
 	 * <p>
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 85ff594e6c38893fa66e2702dc09411e5d91f0f4..39c0f796f0faa10cc226c7b317353009a95dc7b0 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -1,8 +1,7 @@
 package org.briarproject.db;
 
-import org.briarproject.api.Settings;
+import org.briarproject.api.DeviceId;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.ContactExistsException;
@@ -19,7 +18,6 @@ import org.briarproject.api.db.NoSuchTransportException;
 import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
-import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageRequestedEvent;
 import org.briarproject.api.event.MessageToAckEvent;
@@ -28,7 +26,6 @@ import org.briarproject.api.event.MessageValidatedEvent;
 import org.briarproject.api.event.MessagesAckedEvent;
 import org.briarproject.api.event.MessagesSentEvent;
 import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
-import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
 import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.api.event.SubscriptionAddedEvent;
 import org.briarproject.api.event.SubscriptionRemovedEvent;
@@ -38,6 +35,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;
@@ -49,8 +47,6 @@ import org.briarproject.api.sync.Offer;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.api.transport.TransportKeys;
 
 import java.io.IOException;
@@ -99,7 +95,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		this.shutdown = shutdown;
 	}
 
-	public boolean open() throws DbException, IOException {
+	public boolean open() throws DbException {
 		Runnable shutdownHook = new Runnable() {
 			public void run() {
 				lock.writeLock().lock();
@@ -475,47 +471,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
-	public Collection<TransportAck> generateTransportAcks(ContactId c)
-			throws DbException {
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsContact(txn, c))
-					throw new NoSuchContactException();
-				Collection<TransportAck> acks = db.getTransportAcks(txn, c);
-				db.commitTransaction(txn);
-				return acks;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-	}
-
-	public Collection<TransportUpdate> generateTransportUpdates(ContactId c,
-			int maxLatency) throws DbException {
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsContact(txn, c))
-					throw new NoSuchContactException();
-				Collection<TransportUpdate> updates =
-						db.getTransportUpdates(txn, c, maxLatency);
-				db.commitTransaction(txn);
-				return updates;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-	}
-
 	public Collection<Group> getAvailableGroups(ClientId c) throws DbException {
 		lock.readLock().lock();
 		try {
@@ -588,6 +543,23 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
+	public DeviceId getDeviceId() throws DbException {
+		lock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				DeviceId id = db.getDeviceId(txn);
+				db.commitTransaction(txn);
+				return id;
+			} catch (DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
 	public Group getGroup(GroupId g) throws DbException {
 		lock.readLock().lock();
 		try {
@@ -679,45 +651,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
-	public Map<TransportId, TransportProperties> getLocalProperties()
-			throws DbException {
-		lock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				Map<TransportId, TransportProperties> properties =
-						db.getLocalProperties(txn);
-				db.commitTransaction(txn);
-				return properties;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.readLock().unlock();
-		}
-	}
-
-	public TransportProperties getLocalProperties(TransportId t)
-			throws DbException {
-		lock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsTransport(txn, t))
-					throw new NoSuchTransportException();
-				TransportProperties properties = db.getLocalProperties(txn, t);
-				db.commitTransaction(txn);
-				return properties;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.readLock().unlock();
-		}
-	}
-
 	public Collection<MessageId> getMessagesToValidate(ClientId c)
 			throws DbException {
 		lock.readLock().lock();
@@ -840,25 +773,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
-	public Map<ContactId, TransportProperties> getRemoteProperties(
-			TransportId t) throws DbException {
-		lock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				Map<ContactId, TransportProperties> properties =
-						db.getRemoteProperties(txn, t);
-				db.commitTransaction(txn);
-				return properties;
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.readLock().unlock();
-		}
-	}
-
 	public Settings getSettings(String namespace) throws DbException {
 		lock.readLock().lock();
 		try {
@@ -992,30 +906,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
-	public void mergeLocalProperties(TransportId t, TransportProperties p)
-			throws DbException {
-		boolean changed = false;
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsTransport(txn, t))
-					throw new NoSuchTransportException();
-				if (!p.equals(db.getLocalProperties(txn, t))) {
-					db.mergeLocalProperties(txn, t, p);
-					changed = true;
-				}
-				db.commitTransaction(txn);
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-		if (changed) eventBus.broadcast(new LocalTransportsUpdatedEvent());
-	}
-
 	public void mergeMessageMetadata(MessageId m, Metadata meta)
 			throws DbException {
 		lock.writeLock().lock();
@@ -1208,52 +1098,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		if (updated) eventBus.broadcast(new RemoteSubscriptionsUpdatedEvent(c));
 	}
 
-	public void receiveTransportAck(ContactId c, TransportAck a)
-			throws DbException {
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsContact(txn, c))
-					throw new NoSuchContactException();
-				if (!db.containsTransport(txn, a.getId()))
-					throw new NoSuchTransportException();
-				db.setTransportUpdateAcked(txn, c, a.getId(), a.getVersion());
-				db.commitTransaction(txn);
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-	}
-
-	public void receiveTransportUpdate(ContactId c, TransportUpdate u)
-			throws DbException {
-		boolean updated;
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsContact(txn, c))
-					throw new NoSuchContactException();
-				TransportId t = u.getId();
-				TransportProperties p = u.getProperties();
-				long version = u.getVersion();
-				updated = db.setRemoteProperties(txn, c, t, p, version);
-				db.commitTransaction(txn);
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-		if (updated)
-			eventBus.broadcast(new RemoteTransportsUpdatedEvent(c, u.getId()));
-	}
-
 	public void removeContact(ContactId c) throws DbException {
 		lock.writeLock().lock();
 		try {
@@ -1390,25 +1234,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		eventBus.broadcast(new MessageValidatedEvent(m, c, false, valid));
 	}
 
-	public void setRemoteProperties(ContactId c,
-			Map<TransportId, TransportProperties> p) throws DbException {
-		lock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				if (!db.containsContact(txn, c))
-					throw new NoSuchContactException();
-				db.setRemoteProperties(txn, c, p);
-				db.commitTransaction(txn);
-			} catch (DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			lock.writeLock().unlock();
-		}
-	}
-
 	public void setReorderingWindow(ContactId c, TransportId t,
 			long rotationPeriod, long base, byte[] bitmap) throws DbException {
 		lock.writeLock().lock();
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 de8997334865328aee105f3dc6cf06c5e5eefb2d..f671530857d1d83a4b66b7bba3c6676ea45d5726 100644
--- a/briar-core/src/org/briarproject/db/H2Database.java
+++ b/briar-core/src/org/briarproject/db/H2Database.java
@@ -6,11 +6,10 @@ import org.briarproject.api.system.Clock;
 import org.briarproject.util.StringUtils;
 
 import java.io.File;
-import java.io.IOException;
+import java.security.SecureRandom;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.Properties;
 
 import javax.inject.Inject;
@@ -18,7 +17,7 @@ import javax.inject.Inject;
 /** Contains all the H2-specific code for the database. */
 class H2Database extends JdbcDatabase {
 
-	private static final String HASH_TYPE = "BINARY(48)";
+	private static final String HASH_TYPE = "BINARY(32)";
 	private static final String BINARY_TYPE = "BINARY";
 	private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
 	private static final String SECRET_TYPE = "BINARY(32)";
@@ -27,16 +26,16 @@ 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;
-		String path = new File(config.getDatabaseDirectory(), "db").getAbsolutePath();
-		// FIXME: Remove WRITE_DELAY=0 after implementing BTPv2?
+		File dir = config.getDatabaseDirectory();
+		String path = new File(dir, "db").getAbsolutePath();
 		url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
 				+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
 	}
 
-	public boolean open() throws DbException, IOException {
+	public boolean open() throws DbException {
 		boolean reopen = config.databaseExists();
 		if (!reopen) config.getDatabaseDirectory().mkdirs();
 		super.open("org.h2.Driver", reopen);
@@ -83,10 +82,4 @@ class H2Database extends JdbcDatabase {
 		props.put("password", StringUtils.toHexString(key) + " password");
 		return DriverManager.getConnection(url, props);
 	}
-
-	@Override
-	protected void flushBuffersToDisk(Statement s) throws SQLException {
-		// FIXME: Remove this after implementing BTPv2?
-		s.execute("CHECKPOINT SYNC");
-	}
 }
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 69ed999735fafb8987e616884538afca71765a2c..411d7246c7dbafea7d075579281e4bc002f32db0 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -1,8 +1,8 @@
 package org.briarproject.db;
 
-import org.briarproject.api.Settings;
+import org.briarproject.api.DeviceId;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
+import org.briarproject.api.UniqueId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
@@ -13,6 +13,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;
@@ -21,14 +22,13 @@ import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.MessageStatus;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 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.io.IOException;
+import java.security.SecureRandom;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -57,6 +57,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;
 
 /**
@@ -65,8 +70,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"
@@ -210,64 +215,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " maxLatency INT NOT NULL,"
 					+ " PRIMARY KEY (transportId))";
 
-	private static final String CREATE_TRANSPORT_CONFIGS =
-			"CREATE TABLE transportConfigs"
-					+ " (transportId VARCHAR NOT NULL,"
-					+ " key VARCHAR NOT NULL,"
-					+ " value VARCHAR NOT NULL,"
-					+ " PRIMARY KEY (transportId, key),"
-					+ " FOREIGN KEY (transportId)"
-					+ " REFERENCES transports (transportId)"
-					+ " ON DELETE CASCADE)";
-
-	private static final String CREATE_TRANSPORT_PROPS =
-			"CREATE TABLE transportProperties"
-					+ " (transportId VARCHAR NOT NULL,"
-					+ " key VARCHAR NOT NULL,"
-					+ " value VARCHAR NOT NULL,"
-					+ " PRIMARY KEY (transportId, key),"
-					+ " FOREIGN KEY (transportId)"
-					+ " REFERENCES transports (transportId)"
-					+ " ON DELETE CASCADE)";
-
-	private static final String CREATE_TRANSPORT_VERSIONS =
-			"CREATE TABLE transportVersions"
-					+ " (contactId INT NOT NULL,"
-					+ " transportId VARCHAR NOT NULL,"
-					+ " localVersion BIGINT NOT NULL,"
-					+ " localAcked BIGINT NOT NULL,"
-					+ " expiry BIGINT NOT NULL,"
-					+ " txCount INT NOT NULL,"
-					+ " PRIMARY KEY (contactId, transportId),"
-					+ " FOREIGN KEY (contactId)"
-					+ " REFERENCES contacts (contactId)"
-					+ " ON DELETE CASCADE,"
-					+ " FOREIGN KEY (transportId)"
-					+ " REFERENCES transports (transportId)"
-					+ " ON DELETE CASCADE)";
-
-	private static final String CREATE_CONTACT_TRANSPORT_PROPS =
-			"CREATE TABLE contactTransportProperties"
-					+ " (contactId INT NOT NULL,"
-					+ " transportId VARCHAR NOT NULL," // Not a foreign key
-					+ " key VARCHAR NOT NULL,"
-					+ " value VARCHAR NOT NULL,"
-					+ " PRIMARY KEY (contactId, transportId, key),"
-					+ " FOREIGN KEY (contactId)"
-					+ " REFERENCES contacts (contactId)"
-					+ " ON DELETE CASCADE)";
-
-	private static final String CREATE_CONTACT_TRANSPORT_VERSIONS =
-			"CREATE TABLE contactTransportVersions"
-					+ " (contactId INT NOT NULL,"
-					+ " transportId VARCHAR NOT NULL," // Not a foreign key
-					+ " remoteVersion BIGINT NOT NULL,"
-					+ " remoteAcked BOOLEAN NOT NULL,"
-					+ " PRIMARY KEY (contactId, transportId),"
-					+ " FOREIGN KEY (contactId)"
-					+ " REFERENCES contacts (contactId)"
-					+ " ON DELETE CASCADE)";
-
 	private static final String CREATE_INCOMING_KEYS =
 			"CREATE TABLE incomingKeys"
 					+ " (contactId INT NOT NULL,"
@@ -306,6 +253,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 =
@@ -318,22 +266,20 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	protected abstract Connection createConnection() throws SQLException;
 
-	protected abstract void flushBuffersToDisk(Statement s) throws SQLException;
-
 	private final Lock connectionsLock = new ReentrantLock();
 	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;
 	}
 
-	protected void open(String driverClass, boolean reopen) throws DbException,
-			IOException {
+	protected void open(String driverClass, boolean reopen) throws DbException {
 		// Load the JDBC driver
 		try {
 			Class.forName(driverClass);
@@ -347,10 +293,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 				if (!checkSchemaVersion(txn)) throw new DbException();
 			} else {
 				createTables(txn);
-				Settings s = new Settings();
-				s.put("schemaVersion", String.valueOf(SCHEMA_VERSION));
-				s.put("minSchemaVersion", String.valueOf(MIN_SCHEMA_VERSION));
-				mergeSettings(txn, s, "db");
+				storeSchemaVersion(txn);
+				storeDeviceId(txn);
 			}
 			commitTransaction(txn);
 		} catch (DbException e) {
@@ -360,16 +304,27 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	private boolean checkSchemaVersion(Connection txn) throws DbException {
-		try {
-			Settings s = getSettings(txn, "db");
-			int schemaVersion = Integer.valueOf(s.get("schemaVersion"));
-			if (schemaVersion == SCHEMA_VERSION) return true;
-			if (schemaVersion < MIN_SCHEMA_VERSION) return false;
-			int minSchemaVersion = Integer.valueOf(s.get("minSchemaVersion"));
-			return SCHEMA_VERSION >= minSchemaVersion;
-		} catch (NumberFormatException e) {
-			throw new DbException(e);
-		}
+		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(MIN_SCHEMA_VERSION_KEY, -1);
+		return SCHEMA_VERSION >= minSchemaVersion;
+	}
+
+	private void storeSchemaVersion(Connection txn) throws DbException {
+		Settings s = new Settings();
+		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) {
@@ -405,11 +360,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			s.executeUpdate(insertTypeNames(CREATE_OFFERS));
 			s.executeUpdate(insertTypeNames(CREATE_STATUSES));
 			s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
-			s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_CONFIGS));
-			s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_PROPS));
-			s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_VERSIONS));
-			s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_PROPS));
-			s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_VERSIONS));
 			s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
 			s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
 			s.close();
@@ -487,14 +437,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void commitTransaction(Connection txn) throws DbException {
-		Statement s = null;
 		try {
 			txn.commit();
-			s = txn.createStatement();
-			flushBuffersToDisk(s);
-			s.close();
 		} catch (SQLException e) {
-			tryToClose(s);
 			throw new DbException(e);
 		}
 		connectionsLock.lock();
@@ -540,6 +485,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 		if (interrupted) Thread.currentThread().interrupt();
 	}
 
+	public DeviceId getDeviceId(Connection txn) throws DbException {
+		Settings s = getSettings(txn, DEVICE_SETTINGS_NAMESPACE);
+		return new DeviceId(StringUtils.fromHexString(s.get(DEVICE_ID_KEY)));
+	}
+
 	public ContactId addContact(Connection txn, Author remote, AuthorId local)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -589,9 +539,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 				int[] batchAffected = ps.executeBatch();
 				if (batchAffected.length != ids.size())
 					throw new DbStateException();
-				for (int i = 0; i < batchAffected.length; i++) {
-					if (batchAffected[i] != 1) throw new DbStateException();
-				}
+				for (int rows : batchAffected)
+					if (rows != 1) throw new DbStateException();
 				ps.close();
 			}
 			// Make groups that are visible to everyone visible to this contact
@@ -614,9 +563,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 				int[] batchAffected = ps.executeBatch();
 				if (batchAffected.length != ids.size())
 					throw new DbStateException();
-				for (int i = 0; i < batchAffected.length; i++) {
-					if (batchAffected[i] != 1) throw new DbStateException();
-				}
+				for (int rows : batchAffected)
+					if (rows != 1) throw new DbStateException();
 				ps.close();
 			}
 			// Create a group version row
@@ -629,31 +577,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
-			// Create a transport version row for each local transport
-			sql = "SELECT transportId FROM transports";
-			ps = txn.prepareStatement(sql);
-			rs = ps.executeQuery();
-			Collection<String> transports = new ArrayList<String>();
-			while (rs.next()) transports.add(rs.getString(1));
-			rs.close();
-			ps.close();
-			if (transports.isEmpty()) return c;
-			sql = "INSERT INTO transportVersions (contactId, transportId,"
-					+ " localVersion, localAcked, expiry, txCount)"
-					+ " VALUES (?, ?, 1, 0, 0, 0)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			for (String t : transports) {
-				ps.setString(2, t);
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != transports.size())
-				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
 			return c;
 		} catch (SQLException e) {
 			tryToClose(rs);
@@ -851,30 +774,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
-			// Create a transport version row for each contact
-			sql = "SELECT contactId FROM contacts";
-			ps = txn.prepareStatement(sql);
-			rs = ps.executeQuery();
-			Collection<Integer> contacts = new ArrayList<Integer>();
-			while (rs.next()) contacts.add(rs.getInt(1));
-			rs.close();
-			ps.close();
-			if (contacts.isEmpty()) return true;
-			sql = "INSERT INTO transportVersions (contactId, transportId,"
-					+ " localVersion, localAcked, expiry, txCount)"
-					+ " VALUES (?, ?, 1, 0, 0, 0)";
-			ps = txn.prepareStatement(sql);
-			ps.setString(2, t.getString());
-			for (Integer c : contacts) {
-				ps.setInt(1, c);
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != contacts.size())
-				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
 			return true;
 		} catch (SQLException e) {
 			tryToClose(ps);
@@ -920,9 +819,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.addBatch();
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != 3) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
+			for (int rows : batchAffected)
+				if (rows != 1) throw new DbStateException();
 			ps.close();
 			// Store the outgoing keys
 			sql = "INSERT INTO outgoingKeys (contactId, transportId, period,"
@@ -1415,62 +1313,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<TransportId, TransportProperties> getLocalProperties(
-			Connection txn) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT transportId, key, value"
-					+ " FROM transportProperties"
-					+ " ORDER BY transportId";
-			ps = txn.prepareStatement(sql);
-			rs = ps.executeQuery();
-			Map<TransportId, TransportProperties> properties =
-					new HashMap<TransportId, TransportProperties>();
-			TransportId lastId = null;
-			TransportProperties p = null;
-			while (rs.next()) {
-				TransportId id = new TransportId(rs.getString(1));
-				String key = rs.getString(2), value = rs.getString(3);
-				if (!id.equals(lastId)) {
-					p = new TransportProperties();
-					properties.put(id, p);
-					lastId = id;
-				}
-				p.put(key, value);
-			}
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableMap(properties);
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	public TransportProperties getLocalProperties(Connection txn, TransportId t)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT key, value FROM transportProperties"
-					+ " WHERE transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setString(1, t.getString());
-			rs = ps.executeQuery();
-			TransportProperties p = new TransportProperties();
-			while (rs.next()) p.put(rs.getString(1), rs.getString(2));
-			rs.close();
-			ps.close();
-			return p;
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
 			GroupId g) throws DbException {
 		PreparedStatement ps = null;
@@ -1773,42 +1615,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<ContactId, TransportProperties> getRemoteProperties(
-			Connection txn, TransportId t) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT contactId, key, value"
-					+ " FROM contactTransportProperties"
-					+ " WHERE transportId = ?"
-					+ " ORDER BY contactId";
-			ps = txn.prepareStatement(sql);
-			ps.setString(1, t.getString());
-			rs = ps.executeQuery();
-			Map<ContactId, TransportProperties> properties =
-					new HashMap<ContactId, TransportProperties>();
-			ContactId lastId = null;
-			TransportProperties p = null;
-			while (rs.next()) {
-				ContactId id = new ContactId(rs.getInt(1));
-				String key = rs.getString(2), value = rs.getString(3);
-				if (!id.equals(lastId)) {
-					p = new TransportProperties();
-					properties.put(id, p);
-					lastId = id;
-				}
-				p.put(key, value);
-			}
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableMap(properties);
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
 			ContactId c, int maxLength) throws DbException {
 		long now = clock.currentTimeMillis();
@@ -1996,48 +1802,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<TransportAck> getTransportAcks(Connection txn,
-			ContactId c) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT transportId, remoteVersion"
-					+ " FROM contactTransportVersions"
-					+ " WHERE contactId = ? AND remoteAcked = FALSE";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			rs = ps.executeQuery();
-			List<TransportAck> acks = new ArrayList<TransportAck>();
-			while (rs.next()) {
-				TransportId id = new TransportId(rs.getString(1));
-				acks.add(new TransportAck(id, rs.getLong(2)));
-			}
-			rs.close();
-			ps.close();
-			if (acks.isEmpty()) return null;
-			sql = "UPDATE contactTransportVersions SET remoteAcked = TRUE"
-					+ " WHERE contactId = ? AND transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			for (TransportAck a : acks) {
-				ps.setString(2, a.getId().getString());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != acks.size())
-				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
-			return Collections.unmodifiableList(acks);
-		} catch (SQLException e) {
-			tryToClose(ps);
-			tryToClose(rs);
-			throw new DbException(e);
-		}
-	}
-
 	public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
 			TransportId t) throws DbException {
 		PreparedStatement ps = null;
@@ -2123,72 +1887,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<TransportUpdate> getTransportUpdates(Connection txn,
-			ContactId c, int maxLatency) throws DbException {
-		long now = clock.currentTimeMillis();
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT tp.transportId, key, value, localVersion,"
-					+ " txCount"
-					+ " FROM transportProperties AS tp"
-					+ " JOIN transportVersions AS tv"
-					+ " ON tp.transportId = tv.transportId"
-					+ " WHERE tv.contactId = ?"
-					+ " AND localVersion > localAcked"
-					+ " AND expiry < ?"
-					+ " ORDER BY tp.transportId";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setLong(2, now);
-			rs = ps.executeQuery();
-			List<TransportUpdate> updates = new ArrayList<TransportUpdate>();
-			TransportId lastId = null;
-			TransportProperties p = null;
-			List<Integer> txCounts = new ArrayList<Integer>();
-			while (rs.next()) {
-				TransportId id = new TransportId(rs.getString(1));
-				String key = rs.getString(2), value = rs.getString(3);
-				long version = rs.getLong(4);
-				int txCount = rs.getInt(5);
-				if (!id.equals(lastId)) {
-					p = new TransportProperties();
-					updates.add(new TransportUpdate(id, p, version));
-					txCounts.add(txCount);
-					lastId = id;
-				}
-				p.put(key, value);
-			}
-			rs.close();
-			ps.close();
-			if (updates.isEmpty()) return null;
-			sql = "UPDATE transportVersions"
-					+ " SET expiry = ?, txCount = txCount + 1"
-					+ " WHERE contactId = ? AND transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(2, c.getInt());
-			int i = 0;
-			for (TransportUpdate u : updates) {
-				int txCount = txCounts.get(i++);
-				ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
-				ps.setString(3, u.getId().getString());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != updates.size())
-				throw new DbStateException();
-			for (i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
-			return Collections.unmodifiableList(updates);
-		} catch (SQLException e) {
-			tryToClose(ps);
-			tryToClose(rs);
-			throw new DbException(e);
-		}
-	}
-
 	public Collection<ContactId> getVisibility(Connection txn, GroupId g)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -2245,9 +1943,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != acked.size())
 				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] < 0) throw new DbStateException();
-				if (batchAffected[i] > 1) throw new DbStateException();
+			for (int rows : batchAffected) {
+				if (rows < 0) throw new DbStateException();
+				if (rows > 1) throw new DbStateException();
 			}
 			ps.close();
 		} catch (SQLException e) {
@@ -2271,76 +1969,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != requested.size())
 				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] < 0) throw new DbStateException();
-				if (batchAffected[i] > 1) throw new DbStateException();
-			}
-			ps.close();
-		} catch (SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	public void mergeLocalProperties(Connection txn, TransportId t,
-			TransportProperties p) throws DbException {
-		// Merge the new properties with the existing ones
-		mergeStringMap(txn, t, p, "transportProperties");
-		// Bump the transport version
-		PreparedStatement ps = null;
-		try {
-			String sql = "UPDATE transportVersions"
-					+ " SET localVersion = localVersion + 1, expiry = 0"
-					+ " WHERE transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setString(1, t.getString());
-			ps.executeUpdate();
-			ps.close();
-		} catch (SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	private void mergeStringMap(Connection txn, TransportId t,
-			Map<String, String> m, String tableName) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			// Update any properties that already exist
-			String sql = "UPDATE " + tableName + " SET value = ?"
-					+ " WHERE transportId = ? AND key = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setString(2, t.getString());
-			for (Entry<String, String> e : m.entrySet()) {
-				ps.setString(1, e.getValue());
-				ps.setString(3, e.getKey());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != m.size()) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] < 0) throw new DbStateException();
-				if (batchAffected[i] > 1) throw new DbStateException();
-			}
-			// Insert any properties that don't already exist
-			sql = "INSERT INTO " + tableName + " (transportId, key, value)"
-					+ " VALUES (?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setString(1, t.getString());
-			int updateIndex = 0, inserted = 0;
-			for (Entry<String, String> e : m.entrySet()) {
-				if (batchAffected[updateIndex] == 0) {
-					ps.setString(2, e.getKey());
-					ps.setString(3, e.getValue());
-					ps.addBatch();
-					inserted++;
-				}
-				updateIndex++;
-			}
-			batchAffected = ps.executeBatch();
-			if (batchAffected.length != inserted) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
+			for (int rows: batchAffected) {
+				if (rows < 0) throw new DbStateException();
+				if (rows > 1) throw new DbStateException();
 			}
 			ps.close();
 		} catch (SQLException e) {
@@ -2383,9 +2014,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				int[] batchAffected = ps.executeBatch();
 				if (batchAffected.length != removed.size())
 					throw new DbStateException();
-				for (int i = 0; i < batchAffected.length; i++) {
-					if (batchAffected[i] < 0) throw new DbStateException();
-					if (batchAffected[i] > 1) throw new DbStateException();
+				for (int rows : batchAffected) {
+					if (rows < 0) throw new DbStateException();
+					if (rows > 1) throw new DbStateException();
 				}
 				ps.close();
 			}
@@ -2403,9 +2034,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != retained.size())
 				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] < 0) throw new DbStateException();
-				if (batchAffected[i] > 1) throw new DbStateException();
+			for (int rows : batchAffected) {
+				if (rows < 0) throw new DbStateException();
+				if (rows > 1) throw new DbStateException();
 			}
 			// Insert any keys that don't already exist
 			sql = "INSERT INTO " + tableName
@@ -2425,9 +2056,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			}
 			batchAffected = ps.executeBatch();
 			if (batchAffected.length != inserted) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
+			for (int rows : batchAffected)
+				if (rows != 1) throw new DbStateException();
 			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
@@ -2451,9 +2081,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			}
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != s.size()) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] < 0) throw new DbStateException();
-				if (batchAffected[i] > 1) throw new DbStateException();
+			for (int rows : batchAffected) {
+				if (rows < 0) throw new DbStateException();
+				if (rows > 1) throw new DbStateException();
 			}
 			// Insert any settings that don't already exist
 			sql = "INSERT INTO settings (key, value, namespace)"
@@ -2472,9 +2102,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			}
 			batchAffected = ps.executeBatch();
 			if (batchAffected.length != inserted) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
+			for (int rows : batchAffected)
+				if (rows != 1) throw new DbStateException();
 			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
@@ -2586,9 +2215,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != visible.size())
 				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
+			for (int rows : batchAffected)
+				if (rows != 1) throw new DbStateException();
 			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
@@ -2662,9 +2290,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != requested.size())
 				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
+			for (int rows : batchAffected)
+				if (rows != 1) throw new DbStateException();
 			ps.close();
 		} catch (SQLException e) {
 			tryToClose(ps);
@@ -2734,7 +2361,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void setContactStatus(Connection txn, ContactId c, StorageStatus s)
-		throws DbException {
+			throws DbException {
 		PreparedStatement ps = null;
 		try {
 			String sql = "UPDATE contacts SET status = ? WHERE contactId = ?";
@@ -2769,7 +2396,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void setMessageValidity(Connection txn, MessageId m, boolean valid)
-		throws DbException {
+			throws DbException {
 		PreparedStatement ps = null;
 		try {
 			String sql = "UPDATE messages SET valid = ? WHERE messageId = ?";
@@ -2856,9 +2483,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 				int[] batchAffected = ps.executeBatch();
 				if (batchAffected.length != removed.size())
 					throw new DbStateException();
-				for (int i = 0; i < batchAffected.length; i++) {
-					if (batchAffected[i] < 0) throw new DbStateException();
-				}
+				for (int rows : batchAffected)
+					if (rows < 0) throw new DbStateException();
 				ps.close();
 			}
 			// Delete the existing subscriptions, if any
@@ -2882,130 +2508,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int[] batchAffected = ps.executeBatch();
 			if (batchAffected.length != groups.size())
 				throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
-			return true;
-		} catch (SQLException e) {
-			tryToClose(ps);
-			tryToClose(rs);
-			throw new DbException(e);
-		}
-	}
-
-	public void setRemoteProperties(Connection txn, ContactId c,
-			Map<TransportId, TransportProperties> p) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			// Delete the existing properties, if any
-			String sql = "DELETE FROM contactTransportProperties"
-					+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.executeUpdate();
-			ps.close();
-			// Store the new properties
-			sql = "INSERT INTO contactTransportProperties"
-					+ " (contactId, transportId, key, value)"
-					+ " VALUES (?, ?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			int batchSize = 0;
-			for (Entry<TransportId, TransportProperties> e : p.entrySet()) {
-				ps.setString(2, e.getKey().getString());
-				for (Entry<String, String> e1 : e.getValue().entrySet()) {
-					ps.setString(3, e1.getKey());
-					ps.setString(4, e1.getValue());
-					ps.addBatch();
-					batchSize++;
-				}
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != batchSize) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
-		} catch (SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	public boolean setRemoteProperties(Connection txn, ContactId c,
-			TransportId t, TransportProperties p, long version)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			// Find the existing version, if any
-			String sql = "SELECT NULL FROM contactTransportVersions"
-					+ " WHERE contactId = ? AND transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setString(2, t.getString());
-			rs = ps.executeQuery();
-			boolean found = rs.next();
-			if (rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			// Mark the update as needing to be acked
-			if (found) {
-				// The row exists - update it
-				sql = "UPDATE contactTransportVersions"
-						+ " SET remoteVersion = ?, remoteAcked = FALSE"
-						+ " WHERE contactId = ? AND transportId = ?"
-						+ " AND remoteVersion < ?";
-				ps = txn.prepareStatement(sql);
-				ps.setLong(1, version);
-				ps.setInt(2, c.getInt());
-				ps.setString(3, t.getString());
-				ps.setLong(4, version);
-				int affected = ps.executeUpdate();
-				if (affected < 0 || affected > 1) throw new DbStateException();
-				ps.close();
-				// Return false if the update is obsolete
-				if (affected == 0) return false;
-			} else {
-				// The row doesn't exist - create it
-				sql = "INSERT INTO contactTransportVersions (contactId,"
-						+ " transportId, remoteVersion, remoteAcked)"
-						+ " VALUES (?, ?, ?, FALSE)";
-				ps = txn.prepareStatement(sql);
-				ps.setInt(1, c.getInt());
-				ps.setString(2, t.getString());
-				ps.setLong(3, version);
-				int affected = ps.executeUpdate();
-				if (affected != 1) throw new DbStateException();
-				ps.close();
-			}
-			// Delete the existing properties, if any
-			sql = "DELETE FROM contactTransportProperties"
-					+ " WHERE contactId = ? AND transportId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setString(2, t.getString());
-			ps.executeUpdate();
-			ps.close();
-			// Store the new properties, if any
-			if (p.isEmpty()) return true;
-			sql = "INSERT INTO contactTransportProperties"
-					+ " (contactId, transportId, key, value)"
-					+ " VALUES (?, ?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setString(2, t.getString());
-			for (Entry<String, String> e : p.entrySet()) {
-				ps.setString(3, e.getKey());
-				ps.setString(4, e.getValue());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if (batchAffected.length != p.size()) throw new DbStateException();
-			for (int i = 0; i < batchAffected.length; i++) {
-				if (batchAffected[i] != 1) throw new DbStateException();
-			}
+			for (int rows : batchAffected)
+				if (rows != 1) throw new DbStateException();
 			ps.close();
 			return true;
 		} catch (SQLException e) {
@@ -3036,28 +2540,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setTransportUpdateAcked(Connection txn, ContactId c,
-			TransportId t, long version) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			String sql = "UPDATE transportVersions SET localAcked = ?"
-					+ " WHERE contactId = ? AND transportId = ?"
-					+ " AND localAcked < ? AND localVersion >= ?";
-			ps = txn.prepareStatement(sql);
-			ps.setLong(1, version);
-			ps.setInt(2, c.getInt());
-			ps.setString(3, t.getString());
-			ps.setLong(4, version);
-			ps.setLong(5, version);
-			int affected = ps.executeUpdate();
-			if (affected < 0 || affected > 1) throw new DbStateException();
-			ps.close();
-		} catch (SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public void setVisibleToAll(Connection txn, GroupId g, boolean all)
 			throws DbException {
 		PreparedStatement ps = null;
diff --git a/briar-core/src/org/briarproject/forum/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/lifecycle/LifecycleManagerImpl.java b/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
index f5a8c215c5f95fecd44cfe914a83e317cf3538c0..aba60f16b147645e738cda56f258a17d73766c8c 100644
--- a/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
+++ b/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
@@ -1,11 +1,12 @@
 package org.briarproject.lifecycle;
 
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
-import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
-import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
-import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.ShutdownEvent;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.lifecycle.Service;
+import org.briarproject.api.system.Clock;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -17,13 +18,12 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.ShutdownEvent;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.lifecycle.Service;
-import org.briarproject.api.system.Clock;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
+import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
+import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
+import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
 
 class LifecycleManagerImpl implements LifecycleManager {
 
@@ -98,9 +98,6 @@ class LifecycleManagerImpl implements LifecycleManager {
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			return DB_ERROR;
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return DB_ERROR;
 		} finally {
 			startStopSemaphore.release();
 		}
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..036dd125be6314d7ec602c875e4b90132c826b58
--- /dev/null
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -0,0 +1,345 @@
+package org.briarproject.properties;
+
+import com.google.inject.Inject;
+
+import org.briarproject.api.DeviceId;
+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
+			DeviceId dev = db.getDeviceId();
+			Map<TransportId, TransportProperties> local = getLocalProperties();
+			for (Entry<TransportId, TransportProperties> e : local.entrySet())
+				storeMessage(g.getId(), dev, 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, DeviceId dev, TransportId t,
+			TransportProperties p, long version) throws DbException,
+			IOException {
+		byte[] body = encodeProperties(dev, 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(DeviceId dev, TransportId t,
+			TransportProperties p, long version) {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		BdfWriter w = bdfWriterFactory.createWriter(out);
+		try {
+			w.writeListStart();
+			w.writeRaw(dev.getBytes());
+			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, LatestUpdate> latest =
+					findLatest(localGroup.getId(), true);
+			// Retrieve and decode the latest local properties
+			Map<TransportId, TransportProperties> local =
+					new HashMap<TransportId, TransportProperties>();
+			for (Entry<TransportId, LatestUpdate> 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, LatestUpdate> findLatest(GroupId g, boolean local)
+			throws DbException, FormatException {
+		// TODO: Use metadata queries
+		Map<TransportId, LatestUpdate> latestUpdates =
+				new HashMap<TransportId, LatestUpdate>();
+		Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
+		for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
+			BdfDictionary d = metadataParser.parse(e.getValue());
+			if (d.getBoolean("local") != local) continue;
+			TransportId t = new TransportId(d.getString("transportId"));
+			long version = d.getInteger("version");
+			LatestUpdate latest = latestUpdates.get(t);
+			if (latest == null || version > latest.version)
+				latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
+		}
+		return latestUpdates;
+	}
+
+	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);
+		r.readListStart();
+		r.skipRaw(); // Device ID
+		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);
+		}
+		return p;
+	}
+
+	@Override
+	public TransportProperties getLocalProperties(TransportId t)
+			throws DbException {
+		lock.readLock().lock();
+		try {
+			// Find the latest local version
+			LatestUpdate 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
+				LatestUpdate 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
+			TransportProperties merged;
+			LatestUpdate latest = findLatest(localGroup.getId(), true).get(t);
+			if (latest == null) {
+				merged = p;
+			} else {
+				byte[] raw = db.getRawMessage(latest.messageId);
+				TransportProperties old = decodeProperties(raw);
+				merged = new TransportProperties(old);
+				merged.putAll(p);
+				if (merged.equals(old)) return; // Unchanged
+			}
+			// Store the merged properties in the local group
+			DeviceId dev = db.getDeviceId();
+			long version = latest == null ? 0 : latest.version + 1;
+			storeMessage(localGroup.getId(), dev, t, merged, 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(), dev, t, merged, version);
+			}
+		} catch (IOException e) {
+			throw new DbException(e);
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	private static class LatestUpdate {
+
+		private final MessageId messageId;
+		private final long version;
+
+		private LatestUpdate(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..eeee9f333447c2489c62e27d42da582eca6bde9c
--- /dev/null
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
@@ -0,0 +1,85 @@
+package org.briarproject.properties;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.UniqueId;
+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);
+			r.readListStart();
+			byte[] deviceId = r.readRaw(UniqueId.LENGTH);
+			if (deviceId.length != UniqueId.LENGTH) throw new FormatException();
+			String transportId = r.readString(MAX_TRANSPORT_ID_LENGTH);
+			if (transportId.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", transportId);
+			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 355ee55f18e8f689e5b67c7f5a95383cb8793354..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java
+++ /dev/null
@@ -1,53 +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.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 {
-		return db.getLocalProperties();
-	}
-
-	@Override
-	public TransportProperties getLocalProperties(TransportId t)
-			throws DbException {
-		return db.getLocalProperties(t);
-	}
-
-	@Override
-	public Map<ContactId, TransportProperties> getRemoteProperties(
-			TransportId t) throws DbException {
-		return db.getRemoteProperties(t);
-	}
-
-	@Override
-	public void mergeLocalProperties(TransportId t, TransportProperties p)
-			throws DbException {
-		db.mergeLocalProperties(t, p);
-	}
-
-	@Override
-	public void setRemoteProperties(ContactId c,
-			Map<TransportId, TransportProperties> p) throws DbException {
-		db.setRemoteProperties(c, p);
-	}
-}
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/sync/DuplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java
index 1d14d737875f9da93fe8ea72284270addb65b994..52528de6012720b86f5f6ee2d50190d4859569d8 100644
--- a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java
@@ -9,13 +9,11 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
-import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageRequestedEvent;
 import org.briarproject.api.event.MessageToAckEvent;
 import org.briarproject.api.event.MessageToRequestEvent;
 import org.briarproject.api.event.MessageValidatedEvent;
 import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
-import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
 import org.briarproject.api.event.ShutdownEvent;
 import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.sync.Ack;
@@ -25,8 +23,6 @@ import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
 import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.api.system.Clock;
 
 import java.io.IOException;
@@ -91,8 +87,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		eventBus.addListener(this);
 		try {
 			// Start a query for each type of packet, in order of urgency
-			dbExecutor.execute(new GenerateTransportAcks());
-			dbExecutor.execute(new GenerateTransportUpdates());
 			dbExecutor.execute(new GenerateSubscriptionAck());
 			dbExecutor.execute(new GenerateSubscriptionUpdate());
 			dbExecutor.execute(new GenerateAck());
@@ -123,7 +117,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 						now = clock.currentTimeMillis();
 						if (now >= nextRetxQuery) {
 							// Check for retransmittable packets
-							dbExecutor.execute(new GenerateTransportUpdates());
 							dbExecutor.execute(new GenerateSubscriptionUpdate());
 							dbExecutor.execute(new GenerateBatch());
 							dbExecutor.execute(new GenerateOffer());
@@ -171,8 +164,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 				dbExecutor.execute(new GenerateSubscriptionUpdate());
 				dbExecutor.execute(new GenerateOffer());
 			}
-		} else if (e instanceof LocalTransportsUpdatedEvent) {
-			dbExecutor.execute(new GenerateTransportUpdates());
 		} else if (e instanceof MessageRequestedEvent) {
 			if (((MessageRequestedEvent) e).getContactId().equals(contactId))
 				dbExecutor.execute(new GenerateBatch());
@@ -189,11 +180,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 				dbExecutor.execute(new GenerateSubscriptionAck());
 				dbExecutor.execute(new GenerateOffer());
 			}
-		} else if (e instanceof RemoteTransportsUpdatedEvent) {
-			RemoteTransportsUpdatedEvent r =
-					(RemoteTransportsUpdatedEvent) e;
-			if (r.getContactId().equals(contactId))
-				dbExecutor.execute(new GenerateTransportAcks());
 		} else if (e instanceof ShutdownEvent) {
 			interrupt();
 		} else if (e instanceof TransportRemovedEvent) {
@@ -414,76 +400,4 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 			dbExecutor.execute(new GenerateSubscriptionUpdate());
 		}
 	}
-
-	// This task runs on the database thread
-	private class GenerateTransportAcks implements Runnable {
-
-		public void run() {
-			if (interrupted) return;
-			try {
-				Collection<TransportAck> acks =
-						db.generateTransportAcks(contactId);
-				if (LOG.isLoggable(INFO))
-					LOG.info("Generated transport acks: " + (acks != null));
-				if (acks != null) writerTasks.add(new WriteTransportAcks(acks));
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				interrupt();
-			}
-		}
-	}
-
-	// This tasks runs on the writer thread
-	private class WriteTransportAcks implements ThrowingRunnable<IOException> {
-
-		private final Collection<TransportAck> acks;
-
-		private WriteTransportAcks(Collection<TransportAck> acks) {
-			this.acks = acks;
-		}
-
-		public void run() throws IOException {
-			if (interrupted) return;
-			for (TransportAck a : acks) packetWriter.writeTransportAck(a);
-			LOG.info("Sent transport acks");
-			dbExecutor.execute(new GenerateTransportAcks());
-		}
-	}
-
-	// This task runs on the database thread
-	private class GenerateTransportUpdates implements Runnable {
-
-		public void run() {
-			if (interrupted) return;
-			try {
-				Collection<TransportUpdate> t =
-						db.generateTransportUpdates(contactId, maxLatency);
-				if (LOG.isLoggable(INFO))
-					LOG.info("Generated transport updates: " + (t != null));
-				if (t != null) writerTasks.add(new WriteTransportUpdates(t));
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				interrupt();
-			}
-		}
-	}
-
-	// This task runs on the writer thread
-	private class WriteTransportUpdates
-	implements ThrowingRunnable<IOException> {
-
-		private final Collection<TransportUpdate> updates;
-
-		private WriteTransportUpdates(Collection<TransportUpdate> updates) {
-			this.updates = updates;
-		}
-
-		public void run() throws IOException {
-			if (interrupted) return;
-			for (TransportUpdate u : updates)
-				packetWriter.writeTransportUpdate(u);
-			LOG.info("Sent transport updates");
-			dbExecutor.execute(new GenerateTransportUpdates());
-		}
-	}
 }
diff --git a/briar-core/src/org/briarproject/sync/IncomingSession.java b/briar-core/src/org/briarproject/sync/IncomingSession.java
index 9e8bd2caa95e385b2c761858947b8d168e7cedcd..7c7444664ca84f99c426ab93dca6e8e333711dc0 100644
--- a/briar-core/src/org/briarproject/sync/IncomingSession.java
+++ b/briar-core/src/org/briarproject/sync/IncomingSession.java
@@ -19,8 +19,6 @@ import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
 import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 
 import java.io.IOException;
 import java.util.concurrent.Executor;
@@ -77,12 +75,6 @@ class IncomingSession implements SyncSession, EventListener {
 				} else if (packetReader.hasSubscriptionUpdate()) {
 					SubscriptionUpdate u = packetReader.readSubscriptionUpdate();
 					dbExecutor.execute(new ReceiveSubscriptionUpdate(u));
-				} else if (packetReader.hasTransportAck()) {
-					TransportAck a = packetReader.readTransportAck();
-					dbExecutor.execute(new ReceiveTransportAck(a));
-				} else if (packetReader.hasTransportUpdate()) {
-					TransportUpdate u = packetReader.readTransportUpdate();
-					dbExecutor.execute(new ReceiveTransportUpdate(u));
 				} else {
 					throw new FormatException();
 				}
@@ -216,40 +208,4 @@ class IncomingSession implements SyncSession, EventListener {
 			}
 		}
 	}
-
-	private class ReceiveTransportAck implements Runnable {
-
-		private final TransportAck ack;
-
-		private ReceiveTransportAck(TransportAck ack) {
-			this.ack = ack;
-		}
-
-		public void run() {
-			try {
-				db.receiveTransportAck(contactId, ack);
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				interrupt();
-			}
-		}
-	}
-
-	private class ReceiveTransportUpdate implements Runnable {
-
-		private final TransportUpdate update;
-
-		private ReceiveTransportUpdate(TransportUpdate update) {
-			this.update = update;
-		}
-
-		public void run() {
-			try {
-				db.receiveTransportUpdate(contactId, update);
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				interrupt();
-			}
-		}
-	}
 }
diff --git a/briar-core/src/org/briarproject/sync/PacketReaderImpl.java b/briar-core/src/org/briarproject/sync/PacketReaderImpl.java
index 2d1a8bfc7ce4f27ffe76bf69c15c57fc822f99e7..8c9a10ada622c317903e837b5cdbda11397e2b71 100644
--- a/briar-core/src/org/briarproject/sync/PacketReaderImpl.java
+++ b/briar-core/src/org/briarproject/sync/PacketReaderImpl.java
@@ -1,8 +1,6 @@
 package org.briarproject.sync;
 
 import org.briarproject.api.FormatException;
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.UniqueId;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.data.BdfReader;
@@ -17,8 +15,6 @@ import org.briarproject.api.sync.PacketReader;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.util.ByteUtils;
 
 import java.io.ByteArrayInputStream;
@@ -26,21 +22,14 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-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.sync.PacketTypes.ACK;
 import static org.briarproject.api.sync.PacketTypes.MESSAGE;
 import static org.briarproject.api.sync.PacketTypes.OFFER;
 import static org.briarproject.api.sync.PacketTypes.REQUEST;
 import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_ACK;
 import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_UPDATE;
-import static org.briarproject.api.sync.PacketTypes.TRANSPORT_ACK;
-import static org.briarproject.api.sync.PacketTypes.TRANSPORT_UPDATE;
 import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
@@ -205,66 +194,4 @@ class PacketReaderImpl implements PacketReader {
 		state = State.BUFFER_EMPTY;
 		return u;
 	}
-
-	public boolean hasTransportAck() throws IOException {
-		return !eof() && header[1] == TRANSPORT_ACK;
-	}
-
-	public TransportAck readTransportAck() throws IOException {
-		if (!hasTransportAck()) throw new FormatException();
-		// Set up the reader
-		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		BdfReader r = bdfReaderFactory.createReader(bais);
-		// Read the start of the payload
-		r.readListStart();
-		// Read the transport ID and version
-		String idString = r.readString(MAX_TRANSPORT_ID_LENGTH);
-		if (idString.length() == 0) throw new FormatException();
-		TransportId id = new TransportId(idString);
-		long version = r.readInteger();
-		if (version < 0) throw new FormatException();
-		// Read the end of the payload
-		r.readListEnd();
-		if (!r.eof()) throw new FormatException();
-		state = State.BUFFER_EMPTY;
-		// Build and return the transport ack
-		return new TransportAck(id, version);
-	}
-
-	public boolean hasTransportUpdate() throws IOException {
-		return !eof() && header[1] == TRANSPORT_UPDATE;
-	}
-
-	public TransportUpdate readTransportUpdate() throws IOException {
-		if (!hasTransportUpdate()) throw new FormatException();
-		// Set up the reader
-		InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
-		BdfReader r = bdfReaderFactory.createReader(bais);
-		// Read the start of the payload
-		r.readListStart();
-		// Read the transport ID
-		String idString = r.readString(MAX_TRANSPORT_ID_LENGTH);
-		if (idString.length() == 0) throw new FormatException();
-		TransportId id = new TransportId(idString);
-		// Read the transport properties
-		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();
-		// Read the version number
-		long version = r.readInteger();
-		if (version < 0) throw new FormatException();
-		// Read the end of the payload
-		r.readListEnd();
-		if (!r.eof()) throw new FormatException();
-		state = State.BUFFER_EMPTY;
-		// Build and return the transport update
-		return new TransportUpdate(id, new TransportProperties(p), version);
-	}
 }
diff --git a/briar-core/src/org/briarproject/sync/PacketWriterImpl.java b/briar-core/src/org/briarproject/sync/PacketWriterImpl.java
index 449d40f19e233a0ac03fb14562276dc6716f84ae..6ecc62eefd7607d2be9cb4c578720d1a36096fe8 100644
--- a/briar-core/src/org/briarproject/sync/PacketWriterImpl.java
+++ b/briar-core/src/org/briarproject/sync/PacketWriterImpl.java
@@ -12,8 +12,6 @@ import org.briarproject.api.sync.PacketWriter;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.util.ByteUtils;
 
 import java.io.ByteArrayOutputStream;
@@ -25,8 +23,6 @@ import static org.briarproject.api.sync.PacketTypes.OFFER;
 import static org.briarproject.api.sync.PacketTypes.REQUEST;
 import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_ACK;
 import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_UPDATE;
-import static org.briarproject.api.sync.PacketTypes.TRANSPORT_ACK;
-import static org.briarproject.api.sync.PacketTypes.TRANSPORT_UPDATE;
 import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.PROTOCOL_VERSION;
@@ -125,27 +121,6 @@ class PacketWriterImpl implements PacketWriter {
 		writePacket(SUBSCRIPTION_UPDATE);
 	}
 
-	public void writeTransportAck(TransportAck a) throws IOException {
-		if (payload.size() != 0) throw new IllegalStateException();
-		BdfWriter w = bdfWriterFactory.createWriter(payload);
-		w.writeListStart();
-		w.writeString(a.getId().getString());
-		w.writeInteger(a.getVersion());
-		w.writeListEnd();
-		writePacket(TRANSPORT_ACK);
-	}
-
-	public void writeTransportUpdate(TransportUpdate u) throws IOException {
-		if (payload.size() != 0) throw new IllegalStateException();
-		BdfWriter w = bdfWriterFactory.createWriter(payload);
-		w.writeListStart();
-		w.writeString(u.getId().getString());
-		w.writeDictionary(u.getProperties());
-		w.writeInteger(u.getVersion());
-		w.writeListEnd();
-		writePacket(TRANSPORT_UPDATE);
-	}
-
 	public void flush() throws IOException {
 		out.flush();
 	}
diff --git a/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java
index f1504b9c825c674a7b8a618165e008fca1cca708..68e319abd262ec2ba892fd84c9755fc2577d96d5 100644
--- a/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java
@@ -15,8 +15,6 @@ import org.briarproject.api.sync.PacketWriter;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
 import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -68,7 +66,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 		this.transportId = transportId;
 		this.maxLatency = maxLatency;
 		this.packetWriter = packetWriter;
-		outstandingQueries = new AtomicInteger(6); // One per type of packet
+		outstandingQueries = new AtomicInteger(4); // One per type of packet
 		writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
 	}
 
@@ -76,8 +74,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 		eventBus.addListener(this);
 		try {
 			// Start a query for each type of packet, in order of urgency
-			dbExecutor.execute(new GenerateTransportAcks());
-			dbExecutor.execute(new GenerateTransportUpdates());
 			dbExecutor.execute(new GenerateSubscriptionAck());
 			dbExecutor.execute(new GenerateSubscriptionUpdate());
 			dbExecutor.execute(new GenerateAck());
@@ -264,78 +260,4 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 			dbExecutor.execute(new GenerateSubscriptionUpdate());
 		}
 	}
-
-	// This task runs on the database thread
-	private class GenerateTransportAcks implements Runnable {
-
-		public void run() {
-			if (interrupted) return;
-			try {
-				Collection<TransportAck> acks =
-						db.generateTransportAcks(contactId);
-				if (LOG.isLoggable(INFO))
-					LOG.info("Generated transport acks: " + (acks != null));
-				if (acks == null) decrementOutstandingQueries();
-				else writerTasks.add(new WriteTransportAcks(acks));
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				interrupt();
-			}
-		}
-	}
-
-	// This tasks runs on the writer thread
-	private class WriteTransportAcks implements ThrowingRunnable<IOException> {
-
-		private final Collection<TransportAck> acks;
-
-		private WriteTransportAcks(Collection<TransportAck> acks) {
-			this.acks = acks;
-		}
-
-		public void run() throws IOException {
-			if (interrupted) return;
-			for (TransportAck a : acks) packetWriter.writeTransportAck(a);
-			LOG.info("Sent transport acks");
-			dbExecutor.execute(new GenerateTransportAcks());
-		}
-	}
-
-	// This task runs on the database thread
-	private class GenerateTransportUpdates implements Runnable {
-
-		public void run() {
-			if (interrupted) return;
-			try {
-				Collection<TransportUpdate> t =
-						db.generateTransportUpdates(contactId, maxLatency);
-				if (LOG.isLoggable(INFO))
-					LOG.info("Generated transport updates: " + (t != null));
-				if (t == null) decrementOutstandingQueries();
-				else writerTasks.add(new WriteTransportUpdates(t));
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				interrupt();
-			}
-		}
-	}
-
-	// This task runs on the writer thread
-	private class WriteTransportUpdates
-	implements ThrowingRunnable<IOException> {
-
-		private final Collection<TransportUpdate> updates;
-
-		private WriteTransportUpdates(Collection<TransportUpdate> updates) {
-			this.updates = updates;
-		}
-
-		public void run() throws IOException {
-			if (interrupted) return;
-			for (TransportUpdate u : updates)
-				packetWriter.writeTransportUpdate(u);
-			LOG.info("Sent transport updates");
-			dbExecutor.execute(new GenerateTransportUpdates());
-		}
-	}
 }
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-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java
index 822831ac05b347daa88b041144a6bf100a93cd04..0753748e88c7c63ac8220d82c4041ee8094266a8 100644
--- a/briar-core/src/org/briarproject/util/StringUtils.java
+++ b/briar-core/src/org/briarproject/util/StringUtils.java
@@ -65,7 +65,8 @@ public class StringUtils {
 	/** Converts the given hex string to a byte array. */
 	public static byte[] fromHexString(String hex) {
 		int len = hex.length();
-		if (len % 2 != 0) throw new IllegalArgumentException("Not a hex string");
+		if (len % 2 != 0)
+			throw new IllegalArgumentException("Not a hex string");
 		byte[] bytes = new byte[len / 2];
 		for (int i = 0, j = 0; i < len; i += 2, j++) {
 			int high = hexDigitToInt(hex.charAt(i));
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/ProtocolIntegrationTest.java b/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
index 98975f205f2e6cedebc0dff4c377aafe35e22e62..bcd2d4acb85402f78f8b2a4c454c497f88676439 100644
--- a/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
@@ -4,7 +4,6 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.sync.Ack;
@@ -21,7 +20,6 @@ import org.briarproject.api.sync.PacketWriter;
 import org.briarproject.api.sync.PacketWriterFactory;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.api.transport.StreamContext;
 import org.briarproject.api.transport.StreamReaderFactory;
 import org.briarproject.api.transport.StreamWriterFactory;
@@ -56,12 +54,11 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 	private final PacketWriterFactory packetWriterFactory;
 
 	private final ContactId contactId;
+	private final TransportId transportId;
 	private final SecretKey tagKey, headerKey;
 	private final Group group;
 	private final Message message, message1;
 	private final Collection<MessageId> messageIds;
-	private final TransportId transportId;
-	private final TransportProperties transportProperties;
 
 	public ProtocolIntegrationTest() throws Exception {
 		Injector i = Guice.createInjector(new TestDatabaseModule(),
@@ -74,6 +71,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		packetReaderFactory = i.getInstance(PacketReaderFactory.class);
 		packetWriterFactory = i.getInstance(PacketWriterFactory.class);
 		contactId = new ContactId(234);
+		transportId = new TransportId("id");
 		// Create the transport keys
 		tagKey = TestUtils.createSecretKey();
 		headerKey = TestUtils.createSecretKey();
@@ -91,10 +89,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		message1 = messageFactory.createMessage(group.getId(), timestamp,
 				messageBody.getBytes("UTF-8"));
 		messageIds = Arrays.asList(message.getId(), message1.getId());
-		// Create some transport properties
-		transportId = new TransportId("id");
-		transportProperties = new TransportProperties(Collections.singletonMap(
-				"bar", "baz"));
 	}
 
 	@Test
@@ -124,10 +118,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 				Collections.singletonList(group), 1);
 		packetWriter.writeSubscriptionUpdate(su);
 
-		TransportUpdate tu = new TransportUpdate(transportId,
-				transportProperties, 1);
-		packetWriter.writeTransportUpdate(tu);
-
 		streamWriter.flush();
 		return out.toByteArray();
 	}
@@ -174,13 +164,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		assertEquals(Collections.singletonList(group), su.getGroups());
 		assertEquals(1, su.getVersion());
 
-		// Read the transport update
-		assertTrue(packetReader.hasTransportUpdate());
-		TransportUpdate tu = packetReader.readTransportUpdate();
-		assertEquals(transportId, tu.getId());
-		assertEquals(transportProperties, tu.getProperties());
-		assertEquals(1, tu.getVersion());
-
 		in.close();
 	}
 
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
index 4aa204bdd731af30a7529631b4a790bd235402bf..dae5884310c064bc7d125cf507992590c823060a 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
@@ -3,7 +3,6 @@ package org.briarproject.db;
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestUtils;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
@@ -18,7 +17,6 @@ import org.briarproject.api.db.NoSuchTransportException;
 import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
-import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageRequestedEvent;
 import org.briarproject.api.event.MessageToAckEvent;
@@ -42,8 +40,6 @@ import org.briarproject.api.sync.Offer;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionAck;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportAck;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.api.transport.IncomingKeys;
 import org.briarproject.api.transport.OutgoingKeys;
 import org.briarproject.api.transport.TransportKeys;
@@ -60,7 +56,6 @@ import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGT
 import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
@@ -80,7 +75,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 	private final Message message;
 	private final Metadata metadata;
 	private final TransportId transportId;
-	private final TransportProperties transportProperties;
 	private final int maxLatency;
 	private final ContactId contactId;
 	private final Contact contact;
@@ -88,7 +82,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 	public DatabaseComponentImplTest() {
 		clientId = new ClientId(TestUtils.getRandomId());
 		groupId = new GroupId(TestUtils.getRandomId());
-		ClientId clientId = new ClientId(TestUtils.getRandomId());
 		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		group = new Group(groupId, clientId, descriptor);
 		authorId = new AuthorId(TestUtils.getRandomId());
@@ -106,8 +99,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		metadata = new Metadata();
 		metadata.put("foo", new byte[] {'b', 'a', 'r'});
 		transportId = new TransportId("id");
-		transportProperties = new TransportProperties(Collections.singletonMap(
-				"bar", "baz"));
 		maxLatency = Integer.MAX_VALUE;
 		contactId = new ContactId(234);
 		contact = new Contact(contactId, author, localAuthorId,
@@ -128,9 +119,9 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
-			exactly(10).of(database).startTransaction();
+			exactly(9).of(database).startTransaction();
 			will(returnValue(txn));
-			exactly(10).of(database).commitTransaction(txn);
+			exactly(9).of(database).commitTransaction(txn);
 			// open()
 			oneOf(database).open();
 			will(returnValue(false));
@@ -150,9 +141,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			// getContacts()
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contact)));
-			// getRemoteProperties()
-			oneOf(database).getRemoteProperties(txn, transportId);
-			will(returnValue(Collections.emptyMap()));
 			// addGroup()
 			oneOf(database).containsGroup(txn, groupId);
 			will(returnValue(false));
@@ -194,8 +182,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		db.addLocalAuthor(localAuthor);
 		assertEquals(contactId, db.addContact(author, localAuthorId));
 		assertEquals(Collections.singletonList(contact), db.getContacts());
-		assertEquals(Collections.emptyMap(),
-				db.getRemoteProperties(transportId));
 		db.addGroup(group); // First time - listeners called
 		db.addGroup(group); // Second time - not called
 		assertEquals(Collections.singletonList(group), db.getGroups(clientId));
@@ -310,11 +296,11 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// Check whether the contact is in the DB (which it's not)
-			exactly(21).of(database).startTransaction();
+			exactly(17).of(database).startTransaction();
 			will(returnValue(txn));
-			exactly(21).of(database).containsContact(txn, contactId);
+			exactly(17).of(database).containsContact(txn, contactId);
 			will(returnValue(false));
-			exactly(21).of(database).abortTransaction(txn);
+			exactly(17).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
@@ -361,20 +347,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			// Expected
 		}
 
-		try {
-			db.generateTransportAcks(contactId);
-			fail();
-		} catch (NoSuchContactException expected) {
-			// Expected
-		}
-
-		try {
-			db.generateTransportUpdates(contactId, 123);
-			fail();
-		} catch (NoSuchContactException expected) {
-			// Expected
-		}
-
 		try {
 			db.getContact(contactId);
 			fail();
@@ -443,23 +415,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			// Expected
 		}
 
-		try {
-			TransportAck a = new TransportAck(transportId, 0);
-			db.receiveTransportAck(contactId, a);
-			fail();
-		} catch (NoSuchContactException expected) {
-			// Expected
-		}
-
-		try {
-			TransportUpdate u = new TransportUpdate(transportId,
-					transportProperties, 1);
-			db.receiveTransportUpdate(contactId, u);
-			fail();
-		} catch (NoSuchContactException expected) {
-			// Expected
-		}
-
 		try {
 			db.removeContact(contactId);
 			fail();
@@ -677,13 +632,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			will(returnValue(contactId));
 			oneOf(database).commitTransaction(txn);
 			// Check whether the transport is in the DB (which it's not)
-			exactly(6).of(database).startTransaction();
+			exactly(4).of(database).startTransaction();
 			will(returnValue(txn));
 			exactly(2).of(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			exactly(6).of(database).containsTransport(txn, transportId);
+			exactly(4).of(database).containsTransport(txn, transportId);
 			will(returnValue(false));
-			exactly(6).of(database).abortTransaction(txn);
+			exactly(4).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
@@ -691,13 +646,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		db.addLocalAuthor(localAuthor);
 		assertEquals(contactId, db.addContact(author, localAuthorId));
 
-		try {
-			db.getLocalProperties(transportId);
-			fail();
-		} catch (NoSuchTransportException expected) {
-			// Expected
-		}
-
 		try {
 			db.getTransportKeys(transportId);
 			fail();
@@ -705,13 +653,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			// Expected
 		}
 
-		try {
-			db.mergeLocalProperties(transportId, new TransportProperties());
-			fail();
-		} catch (NoSuchTransportException expected) {
-			// Expected
-		}
-
 		try {
 			db.incrementStreamCounter(contactId, transportId, 0);
 			fail();
@@ -952,62 +893,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		context.assertIsSatisfied();
 	}
 
-	@Test
-	public void testGenerateTransportUpdatesNoUpdatesDue() throws Exception {
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventBus eventBus = context.mock(EventBus.class);
-		context.checking(new Expectations() {{
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsContact(txn, contactId);
-			will(returnValue(true));
-			oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
-			will(returnValue(null));
-			oneOf(database).commitTransaction(txn);
-		}});
-		DatabaseComponent db = createDatabaseComponent(database, eventBus,
-				shutdown);
-
-		assertNull(db.generateTransportUpdates(contactId, maxLatency));
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testGenerateTransportUpdates() throws Exception {
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventBus eventBus = context.mock(EventBus.class);
-		context.checking(new Expectations() {{
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsContact(txn, contactId);
-			will(returnValue(true));
-			oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
-			will(returnValue(Collections.singletonList(new TransportUpdate(
-					transportId, transportProperties, 1))));
-			oneOf(database).commitTransaction(txn);
-		}});
-		DatabaseComponent db = createDatabaseComponent(database, eventBus,
-				shutdown);
-
-		Collection<TransportUpdate> updates =
-				db.generateTransportUpdates(contactId, maxLatency);
-		assertNotNull(updates);
-		assertEquals(1, updates.size());
-		TransportUpdate u = updates.iterator().next();
-		assertEquals(transportId, u.getId());
-		assertEquals(transportProperties, u.getProperties());
-		assertEquals(1, u.getVersion());
-
-		context.assertIsSatisfied();
-	}
-
 	@Test
 	public void testReceiveAck() throws Exception {
 		Mockery context = new Mockery();
@@ -1253,116 +1138,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		context.assertIsSatisfied();
 	}
 
-	@Test
-	public void testReceiveTransportAck() throws Exception {
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventBus eventBus = context.mock(EventBus.class);
-		context.checking(new Expectations() {{
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsContact(txn, contactId);
-			will(returnValue(true));
-			oneOf(database).containsTransport(txn, transportId);
-			will(returnValue(true));
-			oneOf(database).setTransportUpdateAcked(txn, contactId,
-					transportId, 1);
-			oneOf(database).commitTransaction(txn);
-		}});
-		DatabaseComponent db = createDatabaseComponent(database, eventBus,
-				shutdown);
-
-		TransportAck a = new TransportAck(transportId, 1);
-		db.receiveTransportAck(contactId, a);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testReceiveTransportUpdate() throws Exception {
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventBus eventBus = context.mock(EventBus.class);
-		context.checking(new Expectations() {{
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsContact(txn, contactId);
-			will(returnValue(true));
-			oneOf(database).setRemoteProperties(txn, contactId, transportId,
-					transportProperties, 1);
-			oneOf(database).commitTransaction(txn);
-		}});
-		DatabaseComponent db = createDatabaseComponent(database, eventBus,
-				shutdown);
-
-		TransportUpdate u = new TransportUpdate(transportId,
-				transportProperties, 1);
-		db.receiveTransportUpdate(contactId, u);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testChangingLocalTransportPropertiesCallsListeners()
-			throws Exception {
-		final TransportProperties properties =
-				new TransportProperties(Collections.singletonMap("bar", "baz"));
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventBus eventBus = context.mock(EventBus.class);
-		context.checking(new Expectations() {{
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsTransport(txn, transportId);
-			will(returnValue(true));
-			oneOf(database).getLocalProperties(txn, transportId);
-			will(returnValue(new TransportProperties()));
-			oneOf(database).mergeLocalProperties(txn, transportId, properties);
-			oneOf(database).commitTransaction(txn);
-			oneOf(eventBus).broadcast(with(any(
-					LocalTransportsUpdatedEvent.class)));
-		}});
-		DatabaseComponent db = createDatabaseComponent(database, eventBus,
-				shutdown);
-
-		db.mergeLocalProperties(transportId, properties);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testNotChangingLocalTransportPropertiesDoesNotCallListeners()
-			throws Exception {
-		final TransportProperties properties =
-				new TransportProperties(Collections.singletonMap("bar", "baz"));
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventBus eventBus = context.mock(EventBus.class);
-		context.checking(new Expectations() {{
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsTransport(txn, transportId);
-			will(returnValue(true));
-			oneOf(database).getLocalProperties(txn, transportId);
-			will(returnValue(properties));
-			oneOf(database).commitTransaction(txn);
-		}});
-		DatabaseComponent db = createDatabaseComponent(database, eventBus,
-				shutdown);
-
-		db.mergeLocalProperties(transportId, properties);
-
-		context.assertIsSatisfied();
-	}
-
 	@Test
 	public void testChangingVisibilityCallsListeners() throws Exception {
 		final ContactId contactId1 = new ContactId(123);
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index d6ad4240f83c2cded1a3d866497525c569c2696e..5dd1208986b9edbfecd0f191385263bbf5d7336a 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -3,9 +3,7 @@ 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.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DbException;
@@ -14,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;
@@ -29,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;
@@ -137,8 +137,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db = open(true);
 		txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
-		assertEquals(Collections.emptyMap(),
-				db.getRemoteProperties(txn, transportId));
 		assertFalse(db.containsGroup(txn, groupId));
 		assertFalse(db.containsMessage(txn, messageId));
 		db.commitTransaction(txn);
@@ -502,71 +500,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertFalse(error.get());
 	}
 
-	@Test
-	public void testUpdateRemoteTransportProperties() throws Exception {
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a contact with a transport
-		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		TransportProperties p = new TransportProperties(
-				Collections.singletonMap("foo", "bar"));
-		db.setRemoteProperties(txn, contactId, transportId, p, 1);
-		assertEquals(Collections.singletonMap(contactId, p),
-				db.getRemoteProperties(txn, transportId));
-
-		// Replace the transport properties
-		TransportProperties p1 = new TransportProperties(
-				Collections.singletonMap("baz", "bam"));
-		db.setRemoteProperties(txn, contactId, transportId, p1, 2);
-		assertEquals(Collections.singletonMap(contactId, p1),
-				db.getRemoteProperties(txn, transportId));
-
-		// Remove the transport properties
-		TransportProperties p2 = new TransportProperties();
-		db.setRemoteProperties(txn, contactId, transportId, p2, 3);
-		assertEquals(Collections.emptyMap(),
-				db.getRemoteProperties(txn, transportId));
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
-	@Test
-	public void testUpdateLocalTransportProperties() throws Exception {
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a transport to the database
-		db.addTransport(txn, transportId, 123);
-
-		// Set the transport properties
-		TransportProperties p = new TransportProperties();
-		p.put("foo", "foo");
-		p.put("bar", "bar");
-		db.mergeLocalProperties(txn, transportId, p);
-		assertEquals(p, db.getLocalProperties(txn, transportId));
-		assertEquals(Collections.singletonMap(transportId, p),
-				db.getLocalProperties(txn));
-
-		// Update one of the properties and add another
-		TransportProperties p1 = new TransportProperties();
-		p1.put("bar", "baz");
-		p1.put("bam", "bam");
-		db.mergeLocalProperties(txn, transportId, p1);
-		TransportProperties merged = new TransportProperties();
-		merged.put("foo", "foo");
-		merged.put("bar", "baz");
-		merged.put("bam", "bam");
-		assertEquals(merged, db.getLocalProperties(txn, transportId));
-		assertEquals(Collections.singletonMap(transportId, merged),
-				db.getLocalProperties(txn));
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
 	@Test
 	public void testUpdateSettings() throws Exception {
 		Database<Connection> db = open(false);
@@ -597,42 +530,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.close();
 	}
 
-	@Test
-	public void testTransportsNotUpdatedIfVersionIsOld() throws Exception {
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a contact
-		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-
-		// Initialise the transport properties with version 1
-		TransportProperties p = new TransportProperties(
-				Collections.singletonMap("foo", "bar"));
-		assertTrue(db.setRemoteProperties(txn, contactId, transportId, p, 1));
-		assertEquals(Collections.singletonMap(contactId, p),
-				db.getRemoteProperties(txn, transportId));
-
-		// Replace the transport properties with version 2
-		TransportProperties p1 = new TransportProperties(
-				Collections.singletonMap("baz", "bam"));
-		assertTrue(db.setRemoteProperties(txn, contactId, transportId, p1, 2));
-		assertEquals(Collections.singletonMap(contactId, p1),
-				db.getRemoteProperties(txn, transportId));
-
-		// Try to replace the transport properties with version 1
-		TransportProperties p2 = new TransportProperties(
-				Collections.singletonMap("quux", "etc"));
-		assertFalse(db.setRemoteProperties(txn, contactId, transportId, p2, 1));
-
-		// Version 2 of the properties should still be there
-		assertEquals(Collections.singletonMap(contactId, p1),
-				db.getRemoteProperties(txn, transportId));
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
 	@Test
 	public void testContainsVisibleMessageRequiresMessageInDatabase()
 			throws Exception {
@@ -1225,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/ConstantsTest.java b/briar-tests/src/org/briarproject/sync/ConstantsTest.java
index a67afd361459b5c2595134332aa5ce27e03016e2..4d0d498a4adcd5f3be7073ac41f9cb95af1c7504 100644
--- a/briar-tests/src/org/briarproject/sync/ConstantsTest.java
+++ b/briar-tests/src/org/briarproject/sync/ConstantsTest.java
@@ -8,8 +8,6 @@ import org.briarproject.TestDatabaseModule;
 import org.briarproject.TestLifecycleModule;
 import org.briarproject.TestSystemModule;
 import org.briarproject.TestUtils;
-import org.briarproject.api.TransportId;
-import org.briarproject.api.TransportProperties;
 import org.briarproject.api.UniqueId;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyPair;
@@ -34,7 +32,6 @@ import org.briarproject.api.sync.PacketWriter;
 import org.briarproject.api.sync.PacketWriterFactory;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionUpdate;
-import org.briarproject.api.sync.TransportUpdate;
 import org.briarproject.contact.ContactModule;
 import org.briarproject.crypto.CryptoModule;
 import org.briarproject.data.DataModule;
@@ -50,9 +47,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Random;
 
-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.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -194,27 +188,6 @@ public class ConstantsTest extends BriarTestCase {
 		testMessageIdsFitIntoRequest(1000);
 	}
 
-	@Test
-	public void testPropertiesFitIntoTransportUpdate() throws Exception {
-		// Create the maximum number of properties with the maximum length
-		TransportProperties p = new TransportProperties();
-		for (int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT; i++) {
-			String key = TestUtils.createRandomString(MAX_PROPERTY_LENGTH);
-			String value = TestUtils.createRandomString(MAX_PROPERTY_LENGTH);
-			p.put(key, value);
-		}
-		// Create a maximum-length transport update
-		String idString = TestUtils.createRandomString(MAX_TRANSPORT_ID_LENGTH);
-		TransportId id = new TransportId(idString);
-		TransportUpdate u = new TransportUpdate(id, p, Long.MAX_VALUE);
-		// Serialise the update
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		PacketWriter writer = packetWriterFactory.createPacketWriter(out);
-		writer.writeTransportUpdate(u);
-		// Check the size of the serialised transport update
-		assertTrue(out.size() <= MAX_PACKET_PAYLOAD_LENGTH);
-	}
-
 	@Test
 	public void testGroupsFitIntoSubscriptionUpdate() throws Exception {
 		// Create the maximum number of maximum-length groups
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();
diff --git a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
index 3cffb9feb6d3464170fe07b265c4be4b828c1d49..983946f22e634a51c405c6dee458b42d79ea9618 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
@@ -52,12 +52,6 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 		context.checking(new Expectations() {{
 			// Add listener
 			oneOf(eventBus).addListener(session);
-			// No transport acks to send
-			oneOf(db).generateTransportAcks(contactId);
-			will(returnValue(null));
-			// No transport updates to send
-			oneOf(db).generateTransportUpdates(contactId, maxLatency);
-			will(returnValue(null));
 			// No subscription ack to send
 			oneOf(db).generateSubscriptionAck(contactId);
 			will(returnValue(null));
@@ -92,12 +86,6 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 		context.checking(new Expectations() {{
 			// Add listener
 			oneOf(eventBus).addListener(session);
-			// No transport acks to send
-			oneOf(db).generateTransportAcks(contactId);
-			will(returnValue(null));
-			// No transport updates to send
-			oneOf(db).generateTransportUpdates(contactId, maxLatency);
-			will(returnValue(null));
 			// No subscription ack to send
 			oneOf(db).generateSubscriptionAck(contactId);
 			will(returnValue(null));