mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
Reworked the Bluetooth invitation process to be symmetrical (untested).
This commit is contained in:
@@ -29,6 +29,7 @@ import net.sf.briar.api.crypto.PseudoRandom;
|
|||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
import net.sf.briar.util.LatchedReference;
|
||||||
import net.sf.briar.util.OsUtils;
|
import net.sf.briar.util.OsUtils;
|
||||||
import net.sf.briar.util.StringUtils;
|
import net.sf.briar.util.StringUtils;
|
||||||
|
|
||||||
@@ -112,20 +113,21 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
TransportProperties p = new TransportProperties();
|
TransportProperties p = new TransportProperties();
|
||||||
p.put("address", localDevice.getBluetoothAddress());
|
p.put("address", localDevice.getBluetoothAddress());
|
||||||
callback.mergeLocalProperties(p);
|
callback.mergeLocalProperties(p);
|
||||||
|
// Bind a server socket to accept connections from contacts
|
||||||
String url = makeUrl("localhost", getUuid());
|
String url = makeUrl("localhost", getUuid());
|
||||||
StreamConnectionNotifier scn;
|
StreamConnectionNotifier ss;
|
||||||
try {
|
try {
|
||||||
scn = (StreamConnectionNotifier) Connector.open(url);
|
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!running) {
|
if(!running) {
|
||||||
tryToClose(scn);
|
tryToClose(ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
socket = scn;
|
socket = ss;
|
||||||
acceptContactConnections(scn);
|
acceptContactConnections(ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeUrl(String address, String uuid) {
|
private String makeUrl(String address, String uuid) {
|
||||||
@@ -145,23 +147,23 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(StreamConnectionNotifier scn) {
|
private void tryToClose(StreamConnectionNotifier ss) {
|
||||||
try {
|
try {
|
||||||
scn.close();
|
if(ss != null) ss.close();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acceptContactConnections(StreamConnectionNotifier scn) {
|
private void acceptContactConnections(StreamConnectionNotifier ss) {
|
||||||
while(true) {
|
while(true) {
|
||||||
StreamConnection s;
|
StreamConnection s;
|
||||||
try {
|
try {
|
||||||
s = scn.acceptAndOpen();
|
s = ss.acceptAndOpen();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
// This is expected when the socket is closed
|
// This is expected when the socket is closed
|
||||||
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||||
tryToClose(scn);
|
tryToClose(ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BluetoothTransportConnection conn =
|
BluetoothTransportConnection conn =
|
||||||
@@ -173,7 +175,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
if(socket != null) tryToClose(socket);
|
tryToClose(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldPoll() {
|
public boolean shouldPoll() {
|
||||||
@@ -199,19 +201,20 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
pluginExecutor.execute(new Runnable() {
|
pluginExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if(!running) return;
|
if(!running) return;
|
||||||
String url = makeUrl(address, uuid);
|
StreamConnection s = connect(makeUrl(address, uuid));
|
||||||
DuplexTransportConnection conn = connect(url);
|
if(s != null) {
|
||||||
if(conn != null)
|
callback.outgoingConnectionCreated(c,
|
||||||
callback.outgoingConnectionCreated(c, conn);
|
new BluetoothTransportConnection(
|
||||||
|
BluetoothPlugin.this, s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DuplexTransportConnection connect(String url) {
|
private StreamConnection connect(String url) {
|
||||||
try {
|
try {
|
||||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
return (StreamConnection) Connector.open(url);
|
||||||
return new BluetoothTransportConnection(this, s);
|
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
@@ -227,7 +230,9 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
String uuid = p.get("uuid");
|
String uuid = p.get("uuid");
|
||||||
if(StringUtils.isNullOrEmpty(uuid)) return null;
|
if(StringUtils.isNullOrEmpty(uuid)) return null;
|
||||||
String url = makeUrl(address, uuid);
|
String url = makeUrl(address, uuid);
|
||||||
return connect(url);
|
StreamConnection s = connect(url);
|
||||||
|
if(s == null) return null;
|
||||||
|
return new BluetoothTransportConnection(this, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportsInvitations() {
|
public boolean supportsInvitations() {
|
||||||
@@ -237,84 +242,42 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
// Use the same pseudo-random UUID as the contact
|
// Use the invitation codes to generate the UUID
|
||||||
byte[] b = r.nextBytes(UUID_BYTES);
|
|
||||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
|
||||||
// Discover nearby devices and connect to any with the right UUID
|
|
||||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
|
||||||
long end = clock.currentTimeMillis() + timeout;
|
|
||||||
String url = null;
|
|
||||||
while(url == null && clock.currentTimeMillis() < end) {
|
|
||||||
if(!discoverySemaphore.tryAcquire()) {
|
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Another device discovery is in progress");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
InvitationListener listener =
|
|
||||||
new InvitationListener(discoveryAgent, uuid);
|
|
||||||
discoveryAgent.startInquiry(GIAC, listener);
|
|
||||||
url = listener.waitForUrl();
|
|
||||||
} catch(BluetoothStateException e) {
|
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Interrupted while waiting for URL");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
discoverySemaphore.release();
|
|
||||||
}
|
|
||||||
if(!running) return null;
|
|
||||||
}
|
|
||||||
if(url == null) return null;
|
|
||||||
return connect(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
final long timeout) {
|
|
||||||
if(!running) return null;
|
|
||||||
// Use the same pseudo-random UUID as the contact
|
|
||||||
byte[] b = r.nextBytes(UUID_BYTES);
|
byte[] b = r.nextBytes(UUID_BYTES);
|
||||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
||||||
String url = makeUrl("localhost", uuid);
|
String url = makeUrl("localhost", uuid);
|
||||||
// Make the device discoverable if possible
|
// Make the device discoverable if possible
|
||||||
makeDeviceDiscoverable();
|
makeDeviceDiscoverable();
|
||||||
// Bind a socket for accepting the invitation connection
|
// Bind a server socket for receiving invitation connections
|
||||||
final StreamConnectionNotifier scn;
|
final StreamConnectionNotifier ss;
|
||||||
try {
|
try {
|
||||||
scn = (StreamConnectionNotifier) Connector.open(url);
|
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if(!running) {
|
if(!running) {
|
||||||
tryToClose(scn);
|
tryToClose(ss);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Close the socket when the invitation times out
|
// Start the background threads
|
||||||
new Thread() {
|
LatchedReference<StreamConnection> socketLatch =
|
||||||
@Override
|
new LatchedReference<StreamConnection>();
|
||||||
public void run() {
|
new DiscoveryThread(socketLatch, uuid, timeout).start();
|
||||||
try {
|
new BluetoothListenerThread(socketLatch, ss).start();
|
||||||
Thread.sleep(timeout);
|
// Wait for an incoming or outgoing connection
|
||||||
} catch(InterruptedException e) {
|
|
||||||
if(LOG.isLoggable(WARNING))
|
|
||||||
LOG.warning("Interrupted while waiting for invitation");
|
|
||||||
}
|
|
||||||
tryToClose(scn);
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
// Try to accept a connection
|
|
||||||
try {
|
try {
|
||||||
StreamConnection s = scn.acceptAndOpen();
|
StreamConnection s = socketLatch.waitForReference(timeout);
|
||||||
return new BluetoothTransportConnection(this, s);
|
if(s != null) return new BluetoothTransportConnection(this, s);
|
||||||
} catch(IOException e) {
|
} catch(InterruptedException e) {
|
||||||
// This is expected when the socket is closed
|
if(LOG.isLoggable(INFO))
|
||||||
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
LOG.info("Interrupted while exchanging invitations");
|
||||||
return null;
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
// Closing the socket will terminate the listener thread
|
||||||
|
tryToClose(ss);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeDeviceDiscoverable() {
|
private void makeDeviceDiscoverable() {
|
||||||
@@ -326,4 +289,107 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
|
// FIXME
|
||||||
|
return sendInvitation(r, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiscoveryThread extends Thread {
|
||||||
|
|
||||||
|
private final LatchedReference<StreamConnection> socketLatch;
|
||||||
|
private final String uuid;
|
||||||
|
private final long timeout;
|
||||||
|
|
||||||
|
private DiscoveryThread(LatchedReference<StreamConnection> socketLatch,
|
||||||
|
String uuid, long timeout) {
|
||||||
|
this.socketLatch = socketLatch;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long end = now + timeout;
|
||||||
|
while(now < end && running && !socketLatch.isSet()) {
|
||||||
|
if(!discoverySemaphore.tryAcquire()) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Another device discovery is in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
InvitationListener listener =
|
||||||
|
new InvitationListener(discoveryAgent, uuid);
|
||||||
|
discoveryAgent.startInquiry(GIAC, listener);
|
||||||
|
String url = listener.waitForUrl();
|
||||||
|
if(url != null) {
|
||||||
|
StreamConnection s = connect(url);
|
||||||
|
if(s == null) return;
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Outgoing connection");
|
||||||
|
if(!socketLatch.set(s)) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Closing redundant connection");
|
||||||
|
tryToClose(s);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch(BluetoothStateException e) {
|
||||||
|
if(LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
return;
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Interrupted while waiting for URL");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
discoverySemaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(StreamConnection s) {
|
||||||
|
try {
|
||||||
|
s.close();
|
||||||
|
} catch(IOException e) {
|
||||||
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BluetoothListenerThread extends Thread {
|
||||||
|
|
||||||
|
private final LatchedReference<StreamConnection> socketLatch;
|
||||||
|
private final StreamConnectionNotifier serverSocket;
|
||||||
|
|
||||||
|
private BluetoothListenerThread(
|
||||||
|
LatchedReference<StreamConnection> socketLatch,
|
||||||
|
StreamConnectionNotifier serverSocket) {
|
||||||
|
this.socketLatch = socketLatch;
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Listening for invitation connections");
|
||||||
|
// Listen until a connection is received or the socket is closed
|
||||||
|
try {
|
||||||
|
StreamConnection s = serverSocket.acceptAndOpen();
|
||||||
|
if(LOG.isLoggable(INFO)) LOG.info("Incoming connection");
|
||||||
|
if(!socketLatch.set(s)) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Closing redundant connection");
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
// This is expected when the socket is closed
|
||||||
|
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user