diff --git a/briar-api/src/org/briarproject/api/contact/ContactManager.java b/briar-api/src/org/briarproject/api/contact/ContactManager.java
index a40a2b08c456db34f9b432795c2dfd01449fa54d..5573e3ea4591d342f8e33922903997e850c9eb38 100644
--- a/briar-api/src/org/briarproject/api/contact/ContactManager.java
+++ b/briar-api/src/org/briarproject/api/contact/ContactManager.java
@@ -45,11 +45,11 @@ public interface ContactManager {
 	void setContactActive(ContactId c, boolean active) throws DbException;
 
 	/** Return true if a contact with this name and public key already exists */
-	boolean contactExists(Transaction txn, AuthorId remoteAuthorID,
+	boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
 			AuthorId localAuthorId) throws DbException;
 
 	/** Return true if a contact with this name and public key already exists */
-	boolean contactExists(AuthorId remoteAuthorID, AuthorId localAuthorId)
+	boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
 			throws DbException;
 
 	interface AddContactHook {
diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
index 64bc309830b2f18ea2e17d8f115a842c2970b6fe..1ccf2d48053a8f14e2151202982b580493370e71 100644
--- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
+++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
@@ -23,7 +23,6 @@ public interface IntroductionConstants {
 	String MSG = "msg";
 	String ACCEPT = "accept";
 	String TIME = "time";
-	String DEVICE_ID = "deviceId";
 	String TRANSPORT = "transport";
 	String MESSAGE_ID = "messageId";
 	String MESSAGE_TIME = "timestamp";
diff --git a/briar-api/src/org/briarproject/api/plugins/ConnectionRegistry.java b/briar-api/src/org/briarproject/api/plugins/ConnectionRegistry.java
index 6de3d00c5b55e34b4ca4a364a6056f4a510fa6cd..dc9376195387a5da851a9aab37a67707bc470e1c 100644
--- a/briar-api/src/org/briarproject/api/plugins/ConnectionRegistry.java
+++ b/briar-api/src/org/briarproject/api/plugins/ConnectionRegistry.java
@@ -16,5 +16,7 @@ public interface ConnectionRegistry {
 
 	Collection<ContactId> getConnectedContacts(TransportId t);
 
+	boolean isConnected(ContactId c, TransportId t);
+
 	boolean isConnected(ContactId c);
 }
diff --git a/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java b/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java
index 445329ba95a6100c1def5d4a9f87363c5b3a3f94..8b63ae612bc4705e779b44e503e57e810fed4ccf 100644
--- a/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java
+++ b/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java
@@ -1,6 +1,5 @@
 package org.briarproject.api.properties;
 
-import org.briarproject.api.DeviceId;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
@@ -14,7 +13,7 @@ public interface TransportPropertyManager {
 	 * Stores the given properties received while adding a contact - they will
 	 * be superseded by any properties synced from the contact.
 	 */
-	void addRemoteProperties(Transaction txn, ContactId c, DeviceId dev,
+	void addRemoteProperties(Transaction txn, ContactId c,
 			Map<TransportId, TransportProperties> props) throws DbException;
 
 	/** Returns the local transport properties for all transports. */
diff --git a/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java b/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java
index 943d02de98598359dedbf1767411a6334b8ed504..8fa1501816cb8771763d765995f0ec283fb14421 100644
--- a/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java
@@ -10,17 +10,22 @@ import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyParser;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.crypto.Signature;
+import org.briarproject.api.data.BdfList;
 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.db.ContactExistsException;
+import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.properties.TransportProperties;
+import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.StreamReaderFactory;
 import org.briarproject.api.transport.StreamWriterFactory;
@@ -29,13 +34,19 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
+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.TransportId.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;
+import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
+import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
 
 public class ContactExchangeTaskImpl extends Thread
 		implements ContactExchangeTask {
@@ -43,35 +54,40 @@ public class ContactExchangeTaskImpl extends Thread
 	private static final Logger LOG =
 			Logger.getLogger(ContactExchangeTaskImpl.class.getName());
 
+	private final DatabaseComponent db;
 	private final AuthorFactory authorFactory;
 	private final BdfReaderFactory bdfReaderFactory;
 	private final BdfWriterFactory bdfWriterFactory;
 	private final Clock clock;
 	private final ConnectionManager connectionManager;
 	private final ContactManager contactManager;
+	private final TransportPropertyManager transportPropertyManager;
 	private final CryptoComponent crypto;
 	private final StreamReaderFactory streamReaderFactory;
 	private final StreamWriterFactory streamWriterFactory;
 
-	private ContactExchangeListener listener;
-	private LocalAuthor localAuthor;
-	private DuplexTransportConnection conn;
-	private TransportId transportId;
-	private SecretKey masterSecret;
-	private boolean alice;
+	private volatile ContactExchangeListener listener;
+	private volatile LocalAuthor localAuthor;
+	private volatile DuplexTransportConnection conn;
+	private volatile TransportId transportId;
+	private volatile SecretKey masterSecret;
+	private volatile boolean alice;
 
-	public ContactExchangeTaskImpl(AuthorFactory authorFactory,
-			BdfReaderFactory bdfReaderFactory,
+	public ContactExchangeTaskImpl(DatabaseComponent db,
+			AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, Clock clock,
 			ConnectionManager connectionManager, ContactManager contactManager,
+			TransportPropertyManager transportPropertyManager,
 			CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory) {
+		this.db = db;
 		this.authorFactory = authorFactory;
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.bdfWriterFactory = bdfWriterFactory;
 		this.clock = clock;
 		this.connectionManager = connectionManager;
 		this.contactManager = contactManager;
+		this.transportPropertyManager = transportPropertyManager;
 		this.crypto = crypto;
 		this.streamReaderFactory = streamReaderFactory;
 		this.streamWriterFactory = streamWriterFactory;
@@ -93,24 +109,12 @@ public class ContactExchangeTaskImpl extends Thread
 
 	@Override
 	public void run() {
-		// Derive the header keys for the transport streams
-		SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
-		SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
-		BdfReader r;
-		BdfWriter w;
+		// Get the transport connection's input and output streams
+		InputStream in;
+		OutputStream out;
 		try {
-			// Create the readers
-			InputStream streamReader =
-					streamReaderFactory.createInvitationStreamReader(
-							conn.getReader().getInputStream(),
-							alice ? bobHeaderKey : aliceHeaderKey);
-			r = bdfReaderFactory.createReader(streamReader);
-			// Create the writers
-			OutputStream streamWriter =
-					streamWriterFactory.createInvitationStreamWriter(
-							conn.getWriter().getOutputStream(),
-							alice ? aliceHeaderKey : bobHeaderKey);
-			w = bdfWriterFactory.createWriter(streamWriter);
+			in = conn.getReader().getInputStream();
+			out = conn.getWriter().getOutputStream();
 		} catch (IOException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			listener.contactExchangeFailed();
@@ -118,6 +122,32 @@ public class ContactExchangeTaskImpl extends Thread
 			return;
 		}
 
+		// Get the local transport properties
+		Map<TransportId, TransportProperties> localProperties, remoteProperties;
+		try {
+			localProperties = transportPropertyManager.getLocalProperties();
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			listener.contactExchangeFailed();
+			tryToClose(conn, true);
+			return;
+		}
+
+		// Derive the header keys for the transport streams
+		SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
+		SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
+
+		// Create the readers
+		InputStream streamReader =
+				streamReaderFactory.createInvitationStreamReader(in,
+						alice ? bobHeaderKey : aliceHeaderKey);
+		BdfReader r = bdfReaderFactory.createReader(streamReader);
+		// Create the writers
+		OutputStream streamWriter =
+				streamWriterFactory.createInvitationStreamWriter(out,
+						alice ? aliceHeaderKey : bobHeaderKey);
+		BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
+
 		// Derive the nonces to be signed
 		byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
 		byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
@@ -130,13 +160,19 @@ public class ContactExchangeTaskImpl extends Thread
 			if (alice) {
 				sendPseudonym(w, aliceNonce);
 				sendTimestamp(w, localTimestamp);
+				sendTransportProperties(w, localProperties);
+				w.flush();
 				remoteAuthor = receivePseudonym(r, bobNonce);
 				remoteTimestamp = receiveTimestamp(r);
+				remoteProperties = receiveTransportProperties(r);
 			} else {
 				remoteAuthor = receivePseudonym(r, aliceNonce);
 				remoteTimestamp = receiveTimestamp(r);
+				remoteProperties = receiveTransportProperties(r);
 				sendPseudonym(w, bobNonce);
 				sendTimestamp(w, localTimestamp);
+				sendTransportProperties(w, localProperties);
+				w.flush();
 			}
 			// Close the outgoing stream and expect EOF on the incoming stream
 			w.close();
@@ -159,7 +195,7 @@ public class ContactExchangeTaskImpl extends Thread
 		try {
 			// Add the contact
 			ContactId contactId = addContact(remoteAuthor, masterSecret,
-					timestamp, alice);
+					timestamp, alice, remoteProperties);
 			// Reuse the connection as a transport connection
 			connectionManager.manageOutgoingConnection(contactId, transportId,
 					conn);
@@ -187,19 +223,22 @@ public class ContactExchangeTaskImpl extends Thread
 		signature.update(nonce);
 		byte[] sig = signature.sign();
 		// Write the name, public key and signature
+		w.writeListStart();
 		w.writeString(localAuthor.getName());
 		w.writeRaw(localAuthor.getPublicKey());
 		w.writeRaw(sig);
-		w.flush();
+		w.writeListEnd();
 		LOG.info("Sent pseudonym");
 	}
 
 	private Author receivePseudonym(BdfReader r, byte[] nonce)
 			throws GeneralSecurityException, IOException {
 		// Read the name, public key and signature
+		r.readListStart();
 		String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
 		byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
 		byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
+		r.readListEnd();
 		LOG.info("Received pseudonym");
 		// Verify the signature
 		Signature signature = crypto.getSignature();
@@ -217,7 +256,6 @@ public class ContactExchangeTaskImpl extends Thread
 	private void sendTimestamp(BdfWriter w, long timestamp)
 			throws IOException {
 		w.writeLong(timestamp);
-		w.flush();
 		LOG.info("Sent timestamp");
 	}
 
@@ -228,11 +266,56 @@ public class ContactExchangeTaskImpl extends Thread
 		return timestamp;
 	}
 
+	private void sendTransportProperties(BdfWriter w,
+			Map<TransportId, TransportProperties> local) throws IOException {
+		w.writeListStart();
+		for (Entry<TransportId, TransportProperties> e : local.entrySet())
+			w.writeList(BdfList.of(e.getKey().getString(), e.getValue()));
+		w.writeListEnd();
+	}
+
+	private Map<TransportId, TransportProperties> receiveTransportProperties(
+			BdfReader r) throws IOException {
+		Map<TransportId, TransportProperties> remote =
+				new HashMap<TransportId, TransportProperties>();
+		r.readListStart();
+		while (!r.hasListEnd()) {
+			r.readListStart();
+			String id = r.readString(MAX_TRANSPORT_ID_LENGTH);
+			if (id.isEmpty()) throw new FormatException();
+			TransportProperties p = new TransportProperties();
+			r.readDictionaryStart();
+			while (!r.hasDictionaryEnd()) {
+				if (p.size() == 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();
+			r.readListEnd();
+			remote.put(new TransportId(id), p);
+		}
+		r.readListEnd();
+		return remote;
+	}
+
 	private ContactId addContact(Author remoteAuthor, SecretKey master,
-			long timestamp, boolean alice) throws DbException {
-		// Add the contact to the database
-		return contactManager.addContact(remoteAuthor, localAuthor.getId(),
-				master, timestamp, alice, true);
+			long timestamp, boolean alice,
+			Map<TransportId, TransportProperties> remoteProperties)
+			throws DbException {
+		ContactId contactId;
+		Transaction txn = db.startTransaction(false);
+		try {
+			contactId = contactManager.addContact(txn, remoteAuthor,
+					localAuthor.getId(), master, timestamp, alice, true);
+			transportPropertyManager.addRemoteProperties(txn, contactId,
+					remoteProperties);
+			txn.setComplete();
+		} finally {
+			db.endTransaction(txn);
+		}
+		return contactId;
 	}
 
 	private void tryToClose(DuplexTransportConnection conn,
diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
index 23ec838802d8ad75c512e970720a185b566e254c..5c5c81d1cc85d8c54ddf30b604f1e40a8a0188f6 100644
--- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
@@ -126,18 +126,18 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 	}
 
 	@Override
-	public boolean contactExists(Transaction txn, AuthorId remoteAuthorID,
+	public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
 			AuthorId localAuthorId) throws DbException {
-		return db.containsContact(txn, remoteAuthorID, localAuthorId);
+		return db.containsContact(txn, remoteAuthorId, localAuthorId);
 	}
 
 	@Override
-	public boolean contactExists(AuthorId remoteAuthorID,
+	public boolean contactExists(AuthorId remoteAuthorId,
 			AuthorId localAuthorId) throws DbException {
 		boolean exists = false;
 		Transaction txn = db.startTransaction(true);
 		try {
-			exists = contactExists(txn, remoteAuthorID, localAuthorId);
+			exists = contactExists(txn, remoteAuthorId, localAuthorId);
 			txn.setComplete();
 		} finally {
 			db.endTransaction(txn);
diff --git a/briar-core/src/org/briarproject/contact/ContactModule.java b/briar-core/src/org/briarproject/contact/ContactModule.java
index fb5c290ce2d3ad0915f57a8e44a7c6d71d4b0aec..2cdf6079eddc4bba0cb54911ce1461d3c3522375 100644
--- a/briar-core/src/org/briarproject/contact/ContactModule.java
+++ b/briar-core/src/org/briarproject/contact/ContactModule.java
@@ -5,10 +5,11 @@ import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.BdfWriterFactory;
+import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.plugins.ConnectionManager;
+import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.StreamReaderFactory;
 import org.briarproject.api.transport.StreamWriterFactory;
@@ -28,22 +29,23 @@ public class ContactModule {
 
 	@Provides
 	@Singleton
-	ContactManager getContactManager(LifecycleManager lifecycleManager,
-			IdentityManager identityManager,
+	ContactManager getContactManager(IdentityManager identityManager,
 			ContactManagerImpl contactManager) {
 		identityManager.registerRemoveIdentityHook(contactManager);
 		return contactManager;
 	}
 
 	@Provides
-	ContactExchangeTask provideContactExchangeTask(
+	ContactExchangeTask provideContactExchangeTask(DatabaseComponent db,
 			AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, Clock clock,
 			ConnectionManager connectionManager, ContactManager contactManager,
+			TransportPropertyManager transportPropertyManager,
 			CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory) {
-		return new ContactExchangeTaskImpl(authorFactory, bdfReaderFactory,
+		return new ContactExchangeTaskImpl(db, authorFactory, bdfReaderFactory,
 				bdfWriterFactory, clock, connectionManager, contactManager,
-				crypto, streamReaderFactory, streamWriterFactory);
+				transportPropertyManager, crypto, streamReaderFactory,
+				streamWriterFactory);
 	}
 }
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 536161562462bc9ce06144418701eb11d4b78510..475dbdc2ff87447739ffdd372dec1b82c4cb45ab 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -63,8 +63,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
  */
 abstract class JdbcDatabase implements Database<Connection> {
 
-	private static final int SCHEMA_VERSION = 22;
-	private static final int MIN_SCHEMA_VERSION = 22;
+	private static final int SCHEMA_VERSION = 23;
+	private static final int MIN_SCHEMA_VERSION = 23;
 
 	private static final String CREATE_SETTINGS =
 			"CREATE TABLE settings"
diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java
index 7be3184086617a1e34fff0aab621b3f390e3fb3f..6f76540d081a4ef7baf314ead7d9709eccb4c664 100644
--- a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java
+++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java
@@ -34,7 +34,6 @@ import static org.briarproject.api.introduction.IntroduceeProtocolState.FINISHED
 import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.api.introduction.IntroductionConstants.ANSWERED;
 import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_1;
-import static org.briarproject.api.introduction.IntroductionConstants.DEVICE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.EXISTS;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
@@ -108,7 +107,6 @@ public class IntroduceeEngine
 				if (localState.getBoolean(ACCEPT)) {
 					msg.put(TIME, localState.getLong(OUR_TIME));
 					msg.put(E_PUBLIC_KEY, localState.getRaw(OUR_PUBLIC_KEY));
-					msg.put(DEVICE_ID, localAction.getRaw(DEVICE_ID));
 					msg.put(TRANSPORT, localAction.getDictionary(TRANSPORT));
 				}
 				messages.add(msg);
@@ -231,7 +229,6 @@ public class IntroduceeEngine
 		if (msg.getBoolean(ACCEPT)) {
 			localState.put(TIME, msg.getLong(TIME));
 			localState.put(E_PUBLIC_KEY, msg.getRaw(E_PUBLIC_KEY));
-			localState.put(DEVICE_ID, msg.getRaw(DEVICE_ID));
 			localState.put(TRANSPORT, msg.getDictionary(TRANSPORT));
 		}
 	}
diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java
index 2049fba3ff5d636ee2acb8b48090184b3b77c715..be16ff1e495206ec06006de66e8e4708b808a19c 100644
--- a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java
+++ b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java
@@ -2,7 +2,6 @@ package org.briarproject.introduction;
 
 
 import org.briarproject.api.Bytes;
-import org.briarproject.api.DeviceId;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.clients.ClientHelper;
@@ -47,7 +46,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.ADDED_CONT
 import static org.briarproject.api.introduction.IntroductionConstants.ANSWERED;
 import static org.briarproject.api.introduction.IntroductionConstants.CONTACT;
 import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_1;
-import static org.briarproject.api.introduction.IntroductionConstants.DEVICE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.EXISTS;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
@@ -167,7 +165,6 @@ class IntroduceeManager {
 
 		// get data to connect and derive a shared secret later
 		long now = clock.currentTimeMillis();
-		byte[] deviceId = db.getDeviceId(txn).getBytes();
 		KeyPair keyPair = cryptoComponent.generateAgreementKeyPair();
 		byte[] publicKey = keyPair.getPublic().getEncoded();
 		byte[] privateKey = keyPair.getPrivate().getEncoded();
@@ -183,14 +180,12 @@ class IntroduceeManager {
 		// define action
 		BdfDictionary localAction = new BdfDictionary();
 		localAction.put(TYPE, TYPE_RESPONSE);
-		localAction.put(DEVICE_ID, deviceId);
 		localAction.put(TRANSPORT,
 				encodeTransportProperties(transportProperties));
 
 		// start engine and process its state update
 		IntroduceeEngine engine = new IntroduceeEngine();
-		processStateUpdate(txn,
-				engine.onLocalAction(state, localAction));
+		processStateUpdate(txn, engine.onLocalAction(state, localAction));
 	}
 
 	public void declineIntroduction(Transaction txn, final SessionId sessionId)
@@ -313,11 +308,10 @@ class IntroduceeManager {
 			localState.put(ADDED_CONTACT_ID, contactId.getInt());
 
 			// let the transport manager know how to connect to the contact
-			DeviceId deviceId = new DeviceId(localState.getRaw(DEVICE_ID));
 			Map<TransportId, TransportProperties> transportProperties =
 					parseTransportProperties(localState);
 			transportPropertyManager.addRemoteProperties(txn, contactId,
-					deviceId, transportProperties);
+					transportProperties);
 
 			// delete the ephemeral private key by overwriting with NULL value
 			// this ensures future ephemeral keys can not be recovered when
diff --git a/briar-core/src/org/briarproject/introduction/IntroducerManager.java b/briar-core/src/org/briarproject/introduction/IntroducerManager.java
index 1b35fc26c0363c8ccbde45b93accb2cdf165e30a..3cc906aedf32c085c132acd818a1152a773f19e2 100644
--- a/briar-core/src/org/briarproject/introduction/IntroducerManager.java
+++ b/briar-core/src/org/briarproject/introduction/IntroducerManager.java
@@ -20,8 +20,6 @@ import org.briarproject.util.StringUtils;
 import java.io.IOException;
 import java.util.logging.Logger;
 
-import javax.inject.Inject;
-
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
 import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_1;
diff --git a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
index addf58ea49749522873ff662e506e42055d42dfa..b8af1be12f7e224b379ad7016f4a2e47bdde81cf 100644
--- a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
+++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
@@ -1,8 +1,6 @@
 package org.briarproject.introduction;
 
-import org.briarproject.api.DeviceId;
 import org.briarproject.api.FormatException;
-import org.briarproject.api.TransportId;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
@@ -13,10 +11,10 @@ import org.briarproject.api.sync.Message;
 import org.briarproject.api.system.Clock;
 import org.briarproject.clients.BdfMessageValidator;
 
+import static org.briarproject.api.TransportId.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.introduction.IntroductionConstants.ACCEPT;
-import static org.briarproject.api.introduction.IntroductionConstants.DEVICE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
@@ -31,6 +29,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
+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.MAX_MESSAGE_BODY_LENGTH;
 
@@ -102,17 +101,16 @@ class IntroductionValidator extends BdfMessageValidator {
 	private BdfDictionary validateResponse(BdfList message)
 			throws FormatException {
 
-		checkSize(message, 3, 7);
+		checkSize(message, 3, 6);
 
 		// parse accept/decline
 		boolean accept = message.getBoolean(2);
 
 		long time = 0;
 		byte[] pubkey = null;
-		byte[] deviceId = null;
 		BdfDictionary tp = new BdfDictionary();
 		if (accept) {
-			checkSize(message, 7);
+			checkSize(message, 6);
 
 			// parse timestamp
 			time = message.getLong(3);
@@ -121,16 +119,13 @@ class IntroductionValidator extends BdfMessageValidator {
 			pubkey = message.getRaw(4);
 			checkLength(pubkey, 0, MAX_PUBLIC_KEY_LENGTH);
 
-			// parse device ID
-			deviceId = message.getRaw(5);
-			checkLength(deviceId, DeviceId.LENGTH);
-
 			// parse transport properties
-			tp = message.getDictionary(6);
+			tp = message.getDictionary(5);
 			if (tp.size() < 1) throw new FormatException();
 			for (String tId : tp.keySet()) {
-				checkLength(tId, 1, TransportId.MAX_TRANSPORT_ID_LENGTH);
+				checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
 				BdfDictionary tProps = tp.getDictionary(tId);
+				checkSize(tProps, MAX_PROPERTIES_PER_TRANSPORT);
 				for (String propId : tProps.keySet()) {
 					checkLength(propId, 0, MAX_PROPERTY_LENGTH);
 					String prop = tProps.getString(propId);
@@ -147,7 +142,6 @@ class IntroductionValidator extends BdfMessageValidator {
 		if (accept) {
 			d.put(TIME, time);
 			d.put(E_PUBLIC_KEY, pubkey);
-			d.put(DEVICE_ID, deviceId);
 			d.put(TRANSPORT, tp);
 		}
 		return d;
diff --git a/briar-core/src/org/briarproject/introduction/MessageEncoder.java b/briar-core/src/org/briarproject/introduction/MessageEncoder.java
index 87bf4d5e6a4e63a0a5d35f793d7e30b653020437..12153122cd51323d3975cfcbcc38b48fac39c15a 100644
--- a/briar-core/src/org/briarproject/introduction/MessageEncoder.java
+++ b/briar-core/src/org/briarproject/introduction/MessageEncoder.java
@@ -5,7 +5,6 @@ import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 
 import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
-import static org.briarproject.api.introduction.IntroductionConstants.DEVICE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
@@ -21,7 +20,8 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPO
 
 public class MessageEncoder {
 
-	public static BdfList encodeMessage(BdfDictionary d) throws FormatException {
+	public static BdfList encodeMessage(BdfDictionary d)
+			throws FormatException {
 
 		BdfList body;
 		long type = d.getLong(TYPE);
@@ -39,7 +39,8 @@ public class MessageEncoder {
 		return body;
 	}
 
-	private static BdfList encodeRequest(BdfDictionary d) throws FormatException {
+	private static BdfList encodeRequest(BdfDictionary d)
+			throws FormatException {
 		BdfList list = BdfList.of(TYPE_REQUEST, d.getRaw(SESSION_ID),
 				d.getString(NAME), d.getRaw(PUBLIC_KEY));
 
@@ -49,14 +50,14 @@ public class MessageEncoder {
 		return list;
 	}
 
-	private static BdfList encodeResponse(BdfDictionary d) throws FormatException {
+	private static BdfList encodeResponse(BdfDictionary d)
+			throws FormatException {
 		BdfList list = BdfList.of(TYPE_RESPONSE, d.getRaw(SESSION_ID),
 				d.getBoolean(ACCEPT));
 
 		if (d.getBoolean(ACCEPT)) {
 			list.add(d.getLong(TIME));
 			list.add(d.getRaw(E_PUBLIC_KEY));
-			list.add(d.getRaw(DEVICE_ID));
 			list.add(d.getDictionary(TRANSPORT));
 		}
 		// TODO Sign the response, see #256
diff --git a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
index 2eb502cf1e929aa30f9dbed84d11a1ee6d069328..3c9f5fb48ed3d792a8180ae93bd5e8cc1e04e0eb 100644
--- a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
@@ -191,7 +191,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 			}
 			if (ctx == null) {
 				LOG.warning("Could not allocate stream context");
-				disposeWriter(false);
+				disposeWriter(true);
 				return;
 			}
 			connectionRegistry.registerConnection(contactId, transportId);
@@ -286,7 +286,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 			}
 			if (ctx == null) {
 				LOG.warning("Could not allocate stream context");
-				disposeWriter(false);
+				disposeWriter(true);
 				return;
 			}
 			try {
@@ -351,10 +351,9 @@ class ConnectionManagerImpl implements ConnectionManager {
 			}
 			if (ctx == null) {
 				LOG.warning("Could not allocate stream context");
-				disposeWriter(false);
+				disposeWriter(true);
 				return;
 			}
-			connectionRegistry.registerConnection(contactId, transportId);
 			// Start the incoming session on another thread
 			ioExecutor.execute(new Runnable() {
 				public void run() {
@@ -369,8 +368,6 @@ class ConnectionManagerImpl implements ConnectionManager {
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeWriter(true);
-			} finally {
-				connectionRegistry.unregisterConnection(contactId, transportId);
 			}
 		}
 
@@ -382,17 +379,17 @@ class ConnectionManagerImpl implements ConnectionManager {
 				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				disposeReader(true, true);
+				disposeReader(true, false);
 				return;
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				disposeReader(true, true);
+				disposeReader(true, false);
 				return;
 			}
 			// Unrecognised tags are suspicious in this case
 			if (ctx == null) {
 				LOG.warning("Unrecognised tag for returning stream");
-				disposeReader(true, true);
+				disposeReader(true, false);
 				return;
 			}
 			// Check that the stream comes from the expected contact
@@ -401,6 +398,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 				disposeReader(true, true);
 				return;
 			}
+			connectionRegistry.registerConnection(contactId, transportId);
 			try {
 				// Create and run the incoming session
 				incomingSession = createIncomingSession(ctx, reader);
@@ -409,6 +407,8 @@ class ConnectionManagerImpl implements ConnectionManager {
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, true);
+			} finally {
+				connectionRegistry.unregisterConnection(contactId, transportId);
 			}
 		}
 
diff --git a/briar-core/src/org/briarproject/plugins/ConnectionRegistryImpl.java b/briar-core/src/org/briarproject/plugins/ConnectionRegistryImpl.java
index 86cfab5a18b5fdc7d3908af341c274cdbc6d453f..0fe9c3b4b4cae11e0f407d61498acd62a89b9ff9 100644
--- a/briar-core/src/org/briarproject/plugins/ConnectionRegistryImpl.java
+++ b/briar-core/src/org/briarproject/plugins/ConnectionRegistryImpl.java
@@ -41,7 +41,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 	}
 
 	public void registerConnection(ContactId c, TransportId t) {
-		LOG.info("Connection registered");
+		if (LOG.isLoggable(INFO)) LOG.info("Connection registered: " + t);
 		boolean firstConnection = false;
 		lock.lock();
 		try {
@@ -63,7 +63,6 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 		} finally {
 			lock.unlock();
 		}
-
 		if (firstConnection) {
 			LOG.info("Contact connected");
 			eventBus.broadcast(new ContactConnectedEvent(c));
@@ -71,7 +70,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 	}
 
 	public void unregisterConnection(ContactId c, TransportId t) {
-		LOG.info("Connection unregistered");
+		if (LOG.isLoggable(INFO)) LOG.info("Connection unregistered: " + t);
 		boolean lastConnection = false;
 		lock.lock();
 		try {
@@ -95,15 +94,13 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 		} finally {
 			lock.unlock();
 		}
-
 		if (lastConnection) {
 			LOG.info("Contact disconnected");
 			eventBus.broadcast(new ContactDisconnectedEvent(c));
 		}
 	}
 
-	public Collection<ContactId> getConnectedContacts(
-			TransportId t) {
+	public Collection<ContactId> getConnectedContacts(TransportId t) {
 		lock.lock();
 		try {
 			Map<ContactId, Integer> m = connections.get(t);
@@ -115,7 +112,16 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 		} finally {
 			lock.unlock();
 		}
+	}
 
+	public boolean isConnected(ContactId c, TransportId t) {
+		lock.lock();
+		try {
+			Map<ContactId, Integer> m = connections.get(t);
+			return m != null && m.containsKey(c);
+		} finally {
+			lock.unlock();
+		}
 	}
 
 	public boolean isConnected(ContactId c) {
@@ -125,6 +131,5 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 		} finally {
 			lock.unlock();
 		}
-
 	}
 }
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index 9f2f85b978f47a876d0b6701f0cca2d017b756a5..4eb8da75b93907cb3e5ef1a03fbea07f9e74d21c 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -3,13 +3,17 @@ package org.briarproject.plugins;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.ContactStatusChangedEvent;
+import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.TransportDisabledEvent;
 import org.briarproject.api.event.TransportEnabledEvent;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.lifecycle.ServiceException;
 import org.briarproject.api.plugins.ConnectionManager;
+import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginCallback;
 import org.briarproject.api.plugins.PluginConfig;
@@ -46,7 +50,7 @@ import javax.inject.Inject;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-class PluginManagerImpl implements PluginManager, Service {
+class PluginManagerImpl implements PluginManager, Service, EventListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(PluginManagerImpl.class.getName());
@@ -56,6 +60,7 @@ class PluginManagerImpl implements PluginManager, Service {
 	private final PluginConfig pluginConfig;
 	private final Poller poller;
 	private final ConnectionManager connectionManager;
+	private final ConnectionRegistry connectionRegistry;
 	private final SettingsManager settingsManager;
 	private final TransportPropertyManager transportPropertyManager;
 	private final UiCallback uiCallback;
@@ -67,6 +72,7 @@ class PluginManagerImpl implements PluginManager, Service {
 	PluginManagerImpl(@IoExecutor Executor ioExecutor, EventBus eventBus,
 			PluginConfig pluginConfig, Poller poller,
 			ConnectionManager connectionManager,
+			ConnectionRegistry connectionRegistry,
 			SettingsManager settingsManager,
 			TransportPropertyManager transportPropertyManager,
 			UiCallback uiCallback) {
@@ -75,6 +81,7 @@ class PluginManagerImpl implements PluginManager, Service {
 		this.pluginConfig = pluginConfig;
 		this.poller = poller;
 		this.connectionManager = connectionManager;
+		this.connectionRegistry = connectionRegistry;
 		this.settingsManager = settingsManager;
 		this.transportPropertyManager = transportPropertyManager;
 		this.uiCallback = uiCallback;
@@ -106,10 +113,14 @@ class PluginManagerImpl implements PluginManager, Service {
 		} catch (InterruptedException e) {
 			throw new ServiceException(e);
 		}
+		// Listen for events
+		eventBus.addListener(this);
 	}
 
 	@Override
 	public void stopService() throws ServiceException {
+		// Stop listening for events
+		eventBus.removeListener(this);
 		// Stop the poller
 		LOG.info("Stopping poller");
 		poller.stop();
@@ -122,9 +133,6 @@ class PluginManagerImpl implements PluginManager, Service {
 		LOG.info("Stopping duplex plugins");
 		for (DuplexPlugin plugin : duplexPlugins)
 			ioExecutor.execute(new PluginStopper(plugin, latch));
-		plugins.clear();
-		simplexPlugins.clear();
-		duplexPlugins.clear();
 		// Wait for all the plugins to stop
 		try {
 			latch.await();
@@ -151,6 +159,47 @@ class PluginManagerImpl implements PluginManager, Service {
 		return Collections.unmodifiableList(supported);
 	}
 
+	@Override
+	public void eventOccurred(Event e) {
+		if (e instanceof ContactStatusChangedEvent) {
+			ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
+			if (c.isActive()) connectToContact(c.getContactId());
+		}
+	}
+
+	private void connectToContact(ContactId c) {
+		for (SimplexPlugin s : simplexPlugins)
+			if (s.shouldPoll()) connectToContact(c, s);
+		for (DuplexPlugin d : duplexPlugins)
+			if (d.shouldPoll()) connectToContact(c, d);
+	}
+
+	private void connectToContact(final ContactId c, final SimplexPlugin p) {
+		ioExecutor.execute(new Runnable() {
+			public void run() {
+				TransportId t = p.getId();
+				if (!connectionRegistry.isConnected(c, t)) {
+					TransportConnectionWriter w = p.createWriter(c);
+					if (w != null)
+						connectionManager.manageOutgoingConnection(c, t, w);
+				}
+			}
+		});
+	}
+
+	private void connectToContact(final ContactId c, final DuplexPlugin p) {
+		ioExecutor.execute(new Runnable() {
+			public void run() {
+				TransportId t = p.getId();
+				if (!connectionRegistry.isConnected(c, t)) {
+					DuplexTransportConnection d = p.createConnection(c);
+					if (d != null)
+						connectionManager.manageOutgoingConnection(c, t, d);
+				}
+			}
+		});
+	}
+
 	private class SimplexPluginStarter implements Runnable {
 
 		private final SimplexPluginFactory factory;
diff --git a/briar-core/src/org/briarproject/plugins/PollerImpl.java b/briar-core/src/org/briarproject/plugins/PollerImpl.java
index 50e79b3b27f77349908628121cd73597a6aee637..938997a7646d573188bd8716871c5196e10347b1 100644
--- a/briar-core/src/org/briarproject/plugins/PollerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PollerImpl.java
@@ -45,12 +45,13 @@ class PollerImpl implements Poller {
 
 	public void addPlugin(Plugin p) {
 		// Randomise first polling interval
-		schedule(p, randomise(p.getPollingInterval()), false);
+		if (p.shouldPoll())
+			schedule(p, randomise(p.getPollingInterval()), false);
 	}
 
 	public void pollNow(Plugin p) {
 		// Randomise next polling interval
-		schedule(p, 0, true);
+		if (p.shouldPoll()) schedule(p, 0, true);
 	}
 
 	private int randomise(int interval) {
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
index f393e3eaa29f9c49a88df097e189797d11e5d18c..5663b6151a823399eaf800f8031bdf2dfd281ea8 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -1,6 +1,5 @@
 package org.briarproject.properties;
 
-import org.briarproject.api.DeviceId;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.clients.Client;
@@ -73,10 +72,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 		db.addGroup(txn, g);
 		db.setVisibleToContact(txn, c.getId(), g.getId(), true);
 		// Copy the latest local properties into the group
-		DeviceId dev = db.getDeviceId(txn);
 		Map<TransportId, TransportProperties> local = getLocalProperties(txn);
 		for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
-			storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 1,
+			storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 1,
 					true, true);
 		}
 	}
@@ -87,11 +85,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	}
 
 	@Override
-	public void addRemoteProperties(Transaction txn, ContactId c, DeviceId dev,
+	public void addRemoteProperties(Transaction txn, ContactId c,
 			Map<TransportId, TransportProperties> props) throws DbException {
 		Group g = getContactGroup(db.getContact(txn, c));
 		for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
-			storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 0,
+			storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 0,
 					false, false);
 		}
 	}
@@ -189,16 +187,15 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 				}
 				if (changed) {
 					// Store the merged properties in the local group
-					DeviceId dev = db.getDeviceId(txn);
 					long version = latest == null ? 1 : latest.version + 1;
-					storeMessage(txn, localGroup.getId(), dev, t, merged,
-							version, true, false);
+					storeMessage(txn, localGroup.getId(), t, merged, version,
+							true, false);
 					// Store the merged properties in each contact's group
 					for (Contact c : db.getContacts(txn)) {
 						Group g = getContactGroup(c);
 						latest = findLatest(txn, g.getId(), t, true);
 						version = latest == null ? 1 : latest.version + 1;
-						storeMessage(txn, g.getId(), dev, t, merged, version,
+						storeMessage(txn, g.getId(), t, merged, version,
 								true, true);
 					}
 				}
@@ -235,11 +232,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 		}
 	}
 
-	private void storeMessage(Transaction txn, GroupId g, DeviceId dev,
-			TransportId t, TransportProperties p, long version, boolean local,
-			boolean shared) throws DbException {
+	private void storeMessage(Transaction txn, GroupId g, TransportId t,
+			TransportProperties p, long version, boolean local, boolean shared)
+			throws DbException {
 		try {
-			BdfList body = encodeProperties(dev, t, p, version);
+			BdfList body = encodeProperties(t, p, version);
 			long now = clock.currentTimeMillis();
 			Message m = clientHelper.createMessage(g, now, body);
 			BdfDictionary meta = new BdfDictionary();
@@ -252,9 +249,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 		}
 	}
 
-	private BdfList encodeProperties(DeviceId dev, TransportId t,
-			TransportProperties p, long version) {
-		return BdfList.of(dev, t.getString(), version, p);
+	private BdfList encodeProperties(TransportId t, TransportProperties p,
+			long version) {
+		return BdfList.of(t.getString(), version, p);
 	}
 
 	private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
@@ -295,8 +292,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 
 	private TransportProperties parseProperties(BdfList message)
 			throws FormatException {
-		// Device ID, transport ID, version, properties
-		BdfDictionary dictionary = message.getDictionary(3);
+		// Transport ID, version, properties
+		BdfDictionary dictionary = message.getDictionary(2);
 		TransportProperties p = new TransportProperties();
 		for (String key : dictionary.keySet())
 			p.put(key, dictionary.getString(key));
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
index b26775d7360abed07d1b69ec9a80b946f79071cb..fc40dd342a65ccc619fc7071119590270a66c6e4 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
@@ -1,7 +1,6 @@
 package org.briarproject.properties;
 
 import org.briarproject.api.FormatException;
-import org.briarproject.api.UniqueId;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
@@ -25,19 +24,16 @@ public class TransportPropertyValidator extends BdfMessageValidator {
 	@Override
 	protected BdfDictionary validateMessage(Message m, Group g,
 			BdfList body) throws FormatException {
-		// Device ID, transport ID, version, properties
-		checkSize(body, 4);
-		// Device ID
-		byte[] deviceId = body.getRaw(0);
-		checkLength(deviceId, UniqueId.LENGTH);
+		// Transport ID, version, properties
+		checkSize(body, 3);
 		// Transport ID
-		String transportId = body.getString(1);
+		String transportId = body.getString(0);
 		checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
 		// Version
-		long version = body.getLong(2);
+		long version = body.getLong(1);
 		if (version < 0) throw new FormatException();
 		// Properties
-		BdfDictionary dictionary = body.getDictionary(3);
+		BdfDictionary dictionary = body.getDictionary(2);
 		checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
 		for (String key : dictionary.keySet()) {
 			checkLength(key, 0, MAX_PROPERTY_LENGTH);
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index 872985ff15f92e9759349064cbe26c40dd4014f1..6627225c3ddf3417e92b3a2316d4d52baab1e6f9 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -1,13 +1,20 @@
 package org.briarproject.plugins;
 
 import org.briarproject.BriarTestCase;
+import org.briarproject.ImmediateExecutor;
 import org.briarproject.api.TransportId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.event.ContactStatusChangedEvent;
 import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.EventListener;
 import org.briarproject.api.plugins.ConnectionManager;
+import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.PluginConfig;
+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.DuplexPluginFactory;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
@@ -36,6 +43,8 @@ public class PluginManagerImplTest extends BriarTestCase {
 		final Poller poller = context.mock(Poller.class);
 		final ConnectionManager connectionManager =
 				context.mock(ConnectionManager.class);
+		final ConnectionRegistry connectionRegistry =
+				context.mock(ConnectionRegistry.class);
 		final SettingsManager settingsManager =
 				context.mock(SettingsManager.class);
 		final TransportPropertyManager transportPropertyManager =
@@ -63,6 +72,7 @@ public class PluginManagerImplTest extends BriarTestCase {
 		final TransportId duplexFailId = new TransportId("duplex1");
 
 		context.checking(new Expectations() {{
+			// start()
 			// First simplex plugin
 			oneOf(pluginConfig).getSimplexFactories();
 			will(returnValue(Arrays.asList(simplexFactory,
@@ -103,6 +113,11 @@ public class PluginManagerImplTest extends BriarTestCase {
 			oneOf(duplexFailFactory).createPlugin(with(any(
 					DuplexPluginCallback.class)));
 			will(returnValue(null)); // Failed to create a plugin
+			// Start listening for events
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
+			// stop()
+			// Stop listening for events
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			// Stop the poller
 			oneOf(poller).stop();
 			// Stop the plugins
@@ -111,8 +126,8 @@ public class PluginManagerImplTest extends BriarTestCase {
 		}});
 
 		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
-				pluginConfig, poller, connectionManager, settingsManager,
-				transportPropertyManager, uiCallback);
+				pluginConfig, poller, connectionManager, connectionRegistry,
+				settingsManager, transportPropertyManager, uiCallback);
 
 		// Two plugins should be started and stopped
 		p.startService();
@@ -120,4 +135,151 @@ public class PluginManagerImplTest extends BriarTestCase {
 
 		context.assertIsSatisfied();
 	}
+
+	@Test
+	public void testConnectToNewContact() throws Exception {
+		Mockery context = new Mockery();
+		final Executor ioExecutor = new ImmediateExecutor();
+		final EventBus eventBus = context.mock(EventBus.class);
+		final PluginConfig pluginConfig = context.mock(PluginConfig.class);
+		final Poller poller = context.mock(Poller.class);
+		final ConnectionManager connectionManager =
+				context.mock(ConnectionManager.class);
+		final ConnectionRegistry connectionRegistry =
+				context.mock(ConnectionRegistry.class);
+		final SettingsManager settingsManager =
+				context.mock(SettingsManager.class);
+		final TransportPropertyManager transportPropertyManager =
+				context.mock(TransportPropertyManager.class);
+		final UiCallback uiCallback = context.mock(UiCallback.class);
+		final TransportConnectionWriter transportConnectionWriter =
+				context.mock(TransportConnectionWriter.class);
+		final DuplexTransportConnection duplexTransportConnection =
+				context.mock(DuplexTransportConnection.class);
+
+		final ContactId contactId = new ContactId(234);
+
+		// Two simplex plugins: one supports polling, the other doesn't
+		final SimplexPluginFactory simplexFactory =
+				context.mock(SimplexPluginFactory.class);
+		final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
+		final TransportId simplexId = new TransportId("simplex");
+		final SimplexPluginFactory simplexFactory1 =
+				context.mock(SimplexPluginFactory.class, "simplexFactory1");
+		final SimplexPlugin simplexPlugin1 =
+				context.mock(SimplexPlugin.class, "simplexPlugin1");
+		final TransportId simplexId1 = new TransportId("simplex1");
+
+		// Two duplex plugins: one supports polling, the other doesn't
+		final DuplexPluginFactory duplexFactory =
+				context.mock(DuplexPluginFactory.class);
+		final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
+		final TransportId duplexId = new TransportId("duplex");
+		final DuplexPluginFactory duplexFactory1 =
+				context.mock(DuplexPluginFactory.class, "duplexFactory1");
+		final DuplexPlugin duplexPlugin1 =
+				context.mock(DuplexPlugin.class, "duplexPlugin1");
+		final TransportId duplexId1 = new TransportId("duplex1");
+
+		context.checking(new Expectations() {{
+			// start()
+			// First simplex plugin
+			oneOf(pluginConfig).getSimplexFactories();
+			will(returnValue(Arrays.asList(simplexFactory, simplexFactory1)));
+			oneOf(simplexFactory).getId();
+			will(returnValue(simplexId));
+			oneOf(simplexFactory).createPlugin(with(any(
+					SimplexPluginCallback.class)));
+			will(returnValue(simplexPlugin)); // Created
+			oneOf(simplexPlugin).start();
+			will(returnValue(true)); // Started
+			oneOf(simplexPlugin).shouldPoll();
+			will(returnValue(true)); // Should poll
+			oneOf(poller).addPlugin(simplexPlugin);
+			// Second simplex plugin
+			oneOf(simplexFactory1).getId();
+			will(returnValue(simplexId1));
+			oneOf(simplexFactory1).createPlugin(with(any(
+					SimplexPluginCallback.class)));
+			will(returnValue(simplexPlugin1)); // Created
+			oneOf(simplexPlugin1).start();
+			will(returnValue(true)); // Started
+			oneOf(simplexPlugin1).shouldPoll();
+			will(returnValue(false)); // Should not poll
+			// First duplex plugin
+			oneOf(pluginConfig).getDuplexFactories();
+			will(returnValue(Arrays.asList(duplexFactory, duplexFactory1)));
+			oneOf(duplexFactory).getId();
+			will(returnValue(duplexId));
+			oneOf(duplexFactory).createPlugin(with(any(
+					DuplexPluginCallback.class)));
+			will(returnValue(duplexPlugin)); // Created
+			oneOf(duplexPlugin).start();
+			will(returnValue(true)); // Started
+			oneOf(duplexPlugin).shouldPoll();
+			will(returnValue(true)); // Should poll
+			oneOf(poller).addPlugin(duplexPlugin);
+			// Second duplex plugin
+			oneOf(duplexFactory1).getId();
+			will(returnValue(duplexId1));
+			oneOf(duplexFactory1).createPlugin(with(any(
+					DuplexPluginCallback.class)));
+			will(returnValue(duplexPlugin1)); // Created
+			oneOf(duplexPlugin1).start();
+			will(returnValue(true)); // Started
+			oneOf(duplexPlugin1).shouldPoll();
+			will(returnValue(false)); // Should not poll
+			// Start listening for events
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
+			// eventOccurred()
+			// First simplex plugin
+			oneOf(simplexPlugin).shouldPoll();
+			will(returnValue(true));
+			oneOf(simplexPlugin).getId();
+			will(returnValue(simplexId));
+			oneOf(connectionRegistry).isConnected(contactId, simplexId);
+			will(returnValue(false));
+			oneOf(simplexPlugin).createWriter(contactId);
+			will(returnValue(transportConnectionWriter));
+			oneOf(connectionManager).manageOutgoingConnection(contactId,
+					simplexId, transportConnectionWriter);
+			// Second simplex plugin
+			oneOf(simplexPlugin1).shouldPoll();
+			will(returnValue(false));
+			// First duplex plugin
+			oneOf(duplexPlugin).shouldPoll();
+			will(returnValue(true));
+			oneOf(duplexPlugin).getId();
+			will(returnValue(duplexId));
+			oneOf(connectionRegistry).isConnected(contactId, duplexId);
+			will(returnValue(false));
+			oneOf(duplexPlugin).createConnection(contactId);
+			will(returnValue(duplexTransportConnection));
+			oneOf(connectionManager).manageOutgoingConnection(contactId,
+					duplexId, duplexTransportConnection);
+			// Second duplex plugin
+			oneOf(duplexPlugin1).shouldPoll();
+			will(returnValue(false));
+			// stop()
+			// Stop listening for events
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
+			// Stop the poller
+			oneOf(poller).stop();
+			// Stop the plugins
+			oneOf(simplexPlugin).stop();
+			oneOf(simplexPlugin1).stop();
+			oneOf(duplexPlugin).stop();
+			oneOf(duplexPlugin1).stop();
+		}});
+
+		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
+				pluginConfig, poller, connectionManager, connectionRegistry,
+				settingsManager, transportPropertyManager, uiCallback);
+
+		p.startService();
+		p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
+		p.stopService();
+
+		context.assertIsSatisfied();
+	}
 }
diff --git a/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java b/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java
index 94378cb0130a6d4c098e0d15d56b41e9bb334f7c..bc61508117b31fcb0a4425022c4122f5d50e3614 100644
--- a/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java
+++ b/briar-tests/src/org/briarproject/properties/TransportPropertyValidatorTest.java
@@ -2,10 +2,8 @@ package org.briarproject.properties;
 
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestUtils;
-import org.briarproject.api.DeviceId;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.UniqueId;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
@@ -28,110 +26,83 @@ import static org.junit.Assert.assertEquals;
 public class TransportPropertyValidatorTest extends BriarTestCase {
 
 	private final TransportId transportId;
-	private final DeviceId deviceId;
 	private final BdfDictionary bdfDictionary;
 	private final Group group;
 	private final Message message;
 	private final TransportPropertyValidator tpv;
 
-    public TransportPropertyValidatorTest() {
+	public TransportPropertyValidatorTest() {
 		transportId = new TransportId("test");
-	    deviceId = new DeviceId(TestUtils.getRandomId());
-	    bdfDictionary = new BdfDictionary();
+		bdfDictionary = new BdfDictionary();
 
-	    GroupId groupId = new GroupId(TestUtils.getRandomId());
-	    ClientId clientId = new ClientId(TestUtils.getRandomId());
-	    byte[] descriptor = TestUtils.getRandomBytes(12);
-	    group = new Group(groupId, clientId, descriptor);
+		GroupId groupId = new GroupId(TestUtils.getRandomId());
+		ClientId clientId = new ClientId(TestUtils.getRandomId());
+		byte[] descriptor = TestUtils.getRandomBytes(12);
+		group = new Group(groupId, clientId, descriptor);
 
-	    MessageId messageId = new MessageId(TestUtils.getRandomId());
-	    long timestamp = System.currentTimeMillis();
-	    byte[] body = TestUtils.getRandomBytes(123);
-	    message = new Message(messageId, groupId, timestamp, body);
+		MessageId messageId = new MessageId(TestUtils.getRandomId());
+		long timestamp = System.currentTimeMillis();
+		byte[] body = TestUtils.getRandomBytes(123);
+		message = new Message(messageId, groupId, timestamp, body);
 
-	    Mockery context = new Mockery();
-	    ClientHelper clientHelper = context.mock(ClientHelper.class);
-	    MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
-	    Clock clock = context.mock(Clock.class);
+		Mockery context = new Mockery();
+		ClientHelper clientHelper = context.mock(ClientHelper.class);
+		MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
+		Clock clock = context.mock(Clock.class);
 
 		tpv = new TransportPropertyValidator(clientHelper, metadataEncoder,
 				clock);
-    }
-
-    @Test
-    public void testValidateProperMessage() throws IOException {
-
-		BdfList body = BdfList.of(deviceId, transportId.getString(), 4,
-				bdfDictionary);
-
-	    BdfDictionary result = tpv.validateMessage(message, group, body);
-
-		assertEquals("test", result.getString("transportId"));
-		assertEquals(result.getLong("version").longValue(), 4);
 	}
 
-    @Test(expected = FormatException.class)
-    public void testValidateWrongVersionValue() throws IOException {
+	@Test
+	public void testValidateProperMessage() throws IOException {
 
-		/* Will create a negative version number */
-		BdfList body = BdfList.of(deviceId, transportId.getString(), -1,
-				bdfDictionary);
-		tpv.validateMessage(message, group, body);
-	}
+		BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
 
-    @Test(expected = FormatException.class)
-    public void testValidateWrongVersionType() throws IOException {
+		BdfDictionary result = tpv.validateMessage(message, group, body);
 
-		/* Instead of sending a version number I'm sending a dict */
-		BdfList body = BdfList.of(deviceId, transportId.getString(),
-				bdfDictionary, bdfDictionary);
-		tpv.validateMessage(message, group, body);
+		assertEquals("test", result.getString("transportId"));
+		assertEquals(4, result.getLong("version").longValue());
 	}
 
-    @Test(expected = FormatException.class)
-    public void testValidateShortDeviceId() throws IOException {
+	@Test(expected = FormatException.class)
+	public void testValidateWrongVersionValue() throws IOException {
 
-		/* Will create a Device Id with a short length, getRaw should work */
-		BdfList body = BdfList.of(new byte[UniqueId.LENGTH - 1],
-				transportId.getString(), 1, bdfDictionary);
+		BdfList body = BdfList.of(transportId.getString(), -1, bdfDictionary);
 		tpv.validateMessage(message, group, body);
 	}
 
-    @Test(expected = FormatException.class)
-	public void testValidateLongDeviceId() throws IOException {
+	@Test(expected = FormatException.class)
+	public void testValidateWrongVersionType() throws IOException {
 
-		BdfList body = BdfList.of(new byte[UniqueId.LENGTH + 1],
-				transportId.getString(), 1, bdfDictionary);
+		BdfList body = BdfList.of(transportId.getString(), bdfDictionary,
+				bdfDictionary);
 		tpv.validateMessage(message, group, body);
 	}
 
-    @Test(expected = FormatException.class)
-	public void testValidateWrongDeviceId() throws IOException {
+	@Test(expected = FormatException.class)
+	public void testValidateLongTransportId() throws IOException {
 
-		BdfList body = BdfList.of(bdfDictionary, transportId.getString(), 1,
-				bdfDictionary);
+		String wrongTransportIdString =
+				TestUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
+		BdfList body = BdfList.of(wrongTransportIdString, 4, bdfDictionary);
 		tpv.validateMessage(message, group, body);
 	}
 
-    @Test(expected = FormatException.class)
-    public void testValidateLongTransportId() throws IOException {
+	@Test(expected = FormatException.class)
+	public void testValidateEmptyTransportId() throws IOException {
 
-		/* Generate a string or arbitrary length for the transport id*/
-		String wrongTransportIdString =
-				TestUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
-		BdfList body = BdfList.of(deviceId, wrongTransportIdString, 4,
-				bdfDictionary);
+		BdfList body = BdfList.of("", 4, bdfDictionary);
 		tpv.validateMessage(message, group, body);
 	}
 
-    @Test(expected = FormatException.class)
-    public void testValidateTooManyProperties() throws IOException {
+	@Test(expected = FormatException.class)
+	public void testValidateTooManyProperties() throws IOException {
 
-		/* Generate a big map for the BdfDictionary*/
-	    BdfDictionary d = new BdfDictionary();
+		BdfDictionary d = new BdfDictionary();
 		for (int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT + 1; i++)
 			d.put(String.valueOf(i), i);
-		BdfList body = BdfList.of(deviceId, transportId.getString(), 4, d);
+		BdfList body = BdfList.of(transportId.getString(), 4, d);
 		tpv.validateMessage(message, group, body);
 	}
 }