mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Use discovery rather than hardcoded URLs (which don't work).
Two devices that aren't discoverable won't be able to communicate. (Likely to affect Linux devices, since changing discoverability requires root on Linux.)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
|
||||
class BluetoothListener implements DiscoveryListener {
|
||||
|
||||
private static final int[] ATTRIBUTES = { 0x100 }; // Service name
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
private final Map<String, ContactId> addresses;
|
||||
private final Map<ContactId, String> uuids;
|
||||
private final Map<ContactId, String> urls;
|
||||
|
||||
BluetoothListener(DiscoveryAgent discoveryAgent,
|
||||
Map<String, ContactId> addresses, Map<ContactId, String> uuids) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.addresses = addresses;
|
||||
this.uuids = uuids;
|
||||
urls = Collections.synchronizedMap(new HashMap<ContactId, String>());
|
||||
}
|
||||
|
||||
public Map<ContactId, String> getUrls() {
|
||||
return urls;
|
||||
}
|
||||
|
||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
||||
// Do we recognise the address?
|
||||
ContactId contactId = addresses.get(device.getBluetoothAddress());
|
||||
if(contactId == null) return;
|
||||
// Do we have a UUID for this contact?
|
||||
String uuid = uuids.get(contactId);
|
||||
if(uuid == null) return;
|
||||
UUID[] uuids = new UUID[] { new UUID(uuid, false) };
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(ATTRIBUTES, uuids, device, this);
|
||||
searches.incrementAndGet();
|
||||
} catch(BluetoothStateException e) {
|
||||
// FIXME: Logging
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if(searches.decrementAndGet() == 0) {
|
||||
synchronized(this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
||||
for(ServiceRecord record : services) {
|
||||
// Do we recognise the address?
|
||||
RemoteDevice device = record.getHostDevice();
|
||||
ContactId c = addresses.get(device.getBluetoothAddress());
|
||||
if(c == null) continue;
|
||||
// Store the URL
|
||||
String url = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if(url != null) urls.put(c, url);
|
||||
}
|
||||
}
|
||||
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if(searches.decrementAndGet() == 0) {
|
||||
synchronized(this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -20,6 +25,8 @@ import net.sf.briar.api.transport.stream.StreamTransportCallback;
|
||||
import net.sf.briar.api.transport.stream.StreamTransportConnection;
|
||||
import net.sf.briar.api.transport.stream.StreamTransportPlugin;
|
||||
import net.sf.briar.plugins.AbstractPlugin;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
|
||||
class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
|
||||
@@ -27,16 +34,14 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
|
||||
private static final TransportId id = new TransportId(TRANSPORT_ID);
|
||||
|
||||
private final String uuid;
|
||||
private final long pollingInterval;
|
||||
|
||||
private StreamTransportCallback callback = null;
|
||||
private LocalDevice localDevice = null;
|
||||
private StreamConnectionNotifier streamConnectionNotifier = null;
|
||||
|
||||
BluetoothPlugin(Executor executor, String uuid, long pollingInterval) {
|
||||
BluetoothPlugin(Executor executor, long pollingInterval) {
|
||||
super(executor);
|
||||
this.uuid = uuid;
|
||||
this.pollingInterval = pollingInterval;
|
||||
}
|
||||
|
||||
@@ -51,7 +56,14 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
super.start(localProperties, remoteProperties, config);
|
||||
this.callback = callback;
|
||||
// Initialise the Bluetooth stack
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
try {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch(UnsatisfiedLinkError e) {
|
||||
// On Linux the user may need to install libbluetooth-dev
|
||||
if(OsUtils.isLinux())
|
||||
callback.showMessage("BLUETOOTH_INSTALL LIBS");
|
||||
throw e;
|
||||
}
|
||||
executor.execute(createBinder());
|
||||
}
|
||||
|
||||
@@ -72,19 +84,17 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
String uuid;
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
uuid = config.get("uuid");
|
||||
if(uuid == null) uuid = createAndSetUuid();
|
||||
}
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
|
||||
} catch(BluetoothStateException e) {
|
||||
// FIXME: Logging
|
||||
try {
|
||||
localDevice.setDiscoverable(DiscoveryAgent.LIAC);
|
||||
} catch(BluetoothStateException e1) {
|
||||
// FIXME: Logging
|
||||
}
|
||||
}
|
||||
// Bind the port
|
||||
String url = "btspp://localhost:" + uuid + ";name=" + uuid;
|
||||
@@ -96,14 +106,33 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
return;
|
||||
}
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
if(!started) {
|
||||
try {
|
||||
scn.close();
|
||||
} catch(IOException e) {
|
||||
// FIXME: Logging
|
||||
}
|
||||
return;
|
||||
}
|
||||
streamConnectionNotifier = scn;
|
||||
setConnectionUrl(localDevice.getBluetoothAddress());
|
||||
setLocalBluetoothAddress(localDevice.getBluetoothAddress());
|
||||
startListener();
|
||||
}
|
||||
}
|
||||
|
||||
private String createAndSetUuid() {
|
||||
assert started;
|
||||
byte[] b = new byte[16];
|
||||
new Random().nextBytes(b); // FIXME: Use a SecureRandom?
|
||||
String uuid = StringUtils.toHexString(b);
|
||||
Map<String, String> m = new TreeMap<String, String>(config);
|
||||
m.put("uuid", uuid);
|
||||
callback.setConfig(m);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private void startListener() {
|
||||
assert started;
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -142,11 +171,10 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void setConnectionUrl(String address) {
|
||||
// Update the local properties with the connection URL
|
||||
String url = "btspp://" + address + ":" + uuid + ";name=" + uuid;
|
||||
private void setLocalBluetoothAddress(String address) {
|
||||
assert started;
|
||||
Map<String, String> m = new TreeMap<String, String>(localProperties);
|
||||
m.put("url", url);
|
||||
m.put("address", address);
|
||||
callback.setLocalProperties(m);
|
||||
}
|
||||
|
||||
@@ -160,38 +188,74 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
|
||||
public synchronized void poll() {
|
||||
if(!started) return;
|
||||
for(ContactId c : remoteProperties.keySet()) {
|
||||
executor.execute(createConnector(c));
|
||||
}
|
||||
executor.execute(createConnectors(remoteProperties.keySet()));
|
||||
}
|
||||
|
||||
private Runnable createConnector(final ContactId c) {
|
||||
private Runnable createConnectors(final Collection<ContactId> contacts) {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
connect(c);
|
||||
connectAndCallBack(contacts);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private StreamTransportConnection connect(ContactId c) {
|
||||
StreamTransportConnection conn = createAndConnectSocket(c);
|
||||
if(conn != null) {
|
||||
synchronized(this) {
|
||||
if(started) callback.outgoingConnectionCreated(c, conn);
|
||||
private void connectAndCallBack(Collection<ContactId> contacts) {
|
||||
Map<ContactId, String> discovered = discover(contacts);
|
||||
for(Entry<ContactId, String> e : discovered.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
String url = e.getValue();
|
||||
StreamTransportConnection conn = createConnection(c, url);
|
||||
if(conn != null) {
|
||||
synchronized(this) {
|
||||
if(started) callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
private StreamTransportConnection createAndConnectSocket(ContactId c) {
|
||||
private Map<ContactId, String> discover(Collection<ContactId> contacts) {
|
||||
DiscoveryAgent discoveryAgent;
|
||||
Map<String, ContactId> addresses;
|
||||
Map<ContactId, String> uuids;
|
||||
synchronized(this) {
|
||||
if(!started) return Collections.emptyMap();
|
||||
if(localDevice == null) return Collections.emptyMap();
|
||||
discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
addresses = new HashMap<String, ContactId>();
|
||||
uuids = new HashMap<ContactId, String>();
|
||||
for(Entry<ContactId, Map<String, String>> e
|
||||
: remoteProperties.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Map<String, String> properties = e.getValue();
|
||||
String address = properties.get("address");
|
||||
String uuid = properties.get("uuid");
|
||||
if(address != null && uuid != null) {
|
||||
addresses.put(address, c);
|
||||
uuids.put(c, uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
BluetoothListener listener =
|
||||
new BluetoothListener(discoveryAgent, addresses, uuids);
|
||||
try {
|
||||
synchronized(listener) {
|
||||
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
|
||||
listener.wait();
|
||||
}
|
||||
} catch(BluetoothStateException e) {
|
||||
// FIXME: Logging
|
||||
} catch(InterruptedException e) {
|
||||
// FIXME: Logging
|
||||
}
|
||||
return listener.getUrls();
|
||||
}
|
||||
|
||||
private StreamTransportConnection createConnection(ContactId c,
|
||||
String url) {
|
||||
try {
|
||||
String url;
|
||||
synchronized(this) {
|
||||
if(!started) return null;
|
||||
Map<String, String> properties = remoteProperties.get(c);
|
||||
if(properties == null) return null;
|
||||
url = properties.get("url");
|
||||
if(url == null) return null;
|
||||
if(!remoteProperties.containsKey(c)) return null;
|
||||
}
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
return new BluetoothTransportConnection(s);
|
||||
@@ -202,9 +266,8 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
|
||||
}
|
||||
|
||||
public StreamTransportConnection createConnection(ContactId c) {
|
||||
synchronized(this) {
|
||||
if(!started) return null;
|
||||
}
|
||||
return createAndConnectSocket(c);
|
||||
Map<ContactId, String> discovered = discover(Collections.singleton(c));
|
||||
String url = discovered.get(c);
|
||||
return url == null ? null : createConnection(c, url);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user