Skip to content
Snippets Groups Projects
Commit eedfa592 authored by akwizgran's avatar akwizgran
Browse files

Stub implementation of the invitation protocol (works on Android).

parent 514bec51
No related branches found
No related tags found
No related merge requests found
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
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());
}
}
}
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());
}
}
}
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();
}
}
}
package net.sf.briar.invitation; package net.sf.briar.invitation;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.PseudoRandom; 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.ConnectionCallback;
import net.sf.briar.api.invitation.InvitationManager; import net.sf.briar.api.invitation.InvitationManager;
import net.sf.briar.api.plugins.PluginManager; import net.sf.briar.api.plugins.PluginManager;
...@@ -27,55 +28,37 @@ class InvitationManagerImpl implements InvitationManager { ...@@ -27,55 +28,37 @@ class InvitationManagerImpl implements InvitationManager {
Collection<DuplexPlugin> plugins = pluginManager.getInvitationPlugins(); Collection<DuplexPlugin> plugins = pluginManager.getInvitationPlugins();
// Alice is the party with the smaller invitation code // Alice is the party with the smaller invitation code
if(localCode < remoteCode) { if(localCode < remoteCode) {
PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode); startAliceWorkers(plugins, localCode, remoteCode, c);
startAliceInvitationWorker(plugins, r, c);
} else { } else {
PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode); startBobWorkers(plugins, localCode, remoteCode, c);
startBobInvitationWorker(plugins, r, c);
} }
} }
private void startAliceInvitationWorker(Collection<DuplexPlugin> plugins, private void startAliceWorkers(Collection<DuplexPlugin> plugins,
PseudoRandom r, ConnectionCallback c) { int localCode, int remoteCode, ConnectionCallback c) {
// FIXME AtomicBoolean connected = new AtomicBoolean(false);
new FakeWorkerThread(c).start(); AtomicBoolean succeeded = new AtomicBoolean(false);
} Collection<Thread> workers = new ArrayList<Thread>();
for(DuplexPlugin p : plugins) {
private void startBobInvitationWorker(Collection<DuplexPlugin> plugins, PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode);
PseudoRandom r, ConnectionCallback c) { Thread worker = new AliceConnector(p, r, c, connected, succeeded);
// FIXME workers.add(worker);
new FakeWorkerThread(c).start(); worker.start();
}
private static class FakeWorkerThread extends Thread {
private final ConnectionCallback callback;
private FakeWorkerThread(ConnectionCallback callback) {
this.callback = callback;
} }
new FailureNotifier(workers, succeeded, c).start();
}
@Override private void startBobWorkers(Collection<DuplexPlugin> plugins,
public void run() { int localCode, int remoteCode, ConnectionCallback c) {
try { AtomicBoolean connected = new AtomicBoolean(false);
Thread.sleep((long) (Math.random() * 30 * 1000)); AtomicBoolean succeeded = new AtomicBoolean(false);
} catch(InterruptedException ignored) {} Collection<Thread> workers = new ArrayList<Thread>();
if(Math.random() < 0.8) { for(DuplexPlugin p : plugins) {
callback.connectionNotEstablished(); PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode);
} else { Thread worker = new BobConnector(p, r, c, connected, succeeded);
callback.connectionEstablished(123456, 123456, workers.add(worker);
new ConfirmationCallback() { worker.start();
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();
}
} }
new FailureNotifier(workers, succeeded, c).start();
} }
} }
package net.sf.briar.plugins.droidtooth; 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.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; 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_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; 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.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
...@@ -265,6 +261,7 @@ class DroidtoothPlugin implements DuplexPlugin { ...@@ -265,6 +261,7 @@ class DroidtoothPlugin implements DuplexPlugin {
// Try to connect // Try to connect
try { try {
BluetoothSocket s = InsecureBluetooth.createSocket(d, u); BluetoothSocket s = InsecureBluetooth.createSocket(d, u);
s.connect();
return new DroidtoothTransportConnection(s); return new DroidtoothTransportConnection(s);
} catch(IOException e) { } catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
...@@ -320,8 +317,6 @@ class DroidtoothPlugin implements DuplexPlugin { ...@@ -320,8 +317,6 @@ class DroidtoothPlugin implements DuplexPlugin {
} }
// Use the same pseudo-random UUID as the contact // Use the same pseudo-random UUID as the contact
UUID uuid = UUID.nameUUIDFromBytes(r.nextBytes(16)); 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 // Bind a new server socket to accept the invitation connection
final BluetoothServerSocket ss; final BluetoothServerSocket ss;
try { try {
...@@ -344,17 +339,6 @@ class DroidtoothPlugin implements DuplexPlugin { ...@@ -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 static class BluetoothStateReceiver extends BroadcastReceiver {
private final CountDownLatch finished = new CountDownLatch(1); private final CountDownLatch finished = new CountDownLatch(1);
......
...@@ -65,9 +65,7 @@ class InsecureBluetooth { ...@@ -65,9 +65,7 @@ class InsecureBluetooth {
int handle = (Integer) addRfcommServiceRecord.invoke(mService, name, int handle = (Integer) addRfcommServiceRecord.invoke(mService, name,
new ParcelUuid(uuid), channel, new Binder()); new ParcelUuid(uuid), channel, new Binder());
if(handle == -1) { if(handle == -1) {
try { socket.close();
socket.close();
} catch(IOException ignored) {}
throw new IOException("Can't register SDP record for " + name); throw new IOException("Can't register SDP record for " + name);
} }
Field f1 = adapter.getClass().getDeclaredField("mHandler"); Field f1 = adapter.getClass().getDeclaredField("mHandler");
...@@ -117,9 +115,7 @@ class InsecureBluetooth { ...@@ -117,9 +115,7 @@ class InsecureBluetooth {
Object result = bindListen.invoke(mSocket, new Object[0]); Object result = bindListen.invoke(mSocket, new Object[0]);
int errno = (Integer) result; int errno = (Integer) result;
if(errno != 0) { if(errno != 0) {
try { socket.close();
socket.close();
} catch(IOException ignored) {}
Method throwErrnoNative = mSocket.getClass().getMethod( Method throwErrnoNative = mSocket.getClass().getMethod(
"throwErrnoNative", int.class); "throwErrnoNative", int.class);
throwErrnoNative.invoke(mSocket, errno); throwErrnoNative.invoke(mSocket, errno);
...@@ -145,9 +141,8 @@ class InsecureBluetooth { ...@@ -145,9 +141,8 @@ class InsecureBluetooth {
@SuppressLint("NewApi") @SuppressLint("NewApi")
static BluetoothSocket createSocket(BluetoothDevice device, UUID uuid) static BluetoothSocket createSocket(BluetoothDevice device, UUID uuid)
throws IOException { throws IOException {
if(Build.VERSION.SDK_INT >= 10) { if(Build.VERSION.SDK_INT >= 10)
return device.createInsecureRfcommSocketToServiceRecord(uuid); return device.createInsecureRfcommSocketToServiceRecord(uuid);
}
try { try {
BluetoothSocket socket = null; BluetoothSocket socket = null;
Constructor<BluetoothSocket> constructor = Constructor<BluetoothSocket> constructor =
......
...@@ -176,9 +176,9 @@ abstract class TcpPlugin implements DuplexPlugin { ...@@ -176,9 +176,9 @@ abstract class TcpPlugin implements DuplexPlugin {
if(!running) return null; if(!running) return null;
} }
SocketAddress addr = getRemoteSocketAddress(c); SocketAddress addr = getRemoteSocketAddress(c);
Socket s = new Socket();
if(addr == null || s == null) return null;
try { try {
Socket s = new Socket();
if(addr == null || s == null) return null;
s.connect(addr); s.connect(addr);
return new TcpTransportConnection(s); return new TcpTransportConnection(s);
} catch(IOException e) { } catch(IOException e) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment