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

This commit is contained in:
akwizgran
2012-11-13 11:08:47 +00:00
parent 514bec5101
commit eedfa592d2
8 changed files with 359 additions and 75 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {
startBobWorkers(plugins, localCode, remoteCode, c);
}
}
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();
}
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);
startBobInvitationWorker(plugins, r, 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;
}
@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();
}
Thread worker = new BobConnector(p, r, c, connected, succeeded);
workers.add(worker);
worker.start();
}
new FailureNotifier(workers, succeeded, c).start();
}
}

View File

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

View File

@@ -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 =

View File

@@ -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) {