From d2d8d9d46e8ff2d44eb78eae15f0defb15180db9 Mon Sep 17 00:00:00 2001
From: str4d <str4d@mail.i2p>
Date: Thu, 28 Jan 2016 23:23:43 +0000
Subject: [PATCH] Implement BQP transport descriptors

---
 .../plugins/droidtooth/DroidtoothPlugin.java  |  97 +++++++++++++++--
 .../briarproject/plugins/tor/TorPlugin.java   |  16 +++
 .../keyagreement/KeyAgreementConnection.java  |  23 ++++
 .../keyagreement/KeyAgreementListener.java    |  35 ++++++
 .../api/keyagreement/TransportDescriptor.java |  28 +++++
 .../api/plugins/PluginManager.java            |   3 +
 .../api/plugins/duplex/DuplexPlugin.java      |  17 +++
 .../plugins/PluginManagerImpl.java            |   7 ++
 .../briarproject/plugins/tcp/TcpPlugin.java   |  16 +++
 .../plugins/bluetooth/BluetoothPlugin.java    | 103 ++++++++++++++++--
 .../plugins/modem/ModemPlugin.java            |  16 +++
 11 files changed, 347 insertions(+), 14 deletions(-)
 create mode 100644 briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java
 create mode 100644 briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java
 create mode 100644 briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java

diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index 2bc7dbe702..40a8a04106 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -14,6 +14,9 @@ import org.briarproject.api.TransportId;
 import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
+import org.briarproject.api.keyagreement.KeyAgreementConnection;
+import org.briarproject.api.keyagreement.KeyAgreementListener;
+import org.briarproject.api.keyagreement.TransportDescriptor;
 import org.briarproject.api.plugins.Backoff;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -67,6 +70,9 @@ class DroidtoothPlugin implements DuplexPlugin {
 	private static final String DISCOVERY_FINISHED =
 			"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
 
+	private static final String PROP_ADDRESS = "address";
+	private static final String PROP_UUID = "uuid";
+
 	private final Executor ioExecutor;
 	private final AndroidExecutor androidExecutor;
 	private final Context appContext;
@@ -161,7 +167,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 				if (!StringUtils.isNullOrEmpty(address)) {
 					// Advertise the Bluetooth address to contacts
 					TransportProperties p = new TransportProperties();
-					p.put("address", address);
+					p.put(PROP_ADDRESS, address);
 					callback.mergeLocalProperties(p);
 				}
 				// Bind a server socket to accept connections from contacts
@@ -187,13 +193,13 @@ class DroidtoothPlugin implements DuplexPlugin {
 	}
 
 	private UUID getUuid() {
-		String uuid = callback.getLocalProperties().get("uuid");
+		String uuid = callback.getLocalProperties().get(PROP_UUID);
 		if (uuid == null) {
 			byte[] random = new byte[UUID_BYTES];
 			secureRandom.nextBytes(random);
 			uuid = UUID.nameUUIDFromBytes(random).toString();
 			TransportProperties p = new TransportProperties();
-			p.put("uuid", uuid);
+			p.put(PROP_UUID, uuid);
 			callback.mergeLocalProperties(p);
 		}
 		return UUID.fromString(uuid);
@@ -264,9 +270,9 @@ class DroidtoothPlugin implements DuplexPlugin {
 		for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
 			final ContactId c = e.getKey();
 			if (connected.contains(c)) continue;
-			final String address = e.getValue().get("address");
+			final String address = e.getValue().get(PROP_ADDRESS);
 			if (StringUtils.isNullOrEmpty(address)) continue;
-			final String uuid = e.getValue().get("uuid");
+			final String uuid = e.getValue().get(PROP_UUID);
 			if (StringUtils.isNullOrEmpty(uuid)) continue;
 			ioExecutor.execute(new Runnable() {
 				public void run() {
@@ -325,9 +331,9 @@ class DroidtoothPlugin implements DuplexPlugin {
 		if (!isRunning()) return null;
 		TransportProperties p = callback.getRemoteProperties().get(c);
 		if (p == null) return null;
-		String address = p.get("address");
+		String address = p.get(PROP_ADDRESS);
 		if (StringUtils.isNullOrEmpty(address)) return null;
-		String uuid = p.get("uuid");
+		String uuid = p.get(PROP_UUID);
 		if (StringUtils.isNullOrEmpty(uuid)) return null;
 		BluetoothSocket s = connect(address, uuid);
 		if (s == null) return null;
@@ -417,6 +423,48 @@ class DroidtoothPlugin implements DuplexPlugin {
 		});
 	}
 
+	public boolean supportsKeyAgreement() {
+		return true;
+	}
+
+	public KeyAgreementListener createKeyAgreementListener(
+			byte[] localCommitment) {
+		// No truncation necessary because COMMIT_LENGTH = 16
+		UUID uuid = UUID.nameUUIDFromBytes(localCommitment);
+		if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
+		// Bind a server socket for receiving invitation connections
+		BluetoothServerSocket ss;
+		try {
+			ss = InsecureBluetooth.listen(adapter, "RFCOMM", uuid);
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return null;
+		}
+		TransportProperties p = new TransportProperties();
+		String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
+		if (!StringUtils.isNullOrEmpty(address))
+			p.put(PROP_ADDRESS, address);
+		TransportDescriptor d = new TransportDescriptor(ID, p);
+		return new BluetoothKeyAgreementListener(d, ss);
+	}
+
+	public DuplexTransportConnection createKeyAgreementConnection(
+			byte[] remoteCommitment, TransportDescriptor d, long timeout) {
+		if (!isRunning()) return null;
+		if (!ID.equals(d.getIdentifier())) return null;
+		TransportProperties p = d.getProperties();
+		if (p == null) return null;
+		String address = p.get(PROP_ADDRESS);
+		if (StringUtils.isNullOrEmpty(address)) return null;
+		// No truncation necessary because COMMIT_LENGTH = 16
+		UUID uuid = UUID.nameUUIDFromBytes(remoteCommitment);
+		if (LOG.isLoggable(INFO))
+			LOG.info("Connecting to key agreement UUID " + uuid);
+		BluetoothSocket s = connect(address, uuid.toString());
+		if (s == null) return null;
+		return new DroidtoothTransportConnection(this, s);
+	}
+
 	private class BluetoothStateReceiver extends BroadcastReceiver {
 
 		@Override
@@ -545,4 +593,39 @@ class DroidtoothPlugin implements DuplexPlugin {
 			return s;
 		}
 	}
+
+	private class BluetoothKeyAgreementListener extends KeyAgreementListener {
+
+		private final BluetoothServerSocket ss;
+
+		public BluetoothKeyAgreementListener(TransportDescriptor descriptor,
+				BluetoothServerSocket ss) {
+			super(descriptor);
+			this.ss = ss;
+		}
+
+		@Override
+		public Callable<KeyAgreementConnection> listen() {
+			return new Callable<KeyAgreementConnection>() {
+				@Override
+				public KeyAgreementConnection call() throws IOException {
+					BluetoothSocket s = ss.accept();
+					if (LOG.isLoggable(INFO))
+						LOG.info(ID.getString() + ": Incoming connection");
+					return new KeyAgreementConnection(
+							new DroidtoothTransportConnection(
+									DroidtoothPlugin.this, s), ID);
+				}
+			};
+		}
+
+		@Override
+		public void close() {
+			try {
+				ss.close();
+			} catch (IOException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			}
+		}
+	}
 }
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index e5f7180c0e..d3dbff7932 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -19,6 +19,8 @@ import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.SettingsUpdatedEvent;
+import org.briarproject.api.keyagreement.KeyAgreementListener;
+import org.briarproject.api.keyagreement.TransportDescriptor;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
@@ -570,6 +572,20 @@ class TorPlugin implements DuplexPlugin, EventHandler,
 		throw new UnsupportedOperationException();
 	}
 
+	public boolean supportsKeyAgreement() {
+		return false;
+	}
+
+	public KeyAgreementListener createKeyAgreementListener(
+			byte[] commitment) {
+		throw new UnsupportedOperationException();
+	}
+
+	public DuplexTransportConnection createKeyAgreementConnection(
+			byte[] commitment, TransportDescriptor d, long timeout) {
+		throw new UnsupportedOperationException();
+	}
+
 	public void circuitStatus(String status, String id, String path) {
 		if (status.equals("BUILT") && !circuitBuilt.getAndSet(true)) {
 			LOG.info("First circuit built");
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java
new file mode 100644
index 0000000000..7dfa3bc689
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java
@@ -0,0 +1,23 @@
+package org.briarproject.api.keyagreement;
+
+import org.briarproject.api.TransportId;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+
+public class KeyAgreementConnection {
+	private final DuplexTransportConnection conn;
+	private final TransportId id;
+
+	public KeyAgreementConnection(DuplexTransportConnection conn,
+			TransportId id) {
+		this.conn = conn;
+		this.id = id;
+	}
+
+	public DuplexTransportConnection getConnection() {
+		return conn;
+	}
+
+	public TransportId getTransportId() {
+		return id;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java
new file mode 100644
index 0000000000..05163614cc
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java
@@ -0,0 +1,35 @@
+package org.briarproject.api.keyagreement;
+
+import java.util.concurrent.Callable;
+
+/**
+ * An class for managing a particular key agreement listener.
+ */
+public abstract class KeyAgreementListener {
+
+	private final TransportDescriptor descriptor;
+
+	public KeyAgreementListener(TransportDescriptor descriptor) {
+		this.descriptor = descriptor;
+	}
+
+	/**
+	 * Returns the descriptor that a remote peer can use to connect to this
+	 * listener.
+	 */
+	public TransportDescriptor getDescriptor() {
+		return descriptor;
+	}
+
+	/**
+	 * Starts listening for incoming connections, and returns a Callable that
+	 * will return a KeyAgreementConnection when an incoming connection is
+	 * received.
+	 */
+	public abstract Callable<KeyAgreementConnection> listen();
+
+	/**
+	 * Closes the underlying server socket.
+	 */
+	public abstract void close();
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java b/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java
new file mode 100644
index 0000000000..cdaa5a5794
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java
@@ -0,0 +1,28 @@
+package org.briarproject.api.keyagreement;
+
+import org.briarproject.api.TransportId;
+import org.briarproject.api.properties.TransportProperties;
+
+/**
+ * Describes how to connect to a device over a short-range transport.
+ */
+public class TransportDescriptor {
+
+	private final TransportId id;
+	private final TransportProperties properties;
+
+	public TransportDescriptor(TransportId id, TransportProperties properties) {
+		this.id = id;
+		this.properties = properties;
+	}
+
+	/** Returns the transport identifier. */
+	public TransportId getIdentifier() {
+		return id;
+	}
+
+	/** Returns the transport properties. */
+	public TransportProperties getProperties() {
+		return properties;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/plugins/PluginManager.java b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
index 35248962d6..838c66ff86 100644
--- a/briar-api/src/org/briarproject/api/plugins/PluginManager.java
+++ b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
@@ -19,4 +19,7 @@ public interface PluginManager {
 
 	/** Returns any running duplex plugins that support invitations. */
 	Collection<DuplexPlugin> getInvitationPlugins();
+
+	/** Returns any running duplex plugins that support key agreement. */
+	Collection<DuplexPlugin> getKeyAgreementPlugins();
 }
diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java
index 519400b563..ff7db3b51d 100644
--- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java
+++ b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java
@@ -2,6 +2,8 @@ package org.briarproject.api.plugins.duplex;
 
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
+import org.briarproject.api.keyagreement.KeyAgreementListener;
+import org.briarproject.api.keyagreement.TransportDescriptor;
 import org.briarproject.api.plugins.Plugin;
 
 /** An interface for transport plugins that support duplex communication. */
@@ -24,4 +26,19 @@ public interface DuplexPlugin extends Plugin {
 	 */
 	DuplexTransportConnection createInvitationConnection(PseudoRandom r,
 			long timeout, boolean alice);
+
+	/** Returns true if the plugin supports short-range key agreement. */
+	boolean supportsKeyAgreement();
+
+	/**
+	 * Returns a listener that can be used to perform key agreement.
+	 */
+	KeyAgreementListener createKeyAgreementListener(byte[] localCommitment);
+
+	/**
+	 * Attempts to connect to the remote peer specified in the given descriptor.
+	 * Returns null if no connection can be established within the given time.
+	 */
+	DuplexTransportConnection createKeyAgreementConnection(
+			byte[] remoteCommitment, TransportDescriptor d, long timeout);
 }
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index bd110d0cf6..43a80bb546 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -161,6 +161,13 @@ class PluginManagerImpl implements PluginManager, Service {
 		return Collections.unmodifiableList(supported);
 	}
 
+	public Collection<DuplexPlugin> getKeyAgreementPlugins() {
+		List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
+		for (DuplexPlugin d : duplexPlugins)
+			if (d.supportsKeyAgreement()) supported.add(d);
+		return Collections.unmodifiableList(supported);
+	}
+
 	private class SimplexPluginStarter implements Runnable {
 
 		private final SimplexPluginFactory factory;
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index 54376310a0..d6effe2a72 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -2,6 +2,8 @@ package org.briarproject.plugins.tcp;
 
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
+import org.briarproject.api.keyagreement.KeyAgreementListener;
+import org.briarproject.api.keyagreement.TransportDescriptor;
 import org.briarproject.api.plugins.Backoff;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -250,6 +252,20 @@ abstract class TcpPlugin implements DuplexPlugin {
 		throw new UnsupportedOperationException();
 	}
 
+	public boolean supportsKeyAgreement() {
+		return false;
+	}
+
+	public KeyAgreementListener createKeyAgreementListener(
+			byte[] commitment) {
+		throw new UnsupportedOperationException();
+	}
+
+	public DuplexTransportConnection createKeyAgreementConnection(
+			byte[] commitment, TransportDescriptor d, long timeout) {
+		throw new UnsupportedOperationException();
+	}
+
 	protected Collection<InetAddress> getLocalIpAddresses() {
 		List<NetworkInterface> ifaces;
 		try {
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
index 79e289c39c..875112c5e8 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
@@ -3,6 +3,9 @@ package org.briarproject.plugins.bluetooth;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
+import org.briarproject.api.keyagreement.KeyAgreementConnection;
+import org.briarproject.api.keyagreement.KeyAgreementListener;
+import org.briarproject.api.keyagreement.TransportDescriptor;
 import org.briarproject.api.plugins.Backoff;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -50,6 +53,9 @@ class BluetoothPlugin implements DuplexPlugin {
 			Logger.getLogger(BluetoothPlugin.class.getName());
 	private static final int UUID_BYTES = 16;
 
+	private static final String PROP_ADDRESS = "address";
+	private static final String PROP_UUID = "uuid";
+
 	private final Executor ioExecutor;
 	private final SecureRandom secureRandom;
 	private final Backoff backoff;
@@ -106,7 +112,7 @@ class BluetoothPlugin implements DuplexPlugin {
 				if (!running) return;
 				// Advertise the Bluetooth address to contacts
 				TransportProperties p = new TransportProperties();
-				p.put("address", localDevice.getBluetoothAddress());
+				p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
 				callback.mergeLocalProperties(p);
 				// Bind a server socket to accept connections from contacts
 				String url = makeUrl("localhost", getUuid());
@@ -135,13 +141,13 @@ class BluetoothPlugin implements DuplexPlugin {
 	}
 
 	private String getUuid() {
-		String uuid = callback.getLocalProperties().get("uuid");
+		String uuid = callback.getLocalProperties().get(PROP_UUID);
 		if (uuid == null) {
 			byte[] random = new byte[UUID_BYTES];
 			secureRandom.nextBytes(random);
 			uuid = UUID.nameUUIDFromBytes(random).toString();
 			TransportProperties p = new TransportProperties();
-			p.put("uuid", uuid);
+			p.put(PROP_UUID, uuid);
 			callback.mergeLocalProperties(p);
 		}
 		return uuid;
@@ -203,9 +209,9 @@ class BluetoothPlugin implements DuplexPlugin {
 		for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
 			final ContactId c = e.getKey();
 			if (connected.contains(c)) continue;
-			final String address = e.getValue().get("address");
+			final String address = e.getValue().get(PROP_ADDRESS);
 			if (StringUtils.isNullOrEmpty(address)) continue;
-			final String uuid = e.getValue().get("uuid");
+			final String uuid = e.getValue().get(PROP_UUID);
 			if (StringUtils.isNullOrEmpty(uuid)) continue;
 			ioExecutor.execute(new Runnable() {
 				public void run() {
@@ -236,9 +242,9 @@ class BluetoothPlugin implements DuplexPlugin {
 		if (!running) return null;
 		TransportProperties p = callback.getRemoteProperties().get(c);
 		if (p == null) return null;
-		String address = p.get("address");
+		String address = p.get(PROP_ADDRESS);
 		if (StringUtils.isNullOrEmpty(address)) return null;
-		String uuid = p.get("uuid");
+		String uuid = p.get(PROP_UUID);
 		if (StringUtils.isNullOrEmpty(uuid)) return null;
 		String url = makeUrl(address, uuid);
 		StreamConnection s = connect(url);
@@ -335,6 +341,54 @@ class BluetoothPlugin implements DuplexPlugin {
 		});
 	}
 
+	public boolean supportsKeyAgreement() {
+		return true;
+	}
+
+	public KeyAgreementListener createKeyAgreementListener(
+			byte[] localCommitment) {
+		// No truncation necessary because COMMIT_LENGTH = 16
+		String uuid = UUID.nameUUIDFromBytes(localCommitment).toString();
+		if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
+		String url = makeUrl("localhost", uuid);
+		// Make the device discoverable if possible
+		makeDeviceDiscoverable();
+		// Bind a server socket for receiving invitation connections
+		final StreamConnectionNotifier ss;
+		try {
+			ss = (StreamConnectionNotifier) Connector.open(url);
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return null;
+		}
+		if (!running) {
+			tryToClose(ss);
+			return null;
+		}
+		TransportProperties p = new TransportProperties();
+		p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
+		TransportDescriptor d = new TransportDescriptor(ID, p);
+		return new BluetoothKeyAgreementListener(d, ss);
+	}
+
+	public DuplexTransportConnection createKeyAgreementConnection(
+			byte[] remoteCommitment, TransportDescriptor d, long timeout) {
+		if (!isRunning()) return null;
+		if (!ID.equals(d.getIdentifier())) return null;
+		TransportProperties p = d.getProperties();
+		if (p == null) return null;
+		String address = p.get(PROP_ADDRESS);
+		if (StringUtils.isNullOrEmpty(address)) return null;
+		// No truncation necessary because COMMIT_LENGTH = 16
+		String uuid = UUID.nameUUIDFromBytes(remoteCommitment).toString();
+		if (LOG.isLoggable(INFO))
+			LOG.info("Connecting to key agreement UUID " + uuid);
+		String url = makeUrl(address, uuid);
+		StreamConnection s = connect(url);
+		if (s == null) return null;
+		return new BluetoothTransportConnection(this, s);
+	}
+
 	private void makeDeviceDiscoverable() {
 		// Try to make the device discoverable (requires root on Linux)
 		try {
@@ -414,4 +468,39 @@ class BluetoothPlugin implements DuplexPlugin {
 			return s;
 		}
 	}
+
+	private class BluetoothKeyAgreementListener extends KeyAgreementListener {
+
+		private final StreamConnectionNotifier ss;
+
+		public BluetoothKeyAgreementListener(TransportDescriptor descriptor,
+				StreamConnectionNotifier ss) {
+			super(descriptor);
+			this.ss = ss;
+		}
+
+		@Override
+		public Callable<KeyAgreementConnection> listen() {
+			return new Callable<KeyAgreementConnection>() {
+				@Override
+				public KeyAgreementConnection call() throws Exception {
+					StreamConnection s = ss.acceptAndOpen();
+					if (LOG.isLoggable(INFO))
+						LOG.info(ID.getString() + ": Incoming connection");
+					return new KeyAgreementConnection(
+							new BluetoothTransportConnection(
+									BluetoothPlugin.this, s), ID);
+				}
+			};
+		}
+
+		@Override
+		public void close() {
+			try {
+				ss.close();
+			} catch (IOException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			}
+		}
+	}
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index 954529a5e8..50dcfb7cd0 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -3,6 +3,8 @@ package org.briarproject.plugins.modem;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.PseudoRandom;
+import org.briarproject.api.keyagreement.KeyAgreementListener;
+import org.briarproject.api.keyagreement.TransportDescriptor;
 import org.briarproject.api.plugins.TransportConnectionReader;
 import org.briarproject.api.plugins.TransportConnectionWriter;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
@@ -158,6 +160,20 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 		throw new UnsupportedOperationException();
 	}
 
+	public boolean supportsKeyAgreement() {
+		return false;
+	}
+
+	public KeyAgreementListener createKeyAgreementListener(
+			byte[] commitment) {
+		throw new UnsupportedOperationException();
+	}
+
+	public DuplexTransportConnection createKeyAgreementConnection(
+			byte[] commitment, TransportDescriptor d, long timeout) {
+		throw new UnsupportedOperationException();
+	}
+
 	public void incomingCallConnected() {
 		LOG.info("Incoming call connected");
 		callback.incomingConnectionCreated(new ModemTransportConnection());
-- 
GitLab