diff --git a/src/net/sf/briar/invitation/AliceConnector.java b/src/net/sf/briar/invitation/AliceConnector.java new file mode 100644 index 0000000000000000000000000000000000000000..0d8bd5b43f55bce09b3b75d3279de40aa365e5f0 --- /dev/null +++ b/src/net/sf/briar/invitation/AliceConnector.java @@ -0,0 +1,121 @@ +package net.sf.briar.invitation; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH; +import static net.sf.briar.api.plugins.InvitationConstants.INVITATION_TIMEOUT; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import net.sf.briar.api.crypto.PseudoRandom; +import net.sf.briar.api.invitation.ConnectionCallback; +import net.sf.briar.api.plugins.duplex.DuplexPlugin; +import net.sf.briar.api.plugins.duplex.DuplexTransportConnection; + +class AliceConnector extends Thread { + + private static final Logger LOG = + Logger.getLogger(AliceConnector.class.getName()); + + private final DuplexPlugin plugin; + private final PseudoRandom random; + private final ConnectionCallback callback; + private final AtomicBoolean connected, succeeded; + private final String pluginName; + + AliceConnector(DuplexPlugin plugin, PseudoRandom random, + ConnectionCallback callback, AtomicBoolean connected, + AtomicBoolean succeeded) { + this.plugin = plugin; + this.random = random; + this.callback = callback; + this.connected = connected; + this.succeeded = succeeded; + pluginName = plugin.getClass().getName(); + } + + @Override + public void run() { + long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2; + DuplexTransportConnection conn = makeOutgoingConnection(); + if(conn == null) conn = acceptIncomingConnection(halfTime); + if(conn == null) return; + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected"); + // Don't proceed with more than one connection + if(connected.getAndSet(true)) { + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant"); + tryToClose(conn, false); + return; + } + // FIXME: Carry out the real invitation protocol + InputStream in; + try { + in = conn.getInputStream(); + OutputStream out = conn.getOutputStream(); + byte[] hash = random.nextBytes(HASH_LENGTH); + out.write(hash); + out.flush(); + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash"); + int offset = 0; + while(offset < hash.length) { + int read = in.read(hash, offset, hash.length - offset); + if(read == -1) break; + offset += read; + } + if(offset < HASH_LENGTH) throw new EOFException(); + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash"); + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded"); + succeeded.set(true); + callback.connectionEstablished(123456, 123456, + new ConfirmationSender(out)); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + tryToClose(conn, true); + return; + } + try { + if(in.read() == 1) callback.codesMatch(); + else callback.codesDoNotMatch(); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + tryToClose(conn, true); + callback.codesDoNotMatch(); + } + } + + private DuplexTransportConnection makeOutgoingConnection() { + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " making outgoing connection"); + return plugin.sendInvitation(random, INVITATION_TIMEOUT / 2); + } + + private DuplexTransportConnection acceptIncomingConnection(long halfTime) { + long now = System.currentTimeMillis(); + if(now < halfTime) { + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " sleeping until half-time"); + try { + Thread.sleep(halfTime - now); + } catch(InterruptedException e) { + if(LOG.isLoggable(INFO)) LOG.info("Interrupted while sleeping"); + return null; + } + } + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " accepting incoming connection"); + return plugin.acceptInvitation(random, INVITATION_TIMEOUT / 2); + } + + private void tryToClose(DuplexTransportConnection conn, boolean exception) { + try { + conn.dispose(exception, true); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } + } +} \ No newline at end of file diff --git a/src/net/sf/briar/invitation/BobConnector.java b/src/net/sf/briar/invitation/BobConnector.java new file mode 100644 index 0000000000000000000000000000000000000000..a895d638ecbf66fcef674f9eefe9707521024ee2 --- /dev/null +++ b/src/net/sf/briar/invitation/BobConnector.java @@ -0,0 +1,121 @@ +package net.sf.briar.invitation; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH; +import static net.sf.briar.api.plugins.InvitationConstants.INVITATION_TIMEOUT; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import net.sf.briar.api.crypto.PseudoRandom; +import net.sf.briar.api.invitation.ConnectionCallback; +import net.sf.briar.api.plugins.duplex.DuplexPlugin; +import net.sf.briar.api.plugins.duplex.DuplexTransportConnection; + +class BobConnector extends Thread { + + private static final Logger LOG = + Logger.getLogger(BobConnector.class.getName()); + + private final DuplexPlugin plugin; + private final PseudoRandom random; + private final ConnectionCallback callback; + private final AtomicBoolean connected, succeeded; + private final String pluginName; + + BobConnector(DuplexPlugin plugin, PseudoRandom random, + ConnectionCallback callback, AtomicBoolean connected, + AtomicBoolean succeeded) { + this.plugin = plugin; + this.random = random; + this.callback = callback; + this.connected = connected; + this.succeeded = succeeded; + pluginName = plugin.getClass().getName(); + } + + @Override + public void run() { + long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2; + DuplexTransportConnection conn = acceptIncomingConnection(); + if(conn == null) conn = makeOutgoingConnection(halfTime); + if(conn == null) return; + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected"); + // FIXME: Carry out the real invitation protocol + InputStream in; + try { + in = conn.getInputStream(); + OutputStream out = conn.getOutputStream(); + byte[] hash = new byte[HASH_LENGTH]; + int offset = 0; + while(offset < hash.length) { + int read = in.read(hash, offset, hash.length - offset); + if(read == -1) break; + offset += read; + } + if(offset < HASH_LENGTH) throw new EOFException(); + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash"); + // Don't proceed with more than one connection + if(connected.getAndSet(true)) { + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " redundant"); + tryToClose(conn, false); + return; + } + out.write(hash); + out.flush(); + if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash"); + succeeded.set(true); + callback.connectionEstablished(123456, 123456, + new ConfirmationSender(out)); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + tryToClose(conn, true); + return; + } + try { + if(in.read() == 1) callback.codesMatch(); + else callback.codesDoNotMatch(); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + tryToClose(conn, true); + callback.codesDoNotMatch(); + } + } + + private DuplexTransportConnection acceptIncomingConnection() { + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " accepting incoming connection"); + return plugin.acceptInvitation(random, INVITATION_TIMEOUT / 2); + } + + private DuplexTransportConnection makeOutgoingConnection(long halfTime) { + long now = System.currentTimeMillis(); + if(now < halfTime) { + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " sleeping until half-time"); + try { + Thread.sleep(halfTime - now); + } catch(InterruptedException e) { + if(LOG.isLoggable(INFO)) LOG.info("Interrupted while sleeping"); + return null; + } + } + if(LOG.isLoggable(INFO)) + LOG.info(pluginName + " making outgoing connection"); + return plugin.sendInvitation(random, INVITATION_TIMEOUT / 2); + } + + private void tryToClose(DuplexTransportConnection conn, boolean exception) { + try { + conn.dispose(exception, true); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } + } +} diff --git a/src/net/sf/briar/invitation/ConfirmationSender.java b/src/net/sf/briar/invitation/ConfirmationSender.java new file mode 100644 index 0000000000000000000000000000000000000000..7542b49f02c4ee4e77b2a473c96ad43fdb20f837 --- /dev/null +++ b/src/net/sf/briar/invitation/ConfirmationSender.java @@ -0,0 +1,38 @@ +package net.sf.briar.invitation; + +import static java.util.logging.Level.WARNING; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.logging.Logger; + +import net.sf.briar.api.invitation.ConfirmationCallback; + +class ConfirmationSender implements ConfirmationCallback { + + private static final Logger LOG = + Logger.getLogger(ConfirmationSender.class.getName()); + + private final OutputStream out; + + ConfirmationSender(OutputStream out) { + this.out = out; + } + + public void codesMatch() { + write(1); + } + + public void codesDoNotMatch() { + write(0); + } + + private void write(int b) { + try { + out.write(b); + out.flush(); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } + } +} diff --git a/src/net/sf/briar/invitation/FailureNotifier.java b/src/net/sf/briar/invitation/FailureNotifier.java new file mode 100644 index 0000000000000000000000000000000000000000..7379a7e930c85bf70d2d4a0a66fb689b184b66bf --- /dev/null +++ b/src/net/sf/briar/invitation/FailureNotifier.java @@ -0,0 +1,42 @@ +package net.sf.briar.invitation; + +import static java.util.logging.Level.INFO; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import net.sf.briar.api.invitation.ConnectionCallback; + +class FailureNotifier extends Thread { + + private static final Logger LOG = + Logger.getLogger(FailureNotifier.class.getName()); + + private final Collection<Thread> workers; + private final AtomicBoolean succeeded; + private final ConnectionCallback callback; + + FailureNotifier(Collection<Thread> workers, AtomicBoolean succeeded, + ConnectionCallback callback) { + this.workers = workers; + this.succeeded = succeeded; + this.callback = callback; + } + + @Override + public void run() { + if(LOG.isLoggable(INFO)) LOG.info(workers.size() + " workers"); + try { + for(Thread worker : workers) worker.join(); + if(!succeeded.get()) { + if(LOG.isLoggable(INFO)) LOG.info("No worker succeeded"); + callback.connectionNotEstablished(); + } + } catch(InterruptedException e) { + if(LOG.isLoggable(INFO)) + LOG.info("Interrupted while waiting for workers"); + callback.connectionNotEstablished(); + } + } +} diff --git a/src/net/sf/briar/invitation/InvitationManagerImpl.java b/src/net/sf/briar/invitation/InvitationManagerImpl.java index 4d4967a068d858b697e5383724c16d38b75060b0..a098add93ac3e36fa86b0dd0ee8deb5a4fd76a14 100644 --- a/src/net/sf/briar/invitation/InvitationManagerImpl.java +++ b/src/net/sf/briar/invitation/InvitationManagerImpl.java @@ -1,10 +1,11 @@ package net.sf.briar.invitation; +import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.PseudoRandom; -import net.sf.briar.api.invitation.ConfirmationCallback; import net.sf.briar.api.invitation.ConnectionCallback; import net.sf.briar.api.invitation.InvitationManager; import net.sf.briar.api.plugins.PluginManager; @@ -27,55 +28,37 @@ class InvitationManagerImpl implements InvitationManager { Collection<DuplexPlugin> plugins = pluginManager.getInvitationPlugins(); // Alice is the party with the smaller invitation code if(localCode < remoteCode) { - PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode); - startAliceInvitationWorker(plugins, r, c); + startAliceWorkers(plugins, localCode, remoteCode, c); } else { - PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode); - startBobInvitationWorker(plugins, r, c); + startBobWorkers(plugins, localCode, remoteCode, c); } } - private void startAliceInvitationWorker(Collection<DuplexPlugin> plugins, - PseudoRandom r, ConnectionCallback c) { - // FIXME - new FakeWorkerThread(c).start(); - } - - private void startBobInvitationWorker(Collection<DuplexPlugin> plugins, - PseudoRandom r, ConnectionCallback c) { - // FIXME - new FakeWorkerThread(c).start(); - } - - private static class FakeWorkerThread extends Thread { - - private final ConnectionCallback callback; - - private FakeWorkerThread(ConnectionCallback callback) { - this.callback = callback; + private void startAliceWorkers(Collection<DuplexPlugin> plugins, + int localCode, int remoteCode, ConnectionCallback c) { + AtomicBoolean connected = new AtomicBoolean(false); + AtomicBoolean succeeded = new AtomicBoolean(false); + Collection<Thread> workers = new ArrayList<Thread>(); + for(DuplexPlugin p : plugins) { + PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode); + Thread worker = new AliceConnector(p, r, c, connected, succeeded); + workers.add(worker); + worker.start(); } + new FailureNotifier(workers, succeeded, c).start(); + } - @Override - public void run() { - try { - Thread.sleep((long) (Math.random() * 30 * 1000)); - } catch(InterruptedException ignored) {} - if(Math.random() < 0.8) { - callback.connectionNotEstablished(); - } else { - callback.connectionEstablished(123456, 123456, - new ConfirmationCallback() { - - public void codesMatch() {} - - public void codesDoNotMatch() {} - }); - try { - Thread.sleep((long) (Math.random() * 10 * 1000)); - } catch(InterruptedException ignored) {} - if(Math.random() < 0.5) callback.codesMatch(); - else callback.codesDoNotMatch(); - } + private void startBobWorkers(Collection<DuplexPlugin> plugins, + int localCode, int remoteCode, ConnectionCallback c) { + AtomicBoolean connected = new AtomicBoolean(false); + AtomicBoolean succeeded = new AtomicBoolean(false); + Collection<Thread> workers = new ArrayList<Thread>(); + for(DuplexPlugin p : plugins) { + PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode); + Thread worker = new BobConnector(p, r, c, connected, succeeded); + workers.add(worker); + worker.start(); } + new FailureNotifier(workers, succeeded, c).start(); } } diff --git a/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java b/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java index bfd543d06f91677f21c4725c3a5cc7713ffb6f22..e9ddc1daab114bace343df3a678798e42c91ccc3 100644 --- a/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java +++ b/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java @@ -1,14 +1,10 @@ package net.sf.briar.plugins.droidtooth; -import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; -import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION; import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; -import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.STATE_OFF; import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; @@ -265,6 +261,7 @@ class DroidtoothPlugin implements DuplexPlugin { // Try to connect try { BluetoothSocket s = InsecureBluetooth.createSocket(d, u); + s.connect(); return new DroidtoothTransportConnection(s); } catch(IOException e) { if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); @@ -320,8 +317,6 @@ class DroidtoothPlugin implements DuplexPlugin { } // Use the same pseudo-random UUID as the contact UUID uuid = UUID.nameUUIDFromBytes(r.nextBytes(16)); - // Make the device discoverable if the user allows it - makeDeviceDiscoverable(); // Bind a new server socket to accept the invitation connection final BluetoothServerSocket ss; try { @@ -344,17 +339,6 @@ class DroidtoothPlugin implements DuplexPlugin { } } - private void makeDeviceDiscoverable() { - synchronized(this) { - if(!running) return; - } - if(adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) return; - Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); - i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120); - i.addFlags(FLAG_ACTIVITY_NEW_TASK); - appContext.startActivity(i); - } - private static class BluetoothStateReceiver extends BroadcastReceiver { private final CountDownLatch finished = new CountDownLatch(1); diff --git a/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java b/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java index 05b7a31ab7ecd24951423d21ab5050c1a758c7b1..3e783daf97e660cd2b9d53bca1746c969d152d9b 100644 --- a/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java +++ b/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java @@ -65,9 +65,7 @@ class InsecureBluetooth { int handle = (Integer) addRfcommServiceRecord.invoke(mService, name, new ParcelUuid(uuid), channel, new Binder()); if(handle == -1) { - try { - socket.close(); - } catch(IOException ignored) {} + socket.close(); throw new IOException("Can't register SDP record for " + name); } Field f1 = adapter.getClass().getDeclaredField("mHandler"); @@ -117,9 +115,7 @@ class InsecureBluetooth { Object result = bindListen.invoke(mSocket, new Object[0]); int errno = (Integer) result; if(errno != 0) { - try { - socket.close(); - } catch(IOException ignored) {} + socket.close(); Method throwErrnoNative = mSocket.getClass().getMethod( "throwErrnoNative", int.class); throwErrnoNative.invoke(mSocket, errno); @@ -145,9 +141,8 @@ class InsecureBluetooth { @SuppressLint("NewApi") static BluetoothSocket createSocket(BluetoothDevice device, UUID uuid) throws IOException { - if(Build.VERSION.SDK_INT >= 10) { + if(Build.VERSION.SDK_INT >= 10) return device.createInsecureRfcommSocketToServiceRecord(uuid); - } try { BluetoothSocket socket = null; Constructor<BluetoothSocket> constructor = diff --git a/src/net/sf/briar/plugins/tcp/TcpPlugin.java b/src/net/sf/briar/plugins/tcp/TcpPlugin.java index f476aad47b60139d81e53d3453390385f82b523a..10c704e9be69661475417a7f9a61be07e3cc0422 100644 --- a/src/net/sf/briar/plugins/tcp/TcpPlugin.java +++ b/src/net/sf/briar/plugins/tcp/TcpPlugin.java @@ -176,9 +176,9 @@ abstract class TcpPlugin implements DuplexPlugin { if(!running) return null; } SocketAddress addr = getRemoteSocketAddress(c); + Socket s = new Socket(); + if(addr == null || s == null) return null; try { - Socket s = new Socket(); - if(addr == null || s == null) return null; s.connect(addr); return new TcpTransportConnection(s); } catch(IOException e) {