mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Refactored invitation code to allow the UI to save & restore its state.
Android UI elements can be destroyed and recreated at any time, and they can only store serialisable state, so references to long-running tasks have to take the form of serialisable handles. This is pretty ugly - it's easy to create memory leaks if you don't clean up stale handle/reference mappings - but it's less ugly than the common solution of using static variables to hold references.
This commit is contained in:
4
lint.xml
Normal file
4
lint.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<issue id="UseSparseArrays" severity="ignore" />
|
||||
</lint>
|
||||
@@ -1,34 +1,121 @@
|
||||
package net.sf.briar.android.invitation;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.invitation.ConfirmationCallback;
|
||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||
import net.sf.briar.api.invitation.InvitationListener;
|
||||
import net.sf.briar.api.invitation.InvitationManager;
|
||||
import net.sf.briar.api.invitation.InvitationState;
|
||||
import net.sf.briar.api.invitation.InvitationTask;
|
||||
import roboguice.activity.RoboActivity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
public class AddContactActivity extends RoboActivity
|
||||
implements ConnectionCallback, ConfirmationCallback {
|
||||
implements InvitationListener {
|
||||
|
||||
@Inject private CryptoComponent crypto;
|
||||
@Inject private InvitationManager invitationManager;
|
||||
|
||||
// All of the following must be accessed on the UI thread
|
||||
private AddContactView view = null;
|
||||
private InvitationTask task = null;
|
||||
private String networkName = null;
|
||||
private boolean useBluetooth = false;
|
||||
private int localInvitationCode = -1;
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private ConfirmationCallback callback = null;
|
||||
private boolean localMatched = false;
|
||||
private boolean remoteCompared = false, remoteMatched = false;
|
||||
private int localInvitationCode = -1, remoteInvitationCode = -1;
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private boolean connectionFailed = false;
|
||||
private boolean localCompared = false, remoteCompared = false;
|
||||
private boolean localMatched = false, remoteMatched = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
if(state == null) {
|
||||
// This is a new activity
|
||||
setView(new NetworkSetupView(this));
|
||||
} else {
|
||||
// Restore the activity's state
|
||||
networkName = state.getString("net.sf.briar.NETWORK_NAME");
|
||||
useBluetooth = state.getBoolean("net.sf.briar.USE_BLUETOOTH");
|
||||
int handle = state.getInt("TASK_HANDLE", -1);
|
||||
task = invitationManager.getTask(handle);
|
||||
if(task == null) {
|
||||
// No background task - we must be in an initial or final state
|
||||
localInvitationCode = state.getInt("net.sf.briar.LOCAL_CODE");
|
||||
remoteInvitationCode = state.getInt("net.sf.briar.REMOTE_CODE");
|
||||
connectionFailed = state.getBoolean("net.sf.briar.FAILED");
|
||||
if(state.getBoolean("net.sf.briar.MATCHED")) {
|
||||
localCompared = remoteCompared = true;
|
||||
localMatched = remoteMatched = true;
|
||||
}
|
||||
// Set the appropriate view for the state
|
||||
if(localInvitationCode == -1) {
|
||||
setView(new NetworkSetupView(this));
|
||||
} else if(remoteInvitationCode == -1) {
|
||||
setView(new InvitationCodeView(this));
|
||||
} else if(connectionFailed) {
|
||||
setView(new ConnectionFailedView(this));
|
||||
} else if(localMatched && remoteMatched) {
|
||||
setView(new ContactAddedView(this));
|
||||
} else {
|
||||
setView(new CodesDoNotMatchView(this));
|
||||
}
|
||||
} else {
|
||||
// A background task exists - listen to it and get its state
|
||||
InvitationState s = task.addListener(this);
|
||||
localInvitationCode = s.getLocalInvitationCode();
|
||||
remoteInvitationCode = s.getRemoteInvitationCode();
|
||||
localConfirmationCode = s.getLocalConfirmationCode();
|
||||
remoteConfirmationCode = s.getRemoteConfirmationCode();
|
||||
connectionFailed = s.getConnectionFailed();
|
||||
localCompared = s.getLocalCompared();
|
||||
remoteCompared = s.getRemoteCompared();
|
||||
localMatched = s.getLocalMatched();
|
||||
remoteMatched = s.getRemoteMatched();
|
||||
// Set the appropriate view for the state
|
||||
if(localInvitationCode == -1) {
|
||||
setView(new NetworkSetupView(this));
|
||||
} else if(remoteInvitationCode == -1) {
|
||||
setView(new InvitationCodeView(this));
|
||||
} else if(localConfirmationCode == -1) {
|
||||
setView(new ConnectionView(this));
|
||||
} else if(connectionFailed) {
|
||||
setView(new ConnectionFailedView(this));
|
||||
} else if(!localCompared) {
|
||||
setView(new ConfirmationCodeView(this));
|
||||
} else if(!remoteCompared) {
|
||||
setView(new WaitForContactView(this));
|
||||
} else if(localMatched && remoteMatched) {
|
||||
setView(new ContactAddedView(this));
|
||||
} else {
|
||||
setView(new CodesDoNotMatchView(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if(view == null) setView(new NetworkSetupView(this));
|
||||
else view.populate();
|
||||
view.populate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
state.putString("net.sf.briar.NETWORK_NAME", networkName);
|
||||
state.putBoolean("net.sf.briar.USE_BLUETOOTH", useBluetooth);
|
||||
state.putInt("net.sf.briar.LOCAL_CODE", localInvitationCode);
|
||||
state.putInt("net.sf.briar.REMOTE_CODE", remoteInvitationCode);
|
||||
state.putBoolean("net.sf.briar.FAILED", connectionFailed);
|
||||
state.putBoolean("net.sf.briar.MATCHED", localMatched && remoteMatched);
|
||||
if(task != null) state.putInt("TASK_HANDLE", task.getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if(task != null) task.removeListener(this);
|
||||
}
|
||||
|
||||
void setView(AddContactView view) {
|
||||
@@ -37,6 +124,18 @@ implements ConnectionCallback, ConfirmationCallback {
|
||||
setContentView(view);
|
||||
}
|
||||
|
||||
void reset(AddContactView view) {
|
||||
task = null;
|
||||
networkName = null;
|
||||
useBluetooth = false;
|
||||
localInvitationCode = -1;
|
||||
localConfirmationCode = remoteConfirmationCode = -1;
|
||||
connectionFailed = false;
|
||||
localCompared = remoteCompared = false;
|
||||
localMatched = remoteMatched = false;
|
||||
setView(view);
|
||||
}
|
||||
|
||||
void setNetworkName(String networkName) {
|
||||
this.networkName = networkName;
|
||||
}
|
||||
@@ -53,19 +152,18 @@ implements ConnectionCallback, ConfirmationCallback {
|
||||
return useBluetooth;
|
||||
}
|
||||
|
||||
int generateLocalInvitationCode() {
|
||||
localInvitationCode = crypto.generateInvitationCode();
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
int getLocalInvitationCode() {
|
||||
if(localInvitationCode == -1)
|
||||
localInvitationCode = crypto.generateInvitationCode();
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
void remoteInvitationCodeEntered(int code) {
|
||||
setView(new ConnectionView(this));
|
||||
localMatched = remoteCompared = remoteMatched = false;
|
||||
invitationManager.connect(localInvitationCode, code, this);
|
||||
// FIXME: These calls are blocking the UI thread for too long
|
||||
task = invitationManager.createTask(localInvitationCode, code);
|
||||
task.addListener(AddContactActivity.this);
|
||||
task.connect();
|
||||
}
|
||||
|
||||
int getLocalConfirmationCode() {
|
||||
@@ -78,34 +176,33 @@ implements ConnectionCallback, ConfirmationCallback {
|
||||
if(remoteMatched) setView(new ContactAddedView(this));
|
||||
else if(remoteCompared) setView(new CodesDoNotMatchView(this));
|
||||
else setView(new WaitForContactView(this));
|
||||
callback.codesMatch();
|
||||
task.localConfirmationSucceeded();
|
||||
} else {
|
||||
setView(new CodesDoNotMatchView(this));
|
||||
callback.codesDoNotMatch();
|
||||
task.localConfirmationFailed();
|
||||
}
|
||||
}
|
||||
|
||||
public void connectionEstablished(final int localCode, final int remoteCode,
|
||||
final ConfirmationCallback c) {
|
||||
public void connectionSucceeded(final int localCode, final int remoteCode) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
localConfirmationCode = localCode;
|
||||
remoteConfirmationCode = remoteCode;
|
||||
callback = c;
|
||||
setView(new ConfirmationCodeView(AddContactActivity.this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void connectionNotEstablished() {
|
||||
public void connectionFailed() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
connectionFailed = true;
|
||||
setView(new ConnectionFailedView(AddContactActivity.this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void codesMatch() {
|
||||
public void remoteConfirmationSucceeded() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
remoteCompared = true;
|
||||
@@ -116,7 +213,7 @@ implements ConnectionCallback, ConfirmationCallback {
|
||||
});
|
||||
}
|
||||
|
||||
public void codesDoNotMatch() {
|
||||
public void remoteConfirmationFailed() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
remoteCompared = true;
|
||||
|
||||
@@ -53,6 +53,6 @@ implements OnClickListener {
|
||||
|
||||
public void onClick(View view) {
|
||||
// Try again
|
||||
container.setView(new NetworkSetupView(container));
|
||||
container.reset(new NetworkSetupView(container));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,6 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
|
||||
|
||||
public void onClick(View view) {
|
||||
// Try again
|
||||
container.setView(new InvitationCodeView(container));
|
||||
container.reset(new InvitationCodeView(container));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,6 @@ OnEditorActionListener {
|
||||
|
||||
public void onClick(View view) {
|
||||
if(view == done) container.finish(); // Done
|
||||
else container.setView(new NetworkSetupView(container)); // Add another
|
||||
else container.reset(new NetworkSetupView(container)); // Add another
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,10 @@ import android.widget.TextView;
|
||||
public class InvitationCodeView extends AddContactView
|
||||
implements CodeEntryListener {
|
||||
|
||||
private int localCode = -1;
|
||||
|
||||
InvitationCodeView(Context ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
void init(AddContactActivity container) {
|
||||
localCode = container.generateLocalInvitationCode();
|
||||
super.init(container);
|
||||
}
|
||||
|
||||
void populate() {
|
||||
removeAllViews();
|
||||
Context ctx = getContext();
|
||||
@@ -31,6 +24,7 @@ implements CodeEntryListener {
|
||||
TextView code = new TextView(ctx);
|
||||
code.setGravity(CENTER_HORIZONTAL);
|
||||
code.setTextSize(50);
|
||||
int localCode = container.getLocalInvitationCode();
|
||||
code.setText(String.format("%06d", localCode));
|
||||
addView(code);
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package net.sf.briar.api.invitation;
|
||||
|
||||
/** An interface for informing a peer of whether confirmation codes match. */
|
||||
public interface ConfirmationCallback {
|
||||
|
||||
/** Called to indicate that the confirmation codes match. */
|
||||
void codesMatch();
|
||||
|
||||
/**
|
||||
* Called to indicate that either the confirmation codes do not match or
|
||||
* the result of the comparison is unknown.
|
||||
*/
|
||||
void codesDoNotMatch();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package net.sf.briar.api.invitation;
|
||||
|
||||
/** An interface for monitoring the status of an invitation connection. */
|
||||
public interface ConnectionCallback extends ConfirmationCallback {
|
||||
|
||||
/**
|
||||
* Called if the connection is successfully established.
|
||||
* @param localCode the local confirmation code.
|
||||
* @param remoteCode the remote confirmation code.
|
||||
* @param c a callback to inform the remote peer of the result of the local
|
||||
* peer's confirmation code comparison.
|
||||
*/
|
||||
void connectionEstablished(int localCode, int remoteCode,
|
||||
ConfirmationCallback c);
|
||||
|
||||
/** Called if the connection cannot be established. */
|
||||
void connectionNotEstablished();
|
||||
}
|
||||
26
src/net/sf/briar/api/invitation/InvitationListener.java
Normal file
26
src/net/sf/briar/api/invitation/InvitationListener.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package net.sf.briar.api.invitation;
|
||||
|
||||
/**
|
||||
* An interface for receiving updates about the state of an
|
||||
* {@link InvitationTask}.
|
||||
*/
|
||||
public interface InvitationListener {
|
||||
|
||||
/** Called if a connection is established and key agreement succeeds. */
|
||||
void connectionSucceeded(int localCode, int remoteCode);
|
||||
|
||||
/** Called if a connection cannot be established. */
|
||||
void connectionFailed();
|
||||
|
||||
/**
|
||||
* Informs the local peer that the remote peer's confirmation check
|
||||
* succeeded.
|
||||
*/
|
||||
void remoteConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Informs the local peer that the remote peer's confirmation check did
|
||||
* not succeed, or the connection was lost during confirmation.
|
||||
*/
|
||||
void remoteConfirmationFailed();
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
package net.sf.briar.api.invitation;
|
||||
|
||||
/**
|
||||
* Allows invitation connections to be established and their status to be
|
||||
* monitored.
|
||||
*/
|
||||
/** Creates and manages tasks for exchanging invitations with remote peers. */
|
||||
public interface InvitationManager {
|
||||
|
||||
/** Creates a task using the given invitation codes. */
|
||||
InvitationTask createTask(int localCode, int remoteCode);
|
||||
|
||||
/**
|
||||
* Tries to establish an invitation connection.
|
||||
* @param localCode the local invitation code.
|
||||
* @param remoteCode the remote invitation code.
|
||||
* @param c1 a callback to be informed of the connection's status and the
|
||||
* result of the remote peer's confirmation code comparison.
|
||||
* Returns the previously created task with the given handle, unless the
|
||||
* task has subsequently removed itself.
|
||||
*/
|
||||
void connect(int localCode, int remoteCode, ConnectionCallback c);
|
||||
InvitationTask getTask(int handle);
|
||||
|
||||
/** Called by tasks to remove themselves when they terminate. */
|
||||
void removeTask(int handle);
|
||||
}
|
||||
|
||||
62
src/net/sf/briar/api/invitation/InvitationState.java
Normal file
62
src/net/sf/briar/api/invitation/InvitationState.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.sf.briar.api.invitation;
|
||||
|
||||
public class InvitationState {
|
||||
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final int localConfirmationCode, remoteConfirmationCode;
|
||||
private final boolean connectionFailed;
|
||||
private final boolean localCompared, remoteCompared;
|
||||
private final boolean localMatched, remoteMatched;
|
||||
|
||||
public InvitationState(int localInvitationCode, int remoteInvitationCode,
|
||||
int localConfirmationCode, int remoteConfirmationCode,
|
||||
boolean connectionFailed, boolean localCompared,
|
||||
boolean remoteCompared, boolean localMatched,
|
||||
boolean remoteMatched) {
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
this.localConfirmationCode = localConfirmationCode;
|
||||
this.remoteConfirmationCode = remoteConfirmationCode;
|
||||
this.connectionFailed = connectionFailed;
|
||||
this.localCompared = localCompared;
|
||||
this.remoteCompared = remoteCompared;
|
||||
this.localMatched = localMatched;
|
||||
this.remoteMatched = remoteMatched;
|
||||
}
|
||||
|
||||
public int getLocalInvitationCode() {
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
public int getRemoteInvitationCode() {
|
||||
return remoteInvitationCode;
|
||||
}
|
||||
|
||||
public int getLocalConfirmationCode() {
|
||||
return localConfirmationCode;
|
||||
}
|
||||
|
||||
public int getRemoteConfirmationCode() {
|
||||
return remoteConfirmationCode;
|
||||
}
|
||||
|
||||
public boolean getConnectionFailed() {
|
||||
return connectionFailed;
|
||||
}
|
||||
|
||||
public boolean getLocalCompared() {
|
||||
return localCompared;
|
||||
}
|
||||
|
||||
public boolean getRemoteCompared() {
|
||||
return remoteCompared;
|
||||
}
|
||||
|
||||
public boolean getLocalMatched() {
|
||||
return localMatched;
|
||||
}
|
||||
|
||||
public boolean getRemoteMatched() {
|
||||
return remoteMatched;
|
||||
}
|
||||
}
|
||||
32
src/net/sf/briar/api/invitation/InvitationTask.java
Normal file
32
src/net/sf/briar/api/invitation/InvitationTask.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.sf.briar.api.invitation;
|
||||
|
||||
/** A task for exchanging invitations with a remote peer. */
|
||||
public interface InvitationTask {
|
||||
|
||||
/** Returns the task's unique handle. */
|
||||
int getHandle();
|
||||
|
||||
/**
|
||||
* Adds a listener to be informed of state changes and returns the
|
||||
* task's current state.
|
||||
*/
|
||||
InvitationState addListener(InvitationListener l);
|
||||
|
||||
/** Removes the given listener. */
|
||||
void removeListener(InvitationListener l);
|
||||
|
||||
/** Asynchronously starts the connection process. */
|
||||
void connect();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes matched.
|
||||
*/
|
||||
void localConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes did not match.
|
||||
*/
|
||||
void localConfirmationFailed();
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package net.sf.briar.api.plugins;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
long INVITATION_TIMEOUT = 30 * 1000; // Milliseconds
|
||||
long CONNECTION_TIMEOUT = 15 * 1000; // Milliseconds
|
||||
|
||||
long CONFIRMATION_TIMEOUT = 60 * 1000; // Milliseconds
|
||||
|
||||
int CODE_BITS = 19; // Codes must fit into six decimal digits
|
||||
|
||||
|
||||
@@ -2,18 +2,15 @@ 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.INVITATION_TIMEOUT;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
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;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
@@ -21,23 +18,23 @@ import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
|
||||
/** A connection thread for the peer being Alice in the invitation protocol. */
|
||||
class AliceConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AliceConnector.class.getName());
|
||||
|
||||
AliceConnector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||
PseudoRandom random, ConnectionCallback callback,
|
||||
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||
super(crypto, readerFactory, writerFactory, plugin, random, callback,
|
||||
connected, succeeded);
|
||||
WriterFactory writerFactory, ConnectorGroup group,
|
||||
DuplexPlugin plugin, int localCode, int remoteCode) {
|
||||
super(crypto, readerFactory, writerFactory, group, plugin,
|
||||
crypto.getPseudoRandom(localCode, remoteCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Try an outgoing connection first, then an incoming connection
|
||||
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
||||
long halfTime = System.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
DuplexTransportConnection conn = makeOutgoingConnection();
|
||||
if(conn == null) {
|
||||
waitForHalfTime(halfTime);
|
||||
@@ -46,7 +43,7 @@ class AliceConnector extends Connector {
|
||||
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(group.getAndSetConnected()) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
@@ -77,21 +74,27 @@ class AliceConnector extends Connector {
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
|
||||
succeeded.set(true);
|
||||
// Derive the confirmation codes
|
||||
int[] codes = crypto.deriveConfirmationCodes(secret);
|
||||
callback.connectionEstablished(codes[0], codes[1],
|
||||
new ConfirmationSender(w));
|
||||
// Check whether the remote peer's confirmation codes matched
|
||||
group.connectionSucceeded(codes[0], codes[1]);
|
||||
// Exchange confirmation results
|
||||
try {
|
||||
if(r.readBoolean()) callback.codesMatch();
|
||||
else callback.codesDoNotMatch();
|
||||
sendConfirmation(w);
|
||||
if(receiveConfirmation(r)) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
tryToClose(conn, true);
|
||||
callback.codesDoNotMatch();
|
||||
group.remoteConfirmationFailed();
|
||||
return;
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
tryToClose(conn, true);
|
||||
group.remoteConfirmationFailed();
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,15 @@ 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.INVITATION_TIMEOUT;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
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;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
@@ -21,23 +18,23 @@ import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
|
||||
/** A connection thread for the peer being Bob in the invitation protocol. */
|
||||
class BobConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BobConnector.class.getName());
|
||||
|
||||
BobConnector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||
PseudoRandom random, ConnectionCallback callback,
|
||||
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||
super(crypto, readerFactory, writerFactory, plugin, random, callback,
|
||||
connected, succeeded);
|
||||
WriterFactory writerFactory, ConnectorGroup group,
|
||||
DuplexPlugin plugin, int localCode, int remoteCode) {
|
||||
super(crypto, readerFactory, writerFactory, group, plugin,
|
||||
crypto.getPseudoRandom(remoteCode, localCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Try an incoming connection first, then an outgoing connection
|
||||
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
||||
long halfTime = System.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
DuplexTransportConnection conn = acceptIncomingConnection();
|
||||
if(conn == null) {
|
||||
waitForHalfTime(halfTime);
|
||||
@@ -58,6 +55,12 @@ class BobConnector extends Connector {
|
||||
w = writerFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
// Don't proceed with more than one connection
|
||||
if(group.getAndSetConnected()) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
sendPublicKeyHash(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
sendPublicKey(w);
|
||||
@@ -71,21 +74,27 @@ class BobConnector extends Connector {
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
|
||||
succeeded.set(true);
|
||||
// Derive the confirmation codes
|
||||
int[] codes = crypto.deriveConfirmationCodes(secret);
|
||||
callback.connectionEstablished(codes[1], codes[0],
|
||||
new ConfirmationSender(w));
|
||||
// Check whether the remote peer's confirmation codes matched
|
||||
group.connectionSucceeded(codes[1], codes[0]);
|
||||
// Exchange confirmation results
|
||||
try {
|
||||
if(r.readBoolean()) callback.codesMatch();
|
||||
else callback.codesDoNotMatch();
|
||||
sendConfirmation(w);
|
||||
if(receiveConfirmation(r)) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
tryToClose(conn, true);
|
||||
callback.codesDoNotMatch();
|
||||
group.remoteConfirmationFailed();
|
||||
return;
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
tryToClose(conn, true);
|
||||
group.remoteConfirmationFailed();
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ 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 static net.sf.briar.api.plugins.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.FormatException;
|
||||
@@ -18,8 +17,6 @@ import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.KeyParser;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
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.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
@@ -35,10 +32,9 @@ abstract class Connector extends Thread {
|
||||
protected final CryptoComponent crypto;
|
||||
protected final ReaderFactory readerFactory;
|
||||
protected final WriterFactory writerFactory;
|
||||
protected final ConnectorGroup group;
|
||||
protected final DuplexPlugin plugin;
|
||||
protected final PseudoRandom random;
|
||||
protected final ConnectionCallback callback;
|
||||
protected final AtomicBoolean connected, succeeded;
|
||||
protected final String pluginName;
|
||||
|
||||
private final KeyPair keyPair;
|
||||
@@ -46,17 +42,14 @@ abstract class Connector extends Thread {
|
||||
private final MessageDigest messageDigest;
|
||||
|
||||
Connector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||
PseudoRandom random, ConnectionCallback callback,
|
||||
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||
WriterFactory writerFactory, ConnectorGroup group,
|
||||
DuplexPlugin plugin, PseudoRandom random) {
|
||||
this.crypto = crypto;
|
||||
this.readerFactory = readerFactory;
|
||||
this.writerFactory = writerFactory;
|
||||
this.group = group;
|
||||
this.plugin = plugin;
|
||||
this.random = random;
|
||||
this.callback = callback;
|
||||
this.connected = connected;
|
||||
this.succeeded = succeeded;
|
||||
pluginName = plugin.getClass().getName();
|
||||
keyPair = crypto.generateAgreementKeyPair();
|
||||
keyParser = crypto.getAgreementKeyParser();
|
||||
@@ -66,13 +59,13 @@ abstract class Connector extends Thread {
|
||||
protected DuplexTransportConnection acceptIncomingConnection() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " accepting incoming connection");
|
||||
return plugin.acceptInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
return plugin.acceptInvitation(random, CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
protected DuplexTransportConnection makeOutgoingConnection() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " making outgoing connection");
|
||||
return plugin.sendInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
return plugin.sendInvitation(random, CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
protected void waitForHalfTime(long halfTime) {
|
||||
@@ -125,7 +118,7 @@ abstract class Connector extends Thread {
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -141,29 +134,18 @@ abstract class Connector extends Thread {
|
||||
return crypto.deriveInitialSecret(key, keyPair, alice);
|
||||
}
|
||||
|
||||
protected static class ConfirmationSender implements ConfirmationCallback {
|
||||
protected void sendConfirmation(Writer w) throws IOException,
|
||||
InterruptedException {
|
||||
boolean matched = group.waitForLocalConfirmationResult();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent confirmation");
|
||||
w.writeBoolean(matched);
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private final Writer writer;
|
||||
|
||||
protected ConfirmationSender(Writer writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public void codesMatch() {
|
||||
write(true);
|
||||
}
|
||||
|
||||
public void codesDoNotMatch() {
|
||||
write(false);
|
||||
}
|
||||
|
||||
private void write(boolean match) {
|
||||
try {
|
||||
writer.writeBoolean(match);
|
||||
writer.flush();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
protected boolean receiveConfirmation(Reader r) throws IOException {
|
||||
boolean matched = r.readBoolean();
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " received confirmation");
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
|
||||
145
src/net/sf/briar/invitation/ConnectorGroup.java
Normal file
145
src/net/sf/briar/invitation/ConnectorGroup.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.CONFIRMATION_TIMEOUT;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.invitation.InvitationListener;
|
||||
import net.sf.briar.api.invitation.InvitationManager;
|
||||
import net.sf.briar.api.invitation.InvitationState;
|
||||
import net.sf.briar.api.invitation.InvitationTask;
|
||||
|
||||
/** A task consisting of one or more parallel connection attempts. */
|
||||
class ConnectorGroup implements InvitationTask {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectorGroup.class.getName());
|
||||
|
||||
private final InvitationManager manager;
|
||||
private final int handle, localInvitationCode, remoteInvitationCode;
|
||||
private final Collection<Connector> connectors;
|
||||
private final Collection<InvitationListener> listeners;
|
||||
private final AtomicBoolean connected;
|
||||
private final CountDownLatch localConfirmationLatch;
|
||||
|
||||
/*
|
||||
* All of the following are locking: this. We don't want to call the
|
||||
* listeners with a lock held, but we need to avoid a race condition in
|
||||
* addListener(), so the state that's accessed there after calling
|
||||
* listeners.add() must be guarded by a lock.
|
||||
*/
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private boolean connectionFailed = false;
|
||||
private boolean localCompared = false, remoteCompared = false;
|
||||
private boolean localMatched = false, remoteMatched = false;
|
||||
|
||||
ConnectorGroup(InvitationManager manager, int handle,
|
||||
int localInvitationCode, int remoteInvitationCode) {
|
||||
this.manager = manager;
|
||||
this.handle = handle;
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
connectors = new CopyOnWriteArrayList<Connector>();
|
||||
listeners = new CopyOnWriteArrayList<InvitationListener>();
|
||||
connected = new AtomicBoolean(false);
|
||||
localConfirmationLatch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
public int getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
public synchronized InvitationState addListener(InvitationListener l) {
|
||||
listeners.add(l);
|
||||
return new InvitationState(localInvitationCode, remoteInvitationCode,
|
||||
localConfirmationCode, remoteConfirmationCode, connectionFailed,
|
||||
localCompared, remoteCompared, localMatched, remoteMatched);
|
||||
}
|
||||
|
||||
public void removeListener(InvitationListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
// FIXME: The task isn't removed from the manager unless this is called
|
||||
public void connect() {
|
||||
for(Connector c : connectors) c.start();
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for(Connector c : connectors) c.join();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.warning("Interrupted while waiting for connectors");
|
||||
}
|
||||
if(!connected.get()) {
|
||||
synchronized(ConnectorGroup.this) {
|
||||
connectionFailed = true;
|
||||
}
|
||||
for(InvitationListener l : listeners) l.connectionFailed();
|
||||
}
|
||||
manager.removeTask(handle);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public void localConfirmationSucceeded() {
|
||||
synchronized(this) {
|
||||
localCompared = true;
|
||||
localMatched = true;
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
public void localConfirmationFailed() {
|
||||
synchronized(this) {
|
||||
localCompared = true;
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
void addConnector(Connector c) {
|
||||
connectors.add(c);
|
||||
}
|
||||
|
||||
boolean getAndSetConnected() {
|
||||
return connected.getAndSet(true);
|
||||
}
|
||||
|
||||
void connectionSucceeded(int localCode, int remoteCode) {
|
||||
synchronized(this) {
|
||||
localConfirmationCode = localCode;
|
||||
remoteConfirmationCode = remoteCode;
|
||||
}
|
||||
for(InvitationListener l : listeners)
|
||||
l.connectionSucceeded(localCode, remoteCode);
|
||||
}
|
||||
|
||||
void remoteConfirmationSucceeded() {
|
||||
synchronized(this) {
|
||||
remoteCompared = true;
|
||||
remoteMatched = true;
|
||||
}
|
||||
for(InvitationListener l : listeners) l.remoteConfirmationSucceeded();
|
||||
}
|
||||
|
||||
void remoteConfirmationFailed() {
|
||||
synchronized(this) {
|
||||
remoteCompared = true;
|
||||
}
|
||||
for(InvitationListener l : listeners) l.remoteConfirmationFailed();
|
||||
}
|
||||
|
||||
boolean waitForLocalConfirmationResult() throws InterruptedException {
|
||||
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
|
||||
synchronized(this) {
|
||||
return localMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||
import net.sf.briar.api.invitation.InvitationManager;
|
||||
import net.sf.briar.api.invitation.InvitationTask;
|
||||
import net.sf.briar.api.plugins.PluginManager;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
@@ -22,6 +22,9 @@ class InvitationManagerImpl implements InvitationManager {
|
||||
private final WriterFactory writerFactory;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
private final AtomicInteger nextHandle;
|
||||
private final Map<Integer, InvitationTask> tasks;
|
||||
|
||||
@Inject
|
||||
InvitationManagerImpl(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, PluginManager pluginManager) {
|
||||
@@ -29,45 +32,36 @@ class InvitationManagerImpl implements InvitationManager {
|
||||
this.readerFactory = readerFactory;
|
||||
this.writerFactory = writerFactory;
|
||||
this.pluginManager = pluginManager;
|
||||
nextHandle = new AtomicInteger(0);
|
||||
tasks = new ConcurrentHashMap<Integer, InvitationTask>();
|
||||
}
|
||||
|
||||
public void connect(int localCode, int remoteCode, ConnectionCallback c) {
|
||||
public InvitationTask createTask(int localCode, int remoteCode) {
|
||||
Collection<DuplexPlugin> plugins = pluginManager.getInvitationPlugins();
|
||||
// Alice is the party with the smaller invitation code
|
||||
int handle = nextHandle.incrementAndGet();
|
||||
ConnectorGroup group =
|
||||
new ConnectorGroup(this, handle, localCode, remoteCode);
|
||||
// Alice is the peer with the lesser invitation code
|
||||
if(localCode < remoteCode) {
|
||||
startAliceWorkers(plugins, localCode, remoteCode, c);
|
||||
for(DuplexPlugin plugin : plugins) {
|
||||
group.addConnector(new AliceConnector(crypto, readerFactory,
|
||||
writerFactory, group, plugin, localCode, remoteCode));
|
||||
}
|
||||
} else {
|
||||
startBobWorkers(plugins, localCode, remoteCode, c);
|
||||
for(DuplexPlugin plugin : plugins) {
|
||||
group.addConnector(new BobConnector(crypto, readerFactory,
|
||||
writerFactory, group, plugin, localCode, remoteCode));
|
||||
}
|
||||
}
|
||||
tasks.put(handle, group);
|
||||
return group;
|
||||
}
|
||||
|
||||
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(crypto, readerFactory,
|
||||
writerFactory, p, r, c, connected, succeeded);
|
||||
workers.add(worker);
|
||||
worker.start();
|
||||
}
|
||||
new FailureNotifier(workers, succeeded, c).start();
|
||||
public InvitationTask getTask(int handle) {
|
||||
return tasks.get(handle);
|
||||
}
|
||||
|
||||
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(crypto, readerFactory,
|
||||
writerFactory, p, r, c, connected, succeeded);
|
||||
workers.add(worker);
|
||||
worker.start();
|
||||
}
|
||||
new FailureNotifier(workers, succeeded, c).start();
|
||||
public void removeTask(int handle) {
|
||||
tasks.remove(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package net.sf.briar.invitation;
|
||||
import net.sf.briar.api.invitation.InvitationManager;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
public class InvitationModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(InvitationManager.class).to(InvitationManagerImpl.class);
|
||||
bind(InvitationManager.class).to(InvitationManagerImpl.class).in(
|
||||
Singleton.class);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user