From 0c5976b28731de0d6050a3c5455a49bd62d7d03b Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Wed, 10 Jan 2018 13:03:07 +0000 Subject: [PATCH] Factor shared Bluetooth code into superclass. --- .../bramble/plugin/AndroidPluginModule.java | 7 +- .../bluetooth/AndroidBluetoothPlugin.java | 223 ++++++++ .../AndroidBluetoothPluginFactory.java} | 8 +- .../AndroidBluetoothTransportConnection.java} | 7 +- .../plugin/droidtooth/DroidtoothPlugin.java | 490 ------------------ .../bramble/util/StringUtils.java | 4 + .../plugin/bluetooth/BluetoothPlugin.java | 222 ++++---- .../bramble/plugin/DesktopPluginModule.java | 7 +- .../plugin/bluetooth/JavaBluetoothPlugin.java | 105 ++++ ...y.java => JavaBluetoothPluginFactory.java} | 8 +- ... => JavaBluetoothTransportConnection.java} | 5 +- 11 files changed, 471 insertions(+), 615 deletions(-) create mode 100644 bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java rename bramble-android/src/main/java/org/briarproject/bramble/plugin/{droidtooth/DroidtoothPluginFactory.java => bluetooth/AndroidBluetoothPluginFactory.java} (89%) rename bramble-android/src/main/java/org/briarproject/bramble/plugin/{droidtooth/DroidtoothTransportConnection.java => bluetooth/AndroidBluetoothTransportConnection.java} (77%) delete mode 100644 bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java rename {bramble-j2se => bramble-core}/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java (65%) create mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java rename bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/{BluetoothPluginFactory.java => JavaBluetoothPluginFactory.java} (87%) rename bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/{BluetoothTransportConnection.java => JavaBluetoothTransportConnection.java} (83%) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java index 0dbc81b3ec..52b2a293d2 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java @@ -13,7 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.LocationUtils; -import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory; +import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.TorPluginFactory; @@ -38,8 +38,9 @@ public class AndroidPluginModule { Application app, LocationUtils locationUtils, DevReporter reporter, EventBus eventBus) { Context appContext = app.getApplicationContext(); - DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor, - androidExecutor, appContext, random, eventBus, backoffFactory); + DuplexPluginFactory bluetooth = + new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, + appContext, random, eventBus, backoffFactory); DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext, locationUtils, reporter, eventBus, torSocketFactory, backoffFactory); diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java new file mode 100644 index 0000000000..8bbfe971d2 --- /dev/null +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -0,0 +1,223 @@ +package org.briarproject.bramble.plugin.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.Backoff; +import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; +import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent; +import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.bramble.util.AndroidUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; +import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE; +import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE; +import static android.bluetooth.BluetoothAdapter.STATE_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static java.util.logging.Level.WARNING; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> + implements EventListener { + + private static final Logger LOG = + Logger.getLogger(AndroidBluetoothPlugin.class.getName()); + + private final AndroidExecutor androidExecutor; + private final Context appContext; + + private volatile boolean wasEnabledByUs = false; + private volatile BluetoothStateReceiver receiver = null; + + // Non-null if the plugin started successfully + private volatile BluetoothAdapter adapter = null; + + AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, + Context appContext, SecureRandom secureRandom, Backoff backoff, + DuplexPluginCallback callback, int maxLatency) { + super(ioExecutor, secureRandom, backoff, callback, maxLatency); + this.androidExecutor = androidExecutor; + this.appContext = appContext; + } + + @Override + void initialiseAdapter() throws IOException { + // BluetoothAdapter.getDefaultAdapter() must be called on a thread + // with a message queue, so submit it to the AndroidExecutor + try { + adapter = androidExecutor.runOnBackgroundThread( + BluetoothAdapter::getDefaultAdapter).get(); + } catch (InterruptedException | ExecutionException e) { + throw new IOException(e); + } + if (adapter == null) { + LOG.info("Bluetooth is not supported"); + throw new IOException(); + } + // Listen for changes to the Bluetooth state + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_STATE_CHANGED); + filter.addAction(ACTION_SCAN_MODE_CHANGED); + receiver = new BluetoothStateReceiver(); + appContext.registerReceiver(receiver, filter); + } + + @Override + boolean isAdapterEnabled() { + return adapter.isEnabled(); + } + + @Override + void enableAdapter() { + if (adapter != null && !adapter.isEnabled()) { + if (adapter.enable()) { + LOG.info("Enabling Bluetooth"); + wasEnabledByUs = true; + } else { + LOG.info("Could not enable Bluetooth"); + } + } + } + + @Override + public void stop() { + super.stop(); + if (receiver != null) appContext.unregisterReceiver(receiver); + disableAdapter(); + } + + private void disableAdapter() { + if (adapter != null && adapter.isEnabled() && wasEnabledByUs) { + if (adapter.disable()) LOG.info("Disabling Bluetooth"); + else LOG.info("Could not disable Bluetooth"); + } + } + + @Override + public boolean isRunning() { + return super.isRunning() && adapter != null && adapter.isEnabled(); + } + + @Override + @Nullable + String getBluetoothAddress() { + String address = AndroidUtils.getBluetoothAddress(appContext, adapter); + return address.isEmpty() ? null : address; + } + + @Override + BluetoothServerSocket openServerSocket(String uuid) throws IOException { + return adapter.listenUsingInsecureRfcommWithServiceRecord( + "RFCOMM", UUID.fromString(uuid)); + } + + @Override + void tryToClose(@Nullable BluetoothServerSocket ss) { + try { + if (ss != null) ss.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + @Override + DuplexTransportConnection acceptConnection(BluetoothServerSocket ss) + throws IOException { + return wrapSocket(ss.accept()); + } + + private DuplexTransportConnection wrapSocket(BluetoothSocket s) { + return new AndroidBluetoothTransportConnection(this, s); + } + + @Override + boolean isValidAddress(String address) { + return BluetoothAdapter.checkBluetoothAddress(address); + } + + @Override + DuplexTransportConnection connectTo(String address, String uuid) + throws IOException { + BluetoothDevice d = adapter.getRemoteDevice(address); + UUID u = UUID.fromString(uuid); + BluetoothSocket s = null; + try { + s = d.createInsecureRfcommSocketToServiceRecord(u); + s.connect(); + return wrapSocket(s); + } catch (IOException e) { + tryToClose(s); + throw e; + } + } + + private void tryToClose(@Nullable Closeable c) { + try { + if (c != null) c.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof EnableBluetoothEvent) { + enableAdapterAsync(); + } else if (e instanceof DisableBluetoothEvent) { + disableAdapterAsync(); + } + } + + private void enableAdapterAsync() { + ioExecutor.execute(this::enableAdapter); + } + + private void disableAdapterAsync() { + ioExecutor.execute(this::disableAdapter); + } + + private class BluetoothStateReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context ctx, Intent intent) { + int state = intent.getIntExtra(EXTRA_STATE, 0); + if (state == STATE_ON) onAdapterEnabled(); + else if (state == STATE_OFF) onAdapterDisabled(); + int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0); + if (scanMode == SCAN_MODE_NONE) { + LOG.info("Scan mode: None"); + } else if (scanMode == SCAN_MODE_CONNECTABLE) { + LOG.info("Scan mode: Connectable"); + } else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + LOG.info("Scan mode: Discoverable"); + } + } + } +} diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java similarity index 89% rename from bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPluginFactory.java rename to bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java index 38c95abefe..57ae5caa89 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java @@ -1,4 +1,4 @@ -package org.briarproject.bramble.plugin.droidtooth; +package org.briarproject.bramble.plugin.bluetooth; import android.content.Context; @@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; @Immutable @NotNullByDefault -public class DroidtoothPluginFactory implements DuplexPluginFactory { +public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute @@ -35,7 +35,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory { private final EventBus eventBus; private final BackoffFactory backoffFactory; - public DroidtoothPluginFactory(Executor ioExecutor, + public AndroidBluetoothPluginFactory(Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, SecureRandom secureRandom, EventBus eventBus, BackoffFactory backoffFactory) { @@ -61,7 +61,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(DuplexPluginCallback callback) { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor, + AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor, androidExecutor, appContext, secureRandom, backoff, callback, MAX_LATENCY); eventBus.addListener(plugin); diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothTransportConnection.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java similarity index 77% rename from bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothTransportConnection.java rename to bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java index bebae98ef7..e9b615e0b1 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothTransportConnection.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java @@ -1,4 +1,4 @@ -package org.briarproject.bramble.plugin.droidtooth; +package org.briarproject.bramble.plugin.bluetooth; import android.bluetooth.BluetoothSocket; @@ -11,11 +11,12 @@ import java.io.InputStream; import java.io.OutputStream; @NotNullByDefault -class DroidtoothTransportConnection extends AbstractDuplexTransportConnection { +class AndroidBluetoothTransportConnection + extends AbstractDuplexTransportConnection { private final BluetoothSocket socket; - DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) { + AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) { super(plugin); this.socket = socket; } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java deleted file mode 100644 index 580c583450..0000000000 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java +++ /dev/null @@ -1,490 +0,0 @@ -package org.briarproject.bramble.plugin.droidtooth; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothServerSocket; -import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; - -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.data.BdfList; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.event.EventListener; -import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; -import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; -import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; -import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.bramble.api.plugin.Backoff; -import org.briarproject.bramble.api.plugin.PluginException; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; -import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; -import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; -import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent; -import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent; -import org.briarproject.bramble.api.properties.TransportProperties; -import org.briarproject.bramble.api.system.AndroidExecutor; -import org.briarproject.bramble.util.AndroidUtils; -import org.briarproject.bramble.util.StringUtils; - -import java.io.Closeable; -import java.io.IOException; -import java.security.SecureRandom; -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Logger; - -import javax.annotation.Nullable; - -import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; -import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; -import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE; -import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; -import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; -import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; -import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE; -import static android.bluetooth.BluetoothAdapter.STATE_OFF; -import static android.bluetooth.BluetoothAdapter.STATE_ON; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; -import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; -import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE; -import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; -import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; -import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; -import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; - -@MethodsNotNullByDefault -@ParametersNotNullByDefault -class DroidtoothPlugin implements DuplexPlugin, EventListener { - - private static final Logger LOG = - Logger.getLogger(DroidtoothPlugin.class.getName()); - - private final Executor ioExecutor; - private final AndroidExecutor androidExecutor; - private final Context appContext; - private final SecureRandom secureRandom; - private final Backoff backoff; - private final DuplexPluginCallback callback; - private final int maxLatency; - private final AtomicBoolean used = new AtomicBoolean(false); - - private volatile boolean running = false; - private volatile boolean wasEnabledByUs = false; - private volatile BluetoothStateReceiver receiver = null; - private volatile BluetoothServerSocket socket = null; - - // Non-null if the plugin started successfully - private volatile BluetoothAdapter adapter = null; - - DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, - Context appContext, SecureRandom secureRandom, Backoff backoff, - DuplexPluginCallback callback, int maxLatency) { - this.ioExecutor = ioExecutor; - this.androidExecutor = androidExecutor; - this.appContext = appContext; - this.secureRandom = secureRandom; - this.backoff = backoff; - this.callback = callback; - this.maxLatency = maxLatency; - } - - @Override - public TransportId getId() { - return ID; - } - - @Override - public int getMaxLatency() { - return maxLatency; - } - - @Override - public int getMaxIdleTime() { - // Bluetooth detects dead connections so we don't need keepalives - return Integer.MAX_VALUE; - } - - @Override - public void start() throws PluginException { - if (used.getAndSet(true)) throw new IllegalStateException(); - // BluetoothAdapter.getDefaultAdapter() must be called on a thread - // with a message queue, so submit it to the AndroidExecutor - try { - adapter = androidExecutor.runOnBackgroundThread( - BluetoothAdapter::getDefaultAdapter).get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.warning("Interrupted while getting BluetoothAdapter"); - throw new PluginException(e); - } catch (ExecutionException e) { - throw new PluginException(e); - } - if (adapter == null) { - LOG.info("Bluetooth is not supported"); - throw new PluginException(); - } - running = true; - // Listen for changes to the Bluetooth state - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_STATE_CHANGED); - filter.addAction(ACTION_SCAN_MODE_CHANGED); - receiver = new BluetoothStateReceiver(); - appContext.registerReceiver(receiver, filter); - // If Bluetooth is enabled, bind a socket - if (adapter.isEnabled()) { - bind(); - } else { - // Enable Bluetooth if settings allow - if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) { - enableAdapter(); - } else { - LOG.info("Not enabling Bluetooth"); - } - } - } - - private void bind() { - ioExecutor.execute(() -> { - if (!isRunning()) return; - String address = AndroidUtils.getBluetoothAddress(appContext, - adapter); - if (LOG.isLoggable(INFO)) - LOG.info("Local address " + scrubMacAddress(address)); - if (!StringUtils.isNullOrEmpty(address)) { - // Advertise the Bluetooth address to contacts - TransportProperties p = new TransportProperties(); - p.put(PROP_ADDRESS, address); - callback.mergeLocalProperties(p); - } - // Bind a server socket to accept connections from contacts - BluetoothServerSocket ss; - try { - ss = adapter.listenUsingInsecureRfcommWithServiceRecord( - "RFCOMM", getUuid()); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - return; - } - if (!isRunning()) { - tryToClose(ss); - return; - } - LOG.info("Socket bound"); - socket = ss; - backoff.reset(); - callback.transportEnabled(); - acceptContactConnections(); - }); - } - - private UUID getUuid() { - 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(PROP_UUID, uuid); - callback.mergeLocalProperties(p); - } - return UUID.fromString(uuid); - } - - private void tryToClose(@Nullable BluetoothServerSocket ss) { - try { - if (ss != null) ss.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - callback.transportDisabled(); - } - } - - private void acceptContactConnections() { - while (isRunning()) { - BluetoothSocket s; - try { - s = socket.accept(); - } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); - return; - } - if (LOG.isLoggable(INFO)) { - String address = s.getRemoteDevice().getAddress(); - LOG.info("Connection from " + scrubMacAddress(address)); - } - backoff.reset(); - callback.incomingConnectionCreated(wrapSocket(s)); - } - } - - private DuplexTransportConnection wrapSocket(BluetoothSocket s) { - return new DroidtoothTransportConnection(this, s); - } - - private void enableAdapter() { - if (adapter != null && !adapter.isEnabled()) { - if (adapter.enable()) { - LOG.info("Enabling Bluetooth"); - wasEnabledByUs = true; - } else { - LOG.info("Could not enable Bluetooth"); - } - } - } - - @Override - public void stop() { - running = false; - if (receiver != null) appContext.unregisterReceiver(receiver); - tryToClose(socket); - disableAdapter(); - } - - private void disableAdapter() { - if (adapter != null && adapter.isEnabled() && wasEnabledByUs) { - if (adapter.disable()) LOG.info("Disabling Bluetooth"); - else LOG.info("Could not disable Bluetooth"); - } - } - - @Override - public boolean isRunning() { - return running && adapter != null && adapter.isEnabled(); - } - - @Override - public boolean shouldPoll() { - return true; - } - - @Override - public int getPollingInterval() { - return backoff.getPollingInterval(); - } - - @Override - public void poll(Collection<ContactId> connected) { - if (!isRunning()) return; - backoff.increment(); - // Try to connect to known devices in parallel - Map<ContactId, TransportProperties> remote = - callback.getRemoteProperties(); - for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { - ContactId c = e.getKey(); - if (connected.contains(c)) continue; - String address = e.getValue().get(PROP_ADDRESS); - if (StringUtils.isNullOrEmpty(address)) continue; - String uuid = e.getValue().get(PROP_UUID); - if (StringUtils.isNullOrEmpty(uuid)) continue; - ioExecutor.execute(() -> { - if (!running) return; - BluetoothSocket s = connect(address, uuid); - if (s != null) { - backoff.reset(); - callback.outgoingConnectionCreated(c, wrapSocket(s)); - } - }); - } - } - - @Nullable - private BluetoothSocket connect(String address, String uuid) { - // Validate the address - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - if (LOG.isLoggable(WARNING)) - // not scrubbing here to be able to figure out the problem - LOG.warning("Invalid address " + address); - return null; - } - // Validate the UUID - UUID u; - try { - u = UUID.fromString(uuid); - } catch (IllegalArgumentException e) { - if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid); - return null; - } - // Try to connect - BluetoothDevice d = adapter.getRemoteDevice(address); - BluetoothSocket s = null; - try { - s = d.createInsecureRfcommSocketToServiceRecord(u); - if (LOG.isLoggable(INFO)) - LOG.info("Connecting to " + scrubMacAddress(address)); - s.connect(); - if (LOG.isLoggable(INFO)) - LOG.info("Connected to " + scrubMacAddress(address)); - return s; - } catch (IOException e) { - if (LOG.isLoggable(INFO)) { - LOG.info("Failed to connect to " + scrubMacAddress(address) - + ": " + e); - } - tryToClose(s); - return null; - } - } - - private void tryToClose(@Nullable Closeable c) { - try { - if (c != null) c.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - - @Override - public DuplexTransportConnection createConnection(ContactId c) { - if (!isRunning()) return null; - TransportProperties p = callback.getRemoteProperties(c); - String address = p.get(PROP_ADDRESS); - if (StringUtils.isNullOrEmpty(address)) return null; - String uuid = p.get(PROP_UUID); - if (StringUtils.isNullOrEmpty(uuid)) return null; - BluetoothSocket s = connect(address, uuid); - if (s == null) return null; - return new DroidtoothTransportConnection(this, s); - } - - @Override - public boolean supportsKeyAgreement() { - return true; - } - - @Override - public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { - if (!isRunning()) return null; - // There's no point listening if we can't discover our own address - String address = AndroidUtils.getBluetoothAddress(appContext, adapter); - if (address.isEmpty()) return null; - // No truncation necessary because COMMIT_LENGTH = 16 - UUID uuid = UUID.nameUUIDFromBytes(commitment); - if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); - // Bind a server socket for receiving key agreement connections - BluetoothServerSocket ss; - try { - ss = adapter.listenUsingInsecureRfcommWithServiceRecord( - "RFCOMM", uuid); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - return null; - } - BdfList descriptor = new BdfList(); - descriptor.add(TRANSPORT_ID_BLUETOOTH); - descriptor.add(StringUtils.macToBytes(address)); - return new BluetoothKeyAgreementListener(descriptor, ss); - } - - @Override - public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, long timeout) { - if (!isRunning()) return null; - String address; - try { - address = parseAddress(descriptor); - } catch (FormatException e) { - LOG.info("Invalid address in key agreement descriptor"); - return null; - } - // No truncation necessary because COMMIT_LENGTH = 16 - UUID uuid = UUID.nameUUIDFromBytes(commitment); - 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 String parseAddress(BdfList descriptor) throws FormatException { - byte[] mac = descriptor.getRaw(1); - if (mac.length != 6) throw new FormatException(); - return StringUtils.macToString(mac); - } - - @Override - public void eventOccurred(Event e) { - if (e instanceof EnableBluetoothEvent) { - enableAdapterAsync(); - } else if (e instanceof DisableBluetoothEvent) { - disableAdapterAsync(); - } - } - - private void enableAdapterAsync() { - ioExecutor.execute(this::enableAdapter); - } - - private void disableAdapterAsync() { - ioExecutor.execute(this::disableAdapter); - } - - private class BluetoothStateReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context ctx, Intent intent) { - int state = intent.getIntExtra(EXTRA_STATE, 0); - if (state == STATE_ON) { - LOG.info("Bluetooth enabled"); - bind(); - } else if (state == STATE_OFF) { - LOG.info("Bluetooth disabled"); - tryToClose(socket); - } - int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0); - if (scanMode == SCAN_MODE_NONE) { - LOG.info("Scan mode: None"); - } else if (scanMode == SCAN_MODE_CONNECTABLE) { - LOG.info("Scan mode: Connectable"); - } else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - LOG.info("Scan mode: Discoverable"); - } - } - } - - private class BluetoothKeyAgreementListener extends KeyAgreementListener { - - private final BluetoothServerSocket ss; - - private BluetoothKeyAgreementListener(BdfList descriptor, - BluetoothServerSocket ss) { - super(descriptor); - this.ss = ss; - } - - @Override - public Callable<KeyAgreementConnection> listen() { - return () -> { - 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/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java index 7e1556fb51..b6d082098e 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java @@ -126,6 +126,10 @@ public class StringUtils { return toUtf8(s).length > maxLength; } + public static boolean isValidMac(String mac) { + return MAC.matcher(mac).matches(); + } + public static byte[] macToBytes(String mac) { if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException(); return fromHexString(mac.replaceAll(":", "")); diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java similarity index 65% rename from bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java rename to bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index 6e119909a8..4da5916f4a 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -14,7 +14,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; -import org.briarproject.bramble.util.OsUtils; import org.briarproject.bramble.util.StringUtils; import java.io.IOException; @@ -29,29 +28,26 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import javax.annotation.Nullable; -import javax.bluetooth.BluetoothStateException; -import javax.bluetooth.LocalDevice; -import javax.microedition.io.Connector; -import javax.microedition.io.StreamConnection; -import javax.microedition.io.StreamConnectionNotifier; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -import static javax.bluetooth.DiscoveryAgent.GIAC; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; +import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; @MethodsNotNullByDefault @ParametersNotNullByDefault -class BluetoothPlugin implements DuplexPlugin { +abstract class BluetoothPlugin<SS> implements DuplexPlugin { private static final Logger LOG = Logger.getLogger(BluetoothPlugin.class.getName()); - private final Executor ioExecutor; + final Executor ioExecutor; + private final SecureRandom secureRandom; private final Backoff backoff; private final DuplexPluginCallback callback; @@ -59,8 +55,28 @@ class BluetoothPlugin implements DuplexPlugin { private final AtomicBoolean used = new AtomicBoolean(false); private volatile boolean running = false; - private volatile StreamConnectionNotifier socket = null; - private volatile LocalDevice localDevice = null; + private volatile SS socket = null; + + abstract void initialiseAdapter() throws IOException; + + abstract boolean isAdapterEnabled(); + + abstract void enableAdapter(); + + @Nullable + abstract String getBluetoothAddress(); + + abstract SS openServerSocket(String uuid) throws IOException; + + abstract void tryToClose(@Nullable SS ss); + + abstract DuplexTransportConnection acceptConnection(SS ss) + throws IOException; + + abstract boolean isValidAddress(String address); + + abstract DuplexTransportConnection connectTo(String address, String uuid) + throws IOException; BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) { @@ -71,6 +87,17 @@ class BluetoothPlugin implements DuplexPlugin { this.maxLatency = maxLatency; } + void onAdapterEnabled() { + LOG.info("Bluetooth enabled"); + bind(); + } + + void onAdapterDisabled() { + LOG.info("Bluetooth disabled"); + tryToClose(socket); + callback.transportDisabled(); + } + @Override public TransportId getId() { return ID; @@ -90,55 +117,56 @@ class BluetoothPlugin implements DuplexPlugin { @Override public void start() throws PluginException { if (used.getAndSet(true)) throw new IllegalStateException(); - // Initialise the Bluetooth stack 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 new PluginException(e); - } catch (BluetoothStateException e) { + initialiseAdapter(); + } catch (IOException e) { throw new PluginException(e); } - if (LOG.isLoggable(INFO)) - LOG.info("Local address " + localDevice.getBluetoothAddress()); running = true; - bind(); + // If Bluetooth is enabled, bind a socket + if (isAdapterEnabled()) { + bind(); + } else { + // Enable Bluetooth if settings allow + if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) { + enableAdapter(); + } else { + LOG.info("Not enabling Bluetooth"); + } + } } private void bind() { ioExecutor.execute(() -> { - if (!running) return; - // Advertise the Bluetooth address to contacts - TransportProperties p = new TransportProperties(); - p.put(PROP_ADDRESS, localDevice.getBluetoothAddress()); - callback.mergeLocalProperties(p); + if (!isRunning()) return; + String address = getBluetoothAddress(); + if (LOG.isLoggable(INFO)) + LOG.info("Local address " + scrubMacAddress(address)); + if (!StringUtils.isNullOrEmpty(address)) { + // Advertise our Bluetooth address to contacts + TransportProperties p = new TransportProperties(); + p.put(PROP_ADDRESS, address); + callback.mergeLocalProperties(p); + } // Bind a server socket to accept connections from contacts - String url = makeUrl("localhost", getUuid()); - StreamConnectionNotifier ss; + SS ss; try { - ss = (StreamConnectionNotifier) Connector.open(url); + ss = openServerSocket(getUuid()); } catch (IOException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return; } - if (!running) { + if (!isRunning()) { tryToClose(ss); return; } socket = ss; backoff.reset(); callback.transportEnabled(); - acceptContactConnections(ss); + acceptContactConnections(); }); } - private String makeUrl(String address, String uuid) { - return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; - } - private String getUuid() { String uuid = callback.getLocalProperties().get(PROP_UUID); if (uuid == null) { @@ -152,40 +180,27 @@ class BluetoothPlugin implements DuplexPlugin { return uuid; } - private void tryToClose(@Nullable StreamConnectionNotifier ss) { - try { - if (ss != null) ss.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - callback.transportDisabled(); - } - } - - private void acceptContactConnections(StreamConnectionNotifier ss) { + private void acceptContactConnections() { while (true) { - StreamConnection s; + DuplexTransportConnection conn; try { - s = ss.acceptAndOpen(); + conn = acceptConnection(socket); } catch (IOException e) { // This is expected when the socket is closed if (LOG.isLoggable(INFO)) LOG.info(e.toString()); return; } backoff.reset(); - callback.incomingConnectionCreated(wrapSocket(s)); + callback.incomingConnectionCreated(conn); if (!running) return; } } - private DuplexTransportConnection wrapSocket(StreamConnection s) { - return new BluetoothTransportConnection(this, s); - } - @Override public void stop() { running = false; tryToClose(socket); + callback.transportDisabled(); } @Override @@ -205,7 +220,7 @@ class BluetoothPlugin implements DuplexPlugin { @Override public void poll(Collection<ContactId> connected) { - if (!running) return; + if (!isRunning()) return; backoff.increment(); // Try to connect to known devices in parallel Map<ContactId, TransportProperties> remote = @@ -218,41 +233,56 @@ class BluetoothPlugin implements DuplexPlugin { String uuid = e.getValue().get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) continue; ioExecutor.execute(() -> { - if (!running) return; - StreamConnection s = connect(makeUrl(address, uuid)); - if (s != null) { + if (!isRunning()) return; + DuplexTransportConnection conn = connect(address, uuid); + if (conn != null) { backoff.reset(); - callback.outgoingConnectionCreated(c, wrapSocket(s)); + callback.outgoingConnectionCreated(c, conn); } }); } } @Nullable - private StreamConnection connect(String url) { - if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + url); + private DuplexTransportConnection connect(String address, String uuid) { + // Validate the address + if (!isValidAddress(address)) { + if (LOG.isLoggable(WARNING)) + // Not scrubbing here to be able to figure out the problem + LOG.warning("Invalid address " + address); + return null; + } + // Validate the UUID + try { + //noinspection ResultOfMethodCallIgnored + UUID.fromString(uuid); + } catch (IllegalArgumentException e) { + if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid); + return null; + } + if (LOG.isLoggable(INFO)) + LOG.info("Connecting to " + scrubMacAddress(address)); try { - StreamConnection s = (StreamConnection) Connector.open(url); - if (LOG.isLoggable(INFO)) LOG.info("Connected to " + url); - return s; + DuplexTransportConnection conn = connectTo(address, uuid); + if (LOG.isLoggable(INFO)) + LOG.info("Connected to " + scrubMacAddress(address)); + return conn; } catch (IOException e) { - if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + url); + if (LOG.isLoggable(INFO)) + LOG.info("Could not connect to " + scrubMacAddress(address)); return null; } } @Override public DuplexTransportConnection createConnection(ContactId c) { - if (!running) return null; + if (!isRunning()) return null; TransportProperties p = callback.getRemoteProperties(c); String address = p.get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) return null; String uuid = p.get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) return null; - String url = makeUrl(address, uuid); - StreamConnection s = connect(url); - if (s == null) return null; - return new BluetoothTransportConnection(this, s); + return connect(address, uuid); } @Override @@ -262,28 +292,27 @@ class BluetoothPlugin implements DuplexPlugin { @Override public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { - if (!running) return null; + if (!isRunning()) return null; + // There's no point listening if we can't discover our own address + String address = getBluetoothAddress(); + if (address == null) return null; // No truncation necessary because COMMIT_LENGTH = 16 String uuid = UUID.nameUUIDFromBytes(commitment).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 key agreementconnections - StreamConnectionNotifier ss; + // Bind a server socket for receiving key agreement connections + SS ss; try { - ss = (StreamConnectionNotifier) Connector.open(url); + ss = openServerSocket(uuid); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return null; } - if (!running) { + if (!isRunning()) { tryToClose(ss); return null; } BdfList descriptor = new BdfList(); descriptor.add(TRANSPORT_ID_BLUETOOTH); - String address = localDevice.getBluetoothAddress(); descriptor.add(StringUtils.macToBytes(address)); return new BluetoothKeyAgreementListener(descriptor, ss); } @@ -303,10 +332,7 @@ class BluetoothPlugin implements DuplexPlugin { String uuid = UUID.nameUUIDFromBytes(commitment).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); + return connect(address, uuid); } private String parseAddress(BdfList descriptor) throws FormatException { @@ -315,21 +341,11 @@ class BluetoothPlugin implements DuplexPlugin { return StringUtils.macToString(mac); } - private void makeDeviceDiscoverable() { - // Try to make the device discoverable (requires root on Linux) - try { - localDevice.setDiscoverable(GIAC); - } catch (BluetoothStateException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - private class BluetoothKeyAgreementListener extends KeyAgreementListener { - private final StreamConnectionNotifier ss; + private final SS ss; - private BluetoothKeyAgreementListener(BdfList descriptor, - StreamConnectionNotifier ss) { + private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) { super(descriptor); this.ss = ss; } @@ -337,22 +353,16 @@ class BluetoothPlugin implements DuplexPlugin { @Override public Callable<KeyAgreementConnection> listen() { return () -> { - StreamConnection s = ss.acceptAndOpen(); + DuplexTransportConnection conn = acceptConnection(ss); if (LOG.isLoggable(INFO)) LOG.info(ID.getString() + ": Incoming connection"); - return new KeyAgreementConnection( - new BluetoothTransportConnection( - BluetoothPlugin.this, s), ID); + return new KeyAgreementConnection(conn, ID); }; } @Override public void close() { - try { - ss.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } + tryToClose(ss); } } } diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java index a2a8f0b8d2..fdd1b4fb9c 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java @@ -8,7 +8,7 @@ import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory; -import org.briarproject.bramble.plugin.bluetooth.BluetoothPluginFactory; +import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory; import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory; import org.briarproject.bramble.plugin.modem.ModemPluginFactory; import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory; @@ -31,8 +31,9 @@ public class DesktopPluginModule extends PluginModule { SecureRandom random, BackoffFactory backoffFactory, ReliabilityLayerFactory reliabilityFactory, ShutdownManager shutdownManager) { - DuplexPluginFactory bluetooth = new BluetoothPluginFactory(ioExecutor, - random, backoffFactory); + DuplexPluginFactory bluetooth = + new JavaBluetoothPluginFactory(ioExecutor, random, + backoffFactory); DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, reliabilityFactory); DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java new file mode 100644 index 0000000000..02bf0e8f09 --- /dev/null +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java @@ -0,0 +1,105 @@ +package org.briarproject.bramble.plugin.bluetooth; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.Backoff; +import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; +import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.bluetooth.BluetoothStateException; +import javax.bluetooth.LocalDevice; +import javax.microedition.io.Connector; +import javax.microedition.io.StreamConnection; +import javax.microedition.io.StreamConnectionNotifier; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.StringUtils.isValidMac; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> { + + private static final Logger LOG = + Logger.getLogger(JavaBluetoothPlugin.class.getName()); + + // Non-null if the plugin started successfully + private volatile LocalDevice localDevice = null; + + JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, + Backoff backoff, DuplexPluginCallback callback, int maxLatency) { + super(ioExecutor, secureRandom, backoff, callback, maxLatency); + } + + @Override + void initialiseAdapter() throws IOException { + try { + localDevice = LocalDevice.getLocalDevice(); + } catch (UnsatisfiedLinkError | BluetoothStateException e) { + throw new IOException(e); + } + } + + @Override + boolean isAdapterEnabled() { + return LocalDevice.isPowerOn(); + } + + @Override + void enableAdapter() { + // Nothing we can do on this platform + LOG.info("Could not enable Bluetooth"); + } + + @Nullable + @Override + String getBluetoothAddress() { + return localDevice.getBluetoothAddress(); + } + + @Override + StreamConnectionNotifier openServerSocket(String uuid) throws IOException { + String url = makeUrl("localhost", uuid); + return (StreamConnectionNotifier) Connector.open(url); + } + + @Override + void tryToClose(@Nullable StreamConnectionNotifier ss) { + try { + if (ss != null) ss.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + @Override + DuplexTransportConnection acceptConnection(StreamConnectionNotifier ss) + throws IOException { + return wrapSocket(ss.acceptAndOpen()); + } + + @Override + boolean isValidAddress(String address) { + return isValidMac(address); + } + + @Override + DuplexTransportConnection connectTo(String address, String uuid) + throws IOException { + String url = makeUrl(address, uuid); + return wrapSocket((StreamConnection) Connector.open(url)); + } + + private String makeUrl(String address, String uuid) { + return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; + } + + private DuplexTransportConnection wrapSocket(StreamConnection s) { + return new JavaBluetoothTransportConnection(this, s); + } +} diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPluginFactory.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java similarity index 87% rename from bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPluginFactory.java rename to bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java index e8dc0a754a..f5ddaed332 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPluginFactory.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java @@ -17,7 +17,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; @Immutable @NotNullByDefault -public class BluetoothPluginFactory implements DuplexPluginFactory { +public class JavaBluetoothPluginFactory implements DuplexPluginFactory { private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute @@ -28,7 +28,7 @@ public class BluetoothPluginFactory implements DuplexPluginFactory { private final SecureRandom secureRandom; private final BackoffFactory backoffFactory; - public BluetoothPluginFactory(Executor ioExecutor, + public JavaBluetoothPluginFactory(Executor ioExecutor, SecureRandom secureRandom, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; @@ -49,7 +49,7 @@ public class BluetoothPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(DuplexPluginCallback callback) { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback, - MAX_LATENCY); + return new JavaBluetoothPlugin(ioExecutor, secureRandom, backoff, + callback, MAX_LATENCY); } } diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothTransportConnection.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java similarity index 83% rename from bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothTransportConnection.java rename to bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java index 4a8849c7b0..ffb0f6d4f8 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothTransportConnection.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java @@ -11,11 +11,12 @@ import java.io.OutputStream; import javax.microedition.io.StreamConnection; @NotNullByDefault -class BluetoothTransportConnection extends AbstractDuplexTransportConnection { +class JavaBluetoothTransportConnection + extends AbstractDuplexTransportConnection { private final StreamConnection stream; - BluetoothTransportConnection(Plugin plugin, StreamConnection stream) { + JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) { super(plugin); this.stream = stream; } -- GitLab