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:
akwizgran
2011-10-07 13:36:24 +01:00
parent ea6beac011
commit a9b1a9123b
2 changed files with 185 additions and 36 deletions

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}