mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Merge branch 'remove-old-bluetooth-code' into 'master'
Remove old Bluetooth code and location permission See merge request !584
This commit is contained in:
@@ -11,7 +11,6 @@ import android.content.IntentFilter;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
@@ -30,23 +29,14 @@ import org.briarproject.bramble.util.StringUtils;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CompletionService;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ExecutorCompletionService;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -61,8 +51,6 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
|||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||||
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 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;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||||
@@ -79,10 +67,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||||
private static final String FOUND =
|
|
||||||
"android.bluetooth.device.action.FOUND";
|
|
||||||
private static final String DISCOVERY_FINISHED =
|
|
||||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
@@ -374,90 +358,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
return new DroidtoothTransportConnection(this, s);
|
return new DroidtoothTransportConnection(this, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
|
||||||
long timeout, boolean alice) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
// Use the invitation codes to generate the UUID
|
|
||||||
byte[] b = r.nextBytes(UUID_BYTES);
|
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(b);
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
|
|
||||||
// Bind a server socket for receiving invitation connections
|
|
||||||
BluetoothServerSocket ss;
|
|
||||||
try {
|
|
||||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
|
||||||
"RFCOMM", uuid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Create the background tasks
|
|
||||||
CompletionService<BluetoothSocket> complete =
|
|
||||||
new ExecutorCompletionService<>(ioExecutor);
|
|
||||||
List<Future<BluetoothSocket>> futures = new ArrayList<>();
|
|
||||||
if (alice) {
|
|
||||||
// Return the first connected socket
|
|
||||||
futures.add(complete.submit(new ListeningTask(ss)));
|
|
||||||
futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
|
|
||||||
} else {
|
|
||||||
// Return the first socket with readable data
|
|
||||||
futures.add(complete.submit(new ReadableTask(
|
|
||||||
new ListeningTask(ss))));
|
|
||||||
futures.add(complete.submit(new ReadableTask(
|
|
||||||
new DiscoveryTask(uuid.toString()))));
|
|
||||||
}
|
|
||||||
BluetoothSocket chosen = null;
|
|
||||||
try {
|
|
||||||
Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
|
|
||||||
if (f == null) return null; // No task completed within the timeout
|
|
||||||
chosen = f.get();
|
|
||||||
return new DroidtoothTransportConnection(this, chosen);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while exchanging invitations");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return null;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
// Closing the socket will terminate the listener task
|
|
||||||
tryToClose(ss);
|
|
||||||
closeSockets(futures, chosen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeSockets(final List<Future<BluetoothSocket>> futures,
|
|
||||||
@Nullable final BluetoothSocket chosen) {
|
|
||||||
ioExecutor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (Future<BluetoothSocket> f : futures) {
|
|
||||||
try {
|
|
||||||
if (f.cancel(true)) {
|
|
||||||
LOG.info("Cancelled task");
|
|
||||||
} else {
|
|
||||||
BluetoothSocket s = f.get();
|
|
||||||
if (s != null && s != chosen) {
|
|
||||||
LOG.info("Closing unwanted socket");
|
|
||||||
s.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while closing sockets");
|
|
||||||
return;
|
|
||||||
} catch (ExecutionException | IOException e) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return true;
|
return true;
|
||||||
@@ -472,7 +372,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||||
// Bind a server socket for receiving invitation connections
|
// Bind a server socket for receiving key agreement connections
|
||||||
BluetoothServerSocket ss;
|
BluetoothServerSocket ss;
|
||||||
try {
|
try {
|
||||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||||
@@ -536,115 +436,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiscoveryTask implements Callable<BluetoothSocket> {
|
|
||||||
|
|
||||||
private final String uuid;
|
|
||||||
|
|
||||||
private DiscoveryTask(String uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BluetoothSocket call() throws Exception {
|
|
||||||
// Repeat discovery until we connect or get interrupted
|
|
||||||
while (true) {
|
|
||||||
// Discover nearby devices
|
|
||||||
LOG.info("Discovering nearby devices");
|
|
||||||
List<String> addresses = discoverDevices();
|
|
||||||
if (addresses.isEmpty()) {
|
|
||||||
LOG.info("No devices discovered");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Connect to any device with the right UUID
|
|
||||||
for (String address : addresses) {
|
|
||||||
BluetoothSocket s = connect(address, uuid);
|
|
||||||
if (s != null) {
|
|
||||||
LOG.info("Outgoing connection");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> discoverDevices() throws InterruptedException {
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(FOUND);
|
|
||||||
filter.addAction(DISCOVERY_FINISHED);
|
|
||||||
DiscoveryReceiver disco = new DiscoveryReceiver();
|
|
||||||
appContext.registerReceiver(disco, filter);
|
|
||||||
LOG.info("Starting discovery");
|
|
||||||
adapter.startDiscovery();
|
|
||||||
return disco.waitForAddresses();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DiscoveryReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
private final CountDownLatch finished = new CountDownLatch(1);
|
|
||||||
private final List<String> addresses = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
|
||||||
String action = intent.getAction();
|
|
||||||
if (action.equals(DISCOVERY_FINISHED)) {
|
|
||||||
LOG.info("Discovery finished");
|
|
||||||
ctx.unregisterReceiver(this);
|
|
||||||
finished.countDown();
|
|
||||||
} else if (action.equals(FOUND)) {
|
|
||||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Discovered device: " +
|
|
||||||
scrubMacAddress(d.getAddress()));
|
|
||||||
}
|
|
||||||
addresses.add(d.getAddress());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> waitForAddresses() throws InterruptedException {
|
|
||||||
finished.await();
|
|
||||||
List<String> shuffled = new ArrayList<>(addresses);
|
|
||||||
Collections.shuffle(shuffled);
|
|
||||||
return shuffled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ListeningTask implements Callable<BluetoothSocket> {
|
|
||||||
|
|
||||||
private final BluetoothServerSocket serverSocket;
|
|
||||||
|
|
||||||
private ListeningTask(BluetoothServerSocket serverSocket) {
|
|
||||||
this.serverSocket = serverSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BluetoothSocket call() throws IOException {
|
|
||||||
BluetoothSocket s = serverSocket.accept();
|
|
||||||
LOG.info("Incoming connection");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ReadableTask implements Callable<BluetoothSocket> {
|
|
||||||
|
|
||||||
private final Callable<BluetoothSocket> connectionTask;
|
|
||||||
|
|
||||||
private ReadableTask(Callable<BluetoothSocket> connectionTask) {
|
|
||||||
this.connectionTask = connectionTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BluetoothSocket call() throws Exception {
|
|
||||||
BluetoothSocket s = connectionTask.call();
|
|
||||||
InputStream in = s.getInputStream();
|
|
||||||
while (in.available() == 0) {
|
|
||||||
LOG.info("Waiting for data");
|
|
||||||
Thread.sleep(1000);
|
|
||||||
}
|
|
||||||
LOG.info("Data available");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||||
|
|
||||||
private final BluetoothServerSocket ss;
|
private final BluetoothServerSocket ss;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import net.freehaven.tor.control.EventHandler;
|
|||||||
import net.freehaven.tor.control.TorControlConnection;
|
import net.freehaven.tor.control.TorControlConnection;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -589,17 +588,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
|
||||||
long timeout, boolean alice) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ public interface CryptoComponent {
|
|||||||
|
|
||||||
SecretKey generateSecretKey();
|
SecretKey generateSecretKey();
|
||||||
|
|
||||||
PseudoRandom getPseudoRandom(int seed1, int seed2);
|
|
||||||
|
|
||||||
SecureRandom getSecureRandom();
|
SecureRandom getSecureRandom();
|
||||||
|
|
||||||
KeyPair generateAgreementKeyPair();
|
KeyPair generateAgreementKeyPair();
|
||||||
@@ -24,15 +22,6 @@ public interface CryptoComponent {
|
|||||||
|
|
||||||
KeyParser getMessageKeyParser();
|
KeyParser getMessageKeyParser();
|
||||||
|
|
||||||
/** Generates a random invitation code. */
|
|
||||||
int generateBTInvitationCode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a confirmation code from the given master secret.
|
|
||||||
* @param alice whether the code is for use by Alice or Bob.
|
|
||||||
*/
|
|
||||||
int deriveBTConfirmationCode(SecretKey master, boolean alice);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a stream header key from the given master secret.
|
* Derives a stream header key from the given master secret.
|
||||||
* @param alice whether the key is for use by Alice or Bob.
|
* @param alice whether the key is for use by Alice or Bob.
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A deterministic pseudo-random number generator.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface PseudoRandom {
|
|
||||||
|
|
||||||
byte[] nextBytes(int bytes);
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,9 @@ public interface StreamDecrypterFactory {
|
|||||||
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
|
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
|
* Creates a {@link StreamDecrypter} for decrypting a contact exchange
|
||||||
|
* stream.
|
||||||
*/
|
*/
|
||||||
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ public interface StreamEncrypterFactory {
|
|||||||
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
|
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
|
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
||||||
|
* stream.
|
||||||
*/
|
*/
|
||||||
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.invitation;
|
|
||||||
|
|
||||||
public interface InvitationConstants {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The connection timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
long CONNECTION_TIMEOUT = 60 * 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The confirmation timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
long CONFIRMATION_TIMEOUT = 60 * 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of bits in an invitation or confirmation code. Codes must fit
|
|
||||||
* into six decimal digits.
|
|
||||||
*/
|
|
||||||
int CODE_BITS = 19;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.invitation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for receiving updates about the state of an
|
|
||||||
* {@link InvitationTask}.
|
|
||||||
*/
|
|
||||||
public interface InvitationListener {
|
|
||||||
|
|
||||||
/** Called if a connection to the remote peer is established. */
|
|
||||||
void connectionSucceeded();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if a connection to the remote peer cannot be established. This
|
|
||||||
* indicates that the protocol has ended unsuccessfully.
|
|
||||||
*/
|
|
||||||
void connectionFailed();
|
|
||||||
|
|
||||||
/** Called if key agreement with the remote peer succeeds. */
|
|
||||||
void keyAgreementSucceeded(int localCode, int remoteCode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if key agreement with the remote peer fails or the connection is
|
|
||||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
|
||||||
*/
|
|
||||||
void keyAgreementFailed();
|
|
||||||
|
|
||||||
/** Called if the remote peer's confirmation check succeeds. */
|
|
||||||
void remoteConfirmationSucceeded();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if remote peer's confirmation check fails or the connection is
|
|
||||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
|
||||||
*/
|
|
||||||
void remoteConfirmationFailed();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if the exchange of pseudonyms succeeds. This indicates that the
|
|
||||||
* protocol has ended successfully.
|
|
||||||
*/
|
|
||||||
void pseudonymExchangeSucceeded(String remoteName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if the exchange of pseudonyms fails or the connection is lost.
|
|
||||||
* This indicates that the protocol has ended unsuccessfully.
|
|
||||||
*/
|
|
||||||
void pseudonymExchangeFailed();
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A snapshot of the state of an {@link InvitationTask}.
|
|
||||||
*/
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
public class InvitationState {
|
|
||||||
|
|
||||||
private final int localInvitationCode, remoteInvitationCode;
|
|
||||||
private final int localConfirmationCode, remoteConfirmationCode;
|
|
||||||
private final boolean connected, connectionFailed;
|
|
||||||
private final boolean localCompared, remoteCompared;
|
|
||||||
private final boolean localMatched, remoteMatched;
|
|
||||||
@Nullable
|
|
||||||
private final String contactName;
|
|
||||||
|
|
||||||
public InvitationState(int localInvitationCode, int remoteInvitationCode,
|
|
||||||
int localConfirmationCode, int remoteConfirmationCode,
|
|
||||||
boolean connected, boolean connectionFailed, boolean localCompared,
|
|
||||||
boolean remoteCompared, boolean localMatched,
|
|
||||||
boolean remoteMatched, @Nullable String contactName) {
|
|
||||||
this.localInvitationCode = localInvitationCode;
|
|
||||||
this.remoteInvitationCode = remoteInvitationCode;
|
|
||||||
this.localConfirmationCode = localConfirmationCode;
|
|
||||||
this.remoteConfirmationCode = remoteConfirmationCode;
|
|
||||||
this.connected = connected;
|
|
||||||
this.connectionFailed = connectionFailed;
|
|
||||||
this.localCompared = localCompared;
|
|
||||||
this.remoteCompared = remoteCompared;
|
|
||||||
this.localMatched = localMatched;
|
|
||||||
this.remoteMatched = remoteMatched;
|
|
||||||
this.contactName = contactName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLocalInvitationCode() {
|
|
||||||
return localInvitationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRemoteInvitationCode() {
|
|
||||||
return remoteInvitationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLocalConfirmationCode() {
|
|
||||||
return localConfirmationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRemoteConfirmationCode() {
|
|
||||||
return remoteConfirmationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getConnected() {
|
|
||||||
return connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getContactName() {
|
|
||||||
return contactName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A task for exchanging invitations with a remote peer.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface InvitationTask {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates tasks for exchanging invitations with remote peers.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface InvitationTaskFactory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a task using the given local and remote invitation codes.
|
|
||||||
*/
|
|
||||||
InvitationTask createTask(int localCode, int remoteCode);
|
|
||||||
}
|
|
||||||
@@ -32,11 +32,6 @@ public interface PluginManager {
|
|||||||
*/
|
*/
|
||||||
Collection<DuplexPlugin> getDuplexPlugins();
|
Collection<DuplexPlugin> getDuplexPlugins();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns any duplex plugins that support invitations.
|
|
||||||
*/
|
|
||||||
Collection<DuplexPlugin> getInvitationPlugins();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns any duplex plugins that support key agreement.
|
* Returns any duplex plugins that support key agreement.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.api.plugin.duplex;
|
package org.briarproject.bramble.api.plugin.duplex;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -23,20 +22,6 @@ public interface DuplexPlugin extends Plugin {
|
|||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection createConnection(ContactId c);
|
DuplexTransportConnection createConnection(ContactId c);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the plugin supports exchanging invitations.
|
|
||||||
*/
|
|
||||||
boolean supportsInvitations();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to create and return an invitation connection to the remote
|
|
||||||
* peer. Returns null if no connection can be established within the given
|
|
||||||
* time.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
|
||||||
long timeout, boolean alice);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the plugin supports short-range key agreement.
|
* Returns true if the plugin supports short-range key agreement.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public interface StreamReaderFactory {
|
|||||||
InputStream createStreamReader(InputStream in, StreamContext ctx);
|
InputStream createStreamReader(InputStream in, StreamContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link InputStream InputStream} for reading from an
|
* Creates an {@link InputStream InputStream} for reading from a contact
|
||||||
* invitation stream.
|
* exchangestream.
|
||||||
*/
|
*/
|
||||||
InputStream createInvitationStreamReader(InputStream in,
|
InputStream createContactExchangeStreamReader(InputStream in,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public interface StreamWriterFactory {
|
|||||||
OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
|
OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link OutputStream OutputStream} for writing to an
|
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
||||||
* invitation stream.
|
* exchange stream.
|
||||||
*/
|
*/
|
||||||
OutputStream createInvitationStreamWriter(OutputStream out,
|
OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
|
|||||||
import org.briarproject.bramble.db.DatabaseModule;
|
import org.briarproject.bramble.db.DatabaseModule;
|
||||||
import org.briarproject.bramble.event.EventModule;
|
import org.briarproject.bramble.event.EventModule;
|
||||||
import org.briarproject.bramble.identity.IdentityModule;
|
import org.briarproject.bramble.identity.IdentityModule;
|
||||||
import org.briarproject.bramble.invitation.InvitationModule;
|
|
||||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
import org.briarproject.bramble.plugin.PluginModule;
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
@@ -32,7 +31,6 @@ import dagger.Module;
|
|||||||
DatabaseExecutorModule.class,
|
DatabaseExecutorModule.class,
|
||||||
EventModule.class,
|
EventModule.class,
|
||||||
IdentityModule.class,
|
IdentityModule.class,
|
||||||
InvitationModule.class,
|
|
||||||
KeyAgreementModule.class,
|
KeyAgreementModule.class,
|
||||||
LifecycleModule.class,
|
LifecycleModule.class,
|
||||||
PluginModule.class,
|
PluginModule.class,
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
private volatile boolean alice;
|
private volatile boolean alice;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ContactExchangeTaskImpl(DatabaseComponent db,
|
ContactExchangeTaskImpl(DatabaseComponent db,
|
||||||
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
||||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||||
ConnectionManager connectionManager, ContactManager contactManager,
|
ConnectionManager connectionManager, ContactManager contactManager,
|
||||||
@@ -146,12 +146,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
|
|
||||||
// Create the readers
|
// Create the readers
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
streamReaderFactory.createInvitationStreamReader(in,
|
streamReaderFactory.createContactExchangeStreamReader(in,
|
||||||
alice ? bobHeaderKey : aliceHeaderKey);
|
alice ? bobHeaderKey : aliceHeaderKey);
|
||||||
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
||||||
// Create the writers
|
// Create the writers
|
||||||
OutputStream streamWriter =
|
OutputStream streamWriter =
|
||||||
streamWriterFactory.createInvitationStreamWriter(out,
|
streamWriterFactory.createContactExchangeStreamWriter(out,
|
||||||
alice ? aliceHeaderKey : bobHeaderKey);
|
alice ? aliceHeaderKey : bobHeaderKey);
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
@@ -41,7 +40,6 @@ import java.util.logging.Logger;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_BITS;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
||||||
@@ -68,9 +66,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return s.getBytes(Charset.forName("US-ASCII"));
|
return s.getBytes(Charset.forName("US-ASCII"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// KDF labels for bluetooth confirmation code derivation
|
|
||||||
private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
|
|
||||||
private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
|
|
||||||
// KDF labels for contact exchange stream header key derivation
|
// KDF labels for contact exchange stream header key derivation
|
||||||
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||||
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||||
@@ -171,14 +166,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return new SecretKey(b);
|
return new SecretKey(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
|
|
||||||
byte[] seed = new byte[INT_32_BYTES * 2];
|
|
||||||
ByteUtils.writeUint32(seed1, seed, 0);
|
|
||||||
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
|
|
||||||
return new PseudoRandomImpl(seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecureRandom getSecureRandom() {
|
public SecureRandom getSecureRandom() {
|
||||||
return secureRandom;
|
return secureRandom;
|
||||||
@@ -250,20 +237,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return messageEncrypter.getKeyParser();
|
return messageEncrypter.getKeyParser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int generateBTInvitationCode() {
|
|
||||||
int codeBytes = (CODE_BITS + 7) / 8;
|
|
||||||
byte[] random = new byte[codeBytes];
|
|
||||||
secureRandom.nextBytes(random);
|
|
||||||
return ByteUtils.readUint(random, CODE_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int deriveBTConfirmationCode(SecretKey master, boolean alice) {
|
|
||||||
byte[] b = macKdf(master, alice ? BT_A_CONFIRM : BT_B_CONFIRM);
|
|
||||||
return ByteUtils.readUint(b, CODE_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveHeaderKey(SecretKey master,
|
public SecretKey deriveHeaderKey(SecretKey master,
|
||||||
boolean alice) {
|
boolean alice) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
public StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
public StreamEncrypter createContactExchangeStreamDecrypter(
|
||||||
SecretKey headerKey) {
|
OutputStream out, SecretKey headerKey) {
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
package org.briarproject.bramble.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriter;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A connection thread for the peer being Alice in the invitation protocol.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
class AliceConnector extends Connector {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(AliceConnector.class.getName());
|
|
||||||
|
|
||||||
AliceConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
|
||||||
BdfWriterFactory bdfWriterFactory,
|
|
||||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
|
||||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
|
||||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
|
||||||
group, plugin, localAuthor, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Create an incoming or outgoing connection
|
|
||||||
DuplexTransportConnection conn = createInvitationConnection(true);
|
|
||||||
if (conn == null) return;
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
|
||||||
// Don't proceed with more than one connection
|
|
||||||
if (group.getAndSetConnected()) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
|
||||||
tryToClose(conn, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Carry out the key agreement protocol
|
|
||||||
InputStream in;
|
|
||||||
OutputStream out;
|
|
||||||
BdfReader r;
|
|
||||||
BdfWriter w;
|
|
||||||
SecretKey master;
|
|
||||||
try {
|
|
||||||
in = conn.getReader().getInputStream();
|
|
||||||
out = conn.getWriter().getOutputStream();
|
|
||||||
r = bdfReaderFactory.createReader(in);
|
|
||||||
w = bdfWriterFactory.createWriter(out);
|
|
||||||
// Alice goes first
|
|
||||||
sendPublicKeyHash(w);
|
|
||||||
byte[] hash = receivePublicKeyHash(r);
|
|
||||||
sendPublicKey(w);
|
|
||||||
byte[] key = receivePublicKey(r);
|
|
||||||
master = deriveMasterSecret(hash, key, true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
group.keyAgreementFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
return;
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
group.keyAgreementFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The key agreement succeeded - derive the confirmation codes
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
|
||||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
|
||||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
|
||||||
group.keyAgreementSucceeded(aliceCode, bobCode);
|
|
||||||
// Exchange confirmation results
|
|
||||||
boolean localMatched, remoteMatched;
|
|
||||||
try {
|
|
||||||
localMatched = group.waitForLocalConfirmationResult();
|
|
||||||
sendConfirmation(w, localMatched);
|
|
||||||
remoteMatched = receiveConfirmation(r);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
group.remoteConfirmationFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
return;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.warning("Interrupted while waiting for confirmation");
|
|
||||||
group.remoteConfirmationFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
|
||||||
else group.remoteConfirmationFailed();
|
|
||||||
if (!(localMatched && remoteMatched)) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " confirmation failed");
|
|
||||||
tryToClose(conn, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Confirmation succeeded - upgrade to a secure connection
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " confirmation succeeded");
|
|
||||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
|
||||||
plugin.getId(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package org.briarproject.bramble.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriter;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A connection thread for the peer being Bob in the invitation protocol.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
class BobConnector extends Connector {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(BobConnector.class.getName());
|
|
||||||
|
|
||||||
BobConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
|
||||||
BdfWriterFactory bdfWriterFactory,
|
|
||||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
|
||||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
|
||||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
|
||||||
group, plugin, localAuthor, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Create an incoming or outgoing connection
|
|
||||||
DuplexTransportConnection conn = createInvitationConnection(false);
|
|
||||||
if (conn == null) return;
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
|
||||||
// Carry out the key agreement protocol
|
|
||||||
InputStream in;
|
|
||||||
OutputStream out;
|
|
||||||
BdfReader r;
|
|
||||||
BdfWriter w;
|
|
||||||
SecretKey master;
|
|
||||||
try {
|
|
||||||
in = conn.getReader().getInputStream();
|
|
||||||
out = conn.getWriter().getOutputStream();
|
|
||||||
r = bdfReaderFactory.createReader(in);
|
|
||||||
w = bdfWriterFactory.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);
|
|
||||||
master = deriveMasterSecret(hash, key, false);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
group.keyAgreementFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
return;
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
group.keyAgreementFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The key agreement succeeded - derive the confirmation codes
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
|
||||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
|
||||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
|
||||||
group.keyAgreementSucceeded(bobCode, aliceCode);
|
|
||||||
// Exchange confirmation results
|
|
||||||
boolean localMatched, remoteMatched;
|
|
||||||
try {
|
|
||||||
remoteMatched = receiveConfirmation(r);
|
|
||||||
localMatched = group.waitForLocalConfirmationResult();
|
|
||||||
sendConfirmation(w, localMatched);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
group.remoteConfirmationFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
return;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.warning("Interrupted while waiting for confirmation");
|
|
||||||
group.remoteConfirmationFailed();
|
|
||||||
tryToClose(conn, true);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
|
||||||
else group.remoteConfirmationFailed();
|
|
||||||
if (!(localMatched && remoteMatched)) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " confirmation failed");
|
|
||||||
tryToClose(conn, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Confirmation succeeded - upgrade to a secure connection
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " confirmation succeeded");
|
|
||||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
|
||||||
plugin.getId(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package org.briarproject.bramble.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriter;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
|
||||||
|
|
||||||
// FIXME: This class has way too many dependencies
|
|
||||||
@NotNullByDefault
|
|
||||||
abstract class Connector extends Thread {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(Connector.class.getName());
|
|
||||||
private static final String LABEL_PUBLIC_KEY =
|
|
||||||
"org.briarproject.bramble.invitation.PUBLIC_KEY";
|
|
||||||
|
|
||||||
protected final CryptoComponent crypto;
|
|
||||||
protected final BdfReaderFactory bdfReaderFactory;
|
|
||||||
protected final BdfWriterFactory bdfWriterFactory;
|
|
||||||
protected final ContactExchangeTask contactExchangeTask;
|
|
||||||
protected final ConnectorGroup group;
|
|
||||||
protected final DuplexPlugin plugin;
|
|
||||||
protected final LocalAuthor localAuthor;
|
|
||||||
protected final PseudoRandom random;
|
|
||||||
protected final String pluginName;
|
|
||||||
|
|
||||||
private final KeyPair keyPair;
|
|
||||||
private final KeyParser keyParser;
|
|
||||||
|
|
||||||
Connector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
|
||||||
BdfWriterFactory bdfWriterFactory,
|
|
||||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
|
||||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
|
||||||
super("Connector");
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.contactExchangeTask = contactExchangeTask;
|
|
||||||
this.group = group;
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.localAuthor = localAuthor;
|
|
||||||
this.random = random;
|
|
||||||
pluginName = plugin.getClass().getName();
|
|
||||||
keyPair = crypto.generateAgreementKeyPair();
|
|
||||||
keyParser = crypto.getAgreementKeyParser();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
DuplexTransportConnection createInvitationConnection(boolean alice) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " creating invitation connection");
|
|
||||||
return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT,
|
|
||||||
alice);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendPublicKeyHash(BdfWriter w) throws IOException {
|
|
||||||
byte[] hash =
|
|
||||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
|
||||||
w.writeRaw(hash);
|
|
||||||
w.flush();
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] receivePublicKeyHash(BdfReader r) throws IOException {
|
|
||||||
int hashLength = crypto.getHashLength();
|
|
||||||
byte[] b = r.readRaw(hashLength);
|
|
||||||
if (b.length < hashLength) throw new FormatException();
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendPublicKey(BdfWriter w) throws IOException {
|
|
||||||
byte[] key = keyPair.getPublic().getEncoded();
|
|
||||||
w.writeRaw(key);
|
|
||||||
w.flush();
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] receivePublicKey(BdfReader r)
|
|
||||||
throws GeneralSecurityException, IOException {
|
|
||||||
byte[] b = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
|
||||||
keyParser.parsePublicKey(b);
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
SecretKey deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
// Check that the hash matches the key
|
|
||||||
byte[] keyHash =
|
|
||||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
|
||||||
if (!Arrays.equals(hash, keyHash)) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " hash does not match key");
|
|
||||||
throw new GeneralSecurityException();
|
|
||||||
}
|
|
||||||
// Derive the master secret
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " deriving master secret");
|
|
||||||
return crypto.deriveMasterSecret(key, keyPair, alice);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendConfirmation(BdfWriter w, boolean confirmed) throws IOException {
|
|
||||||
w.writeBoolean(confirmed);
|
|
||||||
w.flush();
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " sent confirmation: " + confirmed);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean receiveConfirmation(BdfReader r) throws IOException {
|
|
||||||
boolean confirmed = r.readBoolean();
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " received confirmation: " + confirmed);
|
|
||||||
return confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void tryToClose(DuplexTransportConnection conn,
|
|
||||||
boolean exception) {
|
|
||||||
try {
|
|
||||||
LOG.info("Closing connection");
|
|
||||||
conn.getReader().dispose(exception, true);
|
|
||||||
conn.getWriter().dispose(exception);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
package org.briarproject.bramble.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationListener;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationState;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A task consisting of one or more parallel connection attempts.
|
|
||||||
*/
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class ConnectorGroup extends Thread implements InvitationTask,
|
|
||||||
ContactExchangeListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(ConnectorGroup.class.getName());
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
private final ContactExchangeTask contactExchangeTask;
|
|
||||||
private final IdentityManager identityManager;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
private final int localInvitationCode, remoteInvitationCode;
|
|
||||||
private final Collection<InvitationListener> listeners;
|
|
||||||
private final AtomicBoolean connected;
|
|
||||||
private final CountDownLatch localConfirmationLatch;
|
|
||||||
private final Lock lock = new ReentrantLock();
|
|
||||||
|
|
||||||
// The following are locking: lock
|
|
||||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
|
||||||
private boolean connectionFailed = false;
|
|
||||||
private boolean localCompared = false, remoteCompared = false;
|
|
||||||
private boolean localMatched = false, remoteMatched = false;
|
|
||||||
private String remoteName = null;
|
|
||||||
|
|
||||||
ConnectorGroup(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
|
||||||
BdfWriterFactory bdfWriterFactory,
|
|
||||||
ContactExchangeTask contactExchangeTask,
|
|
||||||
IdentityManager identityManager, PluginManager pluginManager,
|
|
||||||
int localInvitationCode, int remoteInvitationCode) {
|
|
||||||
super("ConnectorGroup");
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.contactExchangeTask = contactExchangeTask;
|
|
||||||
this.identityManager = identityManager;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
this.localInvitationCode = localInvitationCode;
|
|
||||||
this.remoteInvitationCode = remoteInvitationCode;
|
|
||||||
listeners = new CopyOnWriteArrayList<InvitationListener>();
|
|
||||||
connected = new AtomicBoolean(false);
|
|
||||||
localConfirmationLatch = new CountDownLatch(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InvitationState addListener(InvitationListener l) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
listeners.add(l);
|
|
||||||
return new InvitationState(localInvitationCode,
|
|
||||||
remoteInvitationCode, localConfirmationCode,
|
|
||||||
remoteConfirmationCode, connected.get(), connectionFailed,
|
|
||||||
localCompared, remoteCompared, localMatched, remoteMatched,
|
|
||||||
remoteName);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeListener(InvitationListener l) {
|
|
||||||
listeners.remove(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connect() {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
LocalAuthor localAuthor;
|
|
||||||
// Load the local pseudonym
|
|
||||||
try {
|
|
||||||
localAuthor = identityManager.getLocalAuthor();
|
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
connectionFailed = true;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
for (InvitationListener l : listeners) l.connectionFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Start the connection threads
|
|
||||||
Collection<Connector> connectors = new ArrayList<Connector>();
|
|
||||||
// Alice is the party with the smaller invitation code
|
|
||||||
if (localInvitationCode < remoteInvitationCode) {
|
|
||||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
|
||||||
Connector c = createAliceConnector(plugin, localAuthor);
|
|
||||||
connectors.add(c);
|
|
||||||
c.start();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
|
||||||
Connector c = createBobConnector(plugin, localAuthor);
|
|
||||||
connectors.add(c);
|
|
||||||
c.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Wait for the connection threads to finish
|
|
||||||
try {
|
|
||||||
for (Connector c : connectors) c.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.warning("Interrupted while waiting for connectors");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
// If none of the threads connected, inform the listeners
|
|
||||||
if (!connected.get()) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
connectionFailed = true;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
for (InvitationListener l : listeners) l.connectionFailed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Connector createAliceConnector(DuplexPlugin plugin,
|
|
||||||
LocalAuthor localAuthor) {
|
|
||||||
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
|
|
||||||
remoteInvitationCode);
|
|
||||||
return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
|
||||||
contactExchangeTask, this, plugin, localAuthor, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Connector createBobConnector(DuplexPlugin plugin,
|
|
||||||
LocalAuthor localAuthor) {
|
|
||||||
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
|
|
||||||
localInvitationCode);
|
|
||||||
return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
|
||||||
contactExchangeTask, this, plugin, localAuthor, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localConfirmationSucceeded() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
localCompared = true;
|
|
||||||
localMatched = true;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
localConfirmationLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localConfirmationFailed() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
localCompared = true;
|
|
||||||
localMatched = false;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
localConfirmationLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean getAndSetConnected() {
|
|
||||||
boolean redundant = connected.getAndSet(true);
|
|
||||||
if (!redundant)
|
|
||||||
for (InvitationListener l : listeners) l.connectionSucceeded();
|
|
||||||
return redundant;
|
|
||||||
}
|
|
||||||
|
|
||||||
void keyAgreementSucceeded(int localCode, int remoteCode) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
localConfirmationCode = localCode;
|
|
||||||
remoteConfirmationCode = remoteCode;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
for (InvitationListener l : listeners)
|
|
||||||
l.keyAgreementSucceeded(localCode, remoteCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void keyAgreementFailed() {
|
|
||||||
for (InvitationListener l : listeners) l.keyAgreementFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean waitForLocalConfirmationResult() throws InterruptedException {
|
|
||||||
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
return localMatched;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void remoteConfirmationSucceeded() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
remoteCompared = true;
|
|
||||||
remoteMatched = true;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
for (InvitationListener l : listeners) l.remoteConfirmationSucceeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void remoteConfirmationFailed() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
remoteCompared = true;
|
|
||||||
remoteMatched = false;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
for (InvitationListener l : listeners) l.remoteConfirmationFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contactExchangeSucceeded(Author remoteAuthor) {
|
|
||||||
String name = remoteAuthor.getName();
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
remoteName = name;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
for (InvitationListener l : listeners)
|
|
||||||
l.pseudonymExchangeSucceeded(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void duplicateContact(Author remoteAuthor) {
|
|
||||||
// TODO differentiate
|
|
||||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contactExchangeFailed() {
|
|
||||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.briarproject.bramble.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
|
||||||
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public class InvitationModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
InvitationTaskFactory provideInvitationTaskFactory(
|
|
||||||
InvitationTaskFactoryImpl invitationTaskFactory) {
|
|
||||||
return invitationTaskFactory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package org.briarproject.bramble.invitation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
private final ContactExchangeTask contactExchangeTask;
|
|
||||||
private final IdentityManager identityManager;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
InvitationTaskFactoryImpl(CryptoComponent crypto,
|
|
||||||
BdfReaderFactory bdfReaderFactory,
|
|
||||||
BdfWriterFactory bdfWriterFactory,
|
|
||||||
ContactExchangeTask contactExchangeTask,
|
|
||||||
IdentityManager identityManager, PluginManager pluginManager) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.contactExchangeTask = contactExchangeTask;
|
|
||||||
this.identityManager = identityManager;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InvitationTask createTask(int localCode, int remoteCode) {
|
|
||||||
return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
|
|
||||||
contactExchangeTask, identityManager, pluginManager,
|
|
||||||
localCode, remoteCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -164,14 +164,6 @@ class PluginManagerImpl implements PluginManager, Service {
|
|||||||
return new ArrayList<DuplexPlugin>(duplexPlugins);
|
return new ArrayList<DuplexPlugin>(duplexPlugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<DuplexPlugin> getInvitationPlugins() {
|
|
||||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
|
||||||
for (DuplexPlugin d : duplexPlugins)
|
|
||||||
if (d.supportsInvitations()) supported.add(d);
|
|
||||||
return supported;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<DuplexPlugin> getKeyAgreementPlugins() {
|
public Collection<DuplexPlugin> getKeyAgreementPlugins() {
|
||||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin.tcp;
|
package org.briarproject.bramble.plugin.tcp;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
@@ -281,17 +280,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
|
||||||
long timeout, boolean alice) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream createInvitationStreamReader(InputStream in,
|
public InputStream createContactExchangeStreamReader(InputStream in,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamReaderImpl(
|
return new StreamReaderImpl(
|
||||||
streamDecrypterFactory.createInvitationStreamDecrypter(in,
|
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
|
||||||
headerKey));
|
headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream createInvitationStreamWriter(OutputStream out,
|
public OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamWriterImpl(
|
return new StreamWriterImpl(
|
||||||
streamEncrypterFactory.createInvitationStreamEncrypter(out,
|
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
|
||||||
headerKey));
|
headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.spongycastle.crypto.Digest;
|
import org.spongycastle.crypto.Digest;
|
||||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||||
@@ -11,11 +10,11 @@ import javax.annotation.concurrent.NotThreadSafe;
|
|||||||
|
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class PseudoRandomImpl implements PseudoRandom {
|
class PseudoRandom {
|
||||||
|
|
||||||
private final Salsa20Engine cipher = new Salsa20Engine();
|
private final Salsa20Engine cipher = new Salsa20Engine();
|
||||||
|
|
||||||
PseudoRandomImpl(byte[] seed) {
|
PseudoRandom(byte[] seed) {
|
||||||
// Hash the seed to produce a 32-byte key
|
// Hash the seed to produce a 32-byte key
|
||||||
byte[] key = new byte[32];
|
byte[] key = new byte[32];
|
||||||
Digest digest = new Blake2sDigest();
|
Digest digest = new Blake2sDigest();
|
||||||
@@ -26,8 +25,7 @@ class PseudoRandomImpl implements PseudoRandom {
|
|||||||
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
|
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
byte[] nextBytes(int length) {
|
||||||
public byte[] nextBytes(int length) {
|
|
||||||
byte[] in = new byte[length], out = new byte[length];
|
byte[] in = new byte[length], out = new byte[length];
|
||||||
cipher.processBytes(in, 0, length, out, 0);
|
cipher.processBytes(in, 0, length, out, 0);
|
||||||
return out;
|
return out;
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
|
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.SecureRandomSpi;
|
import java.security.SecureRandomSpi;
|
||||||
@@ -19,7 +17,7 @@ class PseudoSecureRandom extends SecureRandom {
|
|||||||
private final PseudoRandom pseudoRandom;
|
private final PseudoRandom pseudoRandom;
|
||||||
|
|
||||||
private PseudoSecureRandomSpi(byte[] seed) {
|
private PseudoSecureRandomSpi(byte[] seed) {
|
||||||
pseudoRandom = new PseudoRandomImpl(seed);
|
pseudoRandom = new PseudoRandom(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.bluetooth;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
@@ -19,33 +18,23 @@ import org.briarproject.bramble.util.OsUtils;
|
|||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CompletionService;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ExecutorCompletionService;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.bluetooth.BluetoothStateException;
|
import javax.bluetooth.BluetoothStateException;
|
||||||
import javax.bluetooth.DiscoveryAgent;
|
|
||||||
import javax.bluetooth.LocalDevice;
|
import javax.bluetooth.LocalDevice;
|
||||||
import javax.microedition.io.Connector;
|
import javax.microedition.io.Connector;
|
||||||
import javax.microedition.io.StreamConnection;
|
import javax.microedition.io.StreamConnection;
|
||||||
import javax.microedition.io.StreamConnectionNotifier;
|
import javax.microedition.io.StreamConnectionNotifier;
|
||||||
|
|
||||||
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;
|
||||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||||
@@ -67,7 +56,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
private final Backoff backoff;
|
private final Backoff backoff;
|
||||||
private final DuplexPluginCallback callback;
|
private final DuplexPluginCallback callback;
|
||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
private final Semaphore discoverySemaphore = new Semaphore(1);
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
@@ -273,95 +261,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return new BluetoothTransportConnection(this, s);
|
return new BluetoothTransportConnection(this, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
|
||||||
long timeout, boolean alice) {
|
|
||||||
if (!running) return null;
|
|
||||||
// Use the invitation codes to generate the UUID
|
|
||||||
byte[] b = r.nextBytes(UUID_BYTES);
|
|
||||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
|
||||||
String url = makeUrl("localhost", uuid);
|
|
||||||
// Make the device discoverable if possible
|
|
||||||
makeDeviceDiscoverable();
|
|
||||||
// Bind a server socket for receiving invitation connections
|
|
||||||
final StreamConnectionNotifier ss;
|
|
||||||
try {
|
|
||||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!running) {
|
|
||||||
tryToClose(ss);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Create the background tasks
|
|
||||||
CompletionService<StreamConnection> complete =
|
|
||||||
new ExecutorCompletionService<>(ioExecutor);
|
|
||||||
List<Future<StreamConnection>> futures = new ArrayList<>();
|
|
||||||
if (alice) {
|
|
||||||
// Return the first connected socket
|
|
||||||
futures.add(complete.submit(new ListeningTask(ss)));
|
|
||||||
futures.add(complete.submit(new DiscoveryTask(uuid)));
|
|
||||||
} else {
|
|
||||||
// Return the first socket with readable data
|
|
||||||
futures.add(complete.submit(new ReadableTask(
|
|
||||||
new ListeningTask(ss))));
|
|
||||||
futures.add(complete.submit(new ReadableTask(
|
|
||||||
new DiscoveryTask(uuid))));
|
|
||||||
}
|
|
||||||
StreamConnection chosen = null;
|
|
||||||
try {
|
|
||||||
Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS);
|
|
||||||
if (f == null) return null; // No task completed within the timeout
|
|
||||||
chosen = f.get();
|
|
||||||
return new BluetoothTransportConnection(this, chosen);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while exchanging invitations");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return null;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
// Closing the socket will terminate the listener task
|
|
||||||
tryToClose(ss);
|
|
||||||
closeSockets(futures, chosen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeSockets(final List<Future<StreamConnection>> futures,
|
|
||||||
@Nullable final StreamConnection chosen) {
|
|
||||||
ioExecutor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (Future<StreamConnection> f : futures) {
|
|
||||||
try {
|
|
||||||
if (f.cancel(true)) {
|
|
||||||
LOG.info("Cancelled task");
|
|
||||||
} else {
|
|
||||||
StreamConnection s = f.get();
|
|
||||||
if (s != null && s != chosen) {
|
|
||||||
LOG.info("Closing unwanted socket");
|
|
||||||
s.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while closing sockets");
|
|
||||||
return;
|
|
||||||
} catch (ExecutionException | IOException e) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return true;
|
return true;
|
||||||
@@ -376,7 +275,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
String url = makeUrl("localhost", uuid);
|
String url = makeUrl("localhost", uuid);
|
||||||
// Make the device discoverable if possible
|
// Make the device discoverable if possible
|
||||||
makeDeviceDiscoverable();
|
makeDeviceDiscoverable();
|
||||||
// Bind a server socket for receiving invitation connections
|
// Bind a server socket for receiving key agreementconnections
|
||||||
final StreamConnectionNotifier ss;
|
final StreamConnectionNotifier ss;
|
||||||
try {
|
try {
|
||||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||||
@@ -431,77 +330,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiscoveryTask implements Callable<StreamConnection> {
|
|
||||||
|
|
||||||
private final String uuid;
|
|
||||||
|
|
||||||
private DiscoveryTask(String uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamConnection call() throws Exception {
|
|
||||||
// Repeat discovery until we connect or get interrupted
|
|
||||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
|
||||||
while (true) {
|
|
||||||
if (!discoverySemaphore.tryAcquire())
|
|
||||||
throw new Exception("Discovery is already in progress");
|
|
||||||
try {
|
|
||||||
InvitationListener listener =
|
|
||||||
new InvitationListener(discoveryAgent, uuid);
|
|
||||||
discoveryAgent.startInquiry(GIAC, listener);
|
|
||||||
String url = listener.waitForUrl();
|
|
||||||
if (url != null) {
|
|
||||||
StreamConnection s = connect(url);
|
|
||||||
if (s != null) {
|
|
||||||
LOG.info("Outgoing connection");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
discoverySemaphore.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ListeningTask implements Callable<StreamConnection> {
|
|
||||||
|
|
||||||
private final StreamConnectionNotifier serverSocket;
|
|
||||||
|
|
||||||
private ListeningTask(StreamConnectionNotifier serverSocket) {
|
|
||||||
this.serverSocket = serverSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamConnection call() throws Exception {
|
|
||||||
StreamConnection s = serverSocket.acceptAndOpen();
|
|
||||||
LOG.info("Incoming connection");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ReadableTask implements Callable<StreamConnection> {
|
|
||||||
|
|
||||||
private final Callable<StreamConnection> connectionTask;
|
|
||||||
|
|
||||||
private ReadableTask(Callable<StreamConnection> connectionTask) {
|
|
||||||
this.connectionTask = connectionTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamConnection call() throws Exception {
|
|
||||||
StreamConnection s = connectionTask.call();
|
|
||||||
InputStream in = s.openInputStream();
|
|
||||||
while (in.available() == 0) {
|
|
||||||
LOG.info("Waiting for data");
|
|
||||||
Thread.sleep(1000);
|
|
||||||
}
|
|
||||||
LOG.info("Data available");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||||
|
|
||||||
private final StreamConnectionNotifier ss;
|
private final StreamConnectionNotifier ss;
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.bluetooth;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.bluetooth.BluetoothStateException;
|
|
||||||
import javax.bluetooth.DataElement;
|
|
||||||
import javax.bluetooth.DeviceClass;
|
|
||||||
import javax.bluetooth.DiscoveryAgent;
|
|
||||||
import javax.bluetooth.DiscoveryListener;
|
|
||||||
import javax.bluetooth.RemoteDevice;
|
|
||||||
import javax.bluetooth.ServiceRecord;
|
|
||||||
import javax.bluetooth.UUID;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
class InvitationListener implements DiscoveryListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(InvitationListener.class.getName());
|
|
||||||
|
|
||||||
private final AtomicInteger searches = new AtomicInteger(1);
|
|
||||||
private final CountDownLatch finished = new CountDownLatch(1);
|
|
||||||
private final DiscoveryAgent discoveryAgent;
|
|
||||||
private final String uuid;
|
|
||||||
|
|
||||||
private volatile String url = null;
|
|
||||||
|
|
||||||
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
|
|
||||||
this.discoveryAgent = discoveryAgent;
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
String waitForUrl() throws InterruptedException {
|
|
||||||
finished.await();
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
|
||||||
UUID[] uuids = new UUID[] {new UUID(uuid, false)};
|
|
||||||
// Try to discover the services associated with the UUID
|
|
||||||
try {
|
|
||||||
discoveryAgent.searchServices(null, uuids, device, this);
|
|
||||||
searches.incrementAndGet();
|
|
||||||
} catch (BluetoothStateException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
|
||||||
for (ServiceRecord record : services) {
|
|
||||||
// Does this service have a URL?
|
|
||||||
String serviceUrl = record.getConnectionURL(
|
|
||||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
|
||||||
if (serviceUrl == null) continue;
|
|
||||||
// Does this service have the UUID we're looking for?
|
|
||||||
Collection<String> uuids = new TreeSet<>();
|
|
||||||
findNestedClassIds(record.getAttributeValue(0x1), uuids);
|
|
||||||
for (String u : uuids) {
|
|
||||||
if (uuid.equalsIgnoreCase(u)) {
|
|
||||||
// The UUID matches - store the URL
|
|
||||||
url = serviceUrl;
|
|
||||||
finished.countDown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void inquiryCompleted(int discoveryType) {
|
|
||||||
if (searches.decrementAndGet() == 0) finished.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceSearchCompleted(int transaction, int response) {
|
|
||||||
if (searches.decrementAndGet() == 0) finished.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUIDs are sometimes buried in nested data elements
|
|
||||||
private void findNestedClassIds(Object o, Collection<String> ids) {
|
|
||||||
o = getDataElementValue(o);
|
|
||||||
if (o instanceof Enumeration<?>) {
|
|
||||||
for (Object o1 : Collections.list((Enumeration<?>) o))
|
|
||||||
findNestedClassIds(o1, ids);
|
|
||||||
} else if (o instanceof UUID) {
|
|
||||||
ids.add(o.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object getDataElementValue(Object o) {
|
|
||||||
if (o instanceof DataElement) {
|
|
||||||
// Bluecove throws an exception if the type is unknown
|
|
||||||
try {
|
|
||||||
return ((DataElement) o).getValue();
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin.modem;
|
package org.briarproject.bramble.plugin.modem;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
@@ -167,17 +166,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
|||||||
return new ModemTransportConnection();
|
return new ModemTransportConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
|
||||||
long timeout, boolean alice) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<!-- Since API 23, this is needed to add contacts via Bluetooth -->
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
||||||
@@ -292,16 +290,6 @@
|
|||||||
/>
|
/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="org.briarproject.briar.android.invitation.AddContactActivity"
|
|
||||||
android:label="@string/add_contact_title"
|
|
||||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
|
||||||
/>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
|
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
|
||||||
android:label="@string/add_contact_title"
|
android:label="@string/add_contact_title"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
@@ -89,8 +88,6 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
EventBus eventBus();
|
EventBus eventBus();
|
||||||
|
|
||||||
InvitationTaskFactory invitationTaskFactory();
|
|
||||||
|
|
||||||
AndroidNotificationManager androidNotificationManager();
|
AndroidNotificationManager androidNotificationManager();
|
||||||
|
|
||||||
ScreenFilterMonitor screenFilterMonitor();
|
ScreenFilterMonitor screenFilterMonitor();
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import org.briarproject.briar.android.forum.ForumModule;
|
|||||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||||
import org.briarproject.briar.android.invitation.AddContactActivity;
|
|
||||||
import org.briarproject.briar.android.keyagreement.IntroFragment;
|
import org.briarproject.briar.android.keyagreement.IntroFragment;
|
||||||
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
||||||
import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
||||||
@@ -91,8 +90,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(PanicPreferencesActivity activity);
|
void inject(PanicPreferencesActivity activity);
|
||||||
|
|
||||||
void inject(AddContactActivity activity);
|
|
||||||
|
|
||||||
void inject(KeyAgreementActivity activity);
|
void inject(KeyAgreementActivity activity);
|
||||||
|
|
||||||
void inject(ConversationActivity activity);
|
void inject(ConversationActivity activity);
|
||||||
|
|||||||
@@ -1,449 +0,0 @@
|
|||||||
package org.briarproject.briar.android.invitation;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationListener;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationState;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
|
||||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
|
||||||
import org.briarproject.briar.api.android.ReferenceManager;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
|
||||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.CONNECTED;
|
|
||||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.DETAILS;
|
|
||||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.WAIT_FOR_CONTACT;
|
|
||||||
|
|
||||||
public class AddContactActivity extends BriarActivity
|
|
||||||
implements InvitationListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(AddContactActivity.class.getName());
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
CryptoComponent crypto;
|
|
||||||
@Inject
|
|
||||||
InvitationTaskFactory invitationTaskFactory;
|
|
||||||
@Inject
|
|
||||||
ReferenceManager referenceManager;
|
|
||||||
|
|
||||||
private AddContactView view = null;
|
|
||||||
private InvitationTask task = null;
|
|
||||||
private long taskHandle = -1;
|
|
||||||
private AuthorId localAuthorId = null;
|
|
||||||
private int localInvitationCode = -1, remoteInvitationCode = -1;
|
|
||||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
|
||||||
private boolean connected = false, connectionFailed = false;
|
|
||||||
private boolean localCompared = false, remoteCompared = false;
|
|
||||||
private boolean localMatched = false, remoteMatched = false;
|
|
||||||
private String contactName = null;
|
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
|
||||||
@Inject
|
|
||||||
volatile IdentityManager identityManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle state) {
|
|
||||||
super.onCreate(state);
|
|
||||||
if (state == null) {
|
|
||||||
// This is a new activity
|
|
||||||
setView(new ChooseIdentityView(this));
|
|
||||||
} else {
|
|
||||||
// Restore the activity's state
|
|
||||||
byte[] b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
|
|
||||||
if (b != null) localAuthorId = new AuthorId(b);
|
|
||||||
taskHandle = state.getLong("briar.TASK_HANDLE", -1);
|
|
||||||
task = referenceManager.getReference(taskHandle,
|
|
||||||
InvitationTask.class);
|
|
||||||
|
|
||||||
if (task == null) {
|
|
||||||
// No background task - we must be in an initial or final state
|
|
||||||
localInvitationCode = state.getInt("briar.LOCAL_CODE");
|
|
||||||
remoteInvitationCode = state.getInt("briar.REMOTE_CODE");
|
|
||||||
connectionFailed = state.getBoolean("briar.FAILED");
|
|
||||||
contactName = state.getString("briar.CONTACT_NAME");
|
|
||||||
if (contactName != null) {
|
|
||||||
localCompared = remoteCompared = true;
|
|
||||||
localMatched = remoteMatched = true;
|
|
||||||
}
|
|
||||||
// Set the appropriate view for the state
|
|
||||||
if (localInvitationCode == -1) {
|
|
||||||
setView(new ChooseIdentityView(this));
|
|
||||||
} else if (remoteInvitationCode == -1) {
|
|
||||||
setView(new InvitationCodeView(this));
|
|
||||||
} else if (connectionFailed) {
|
|
||||||
setView(new ErrorView(this, R.string.connection_failed,
|
|
||||||
R.string.could_not_find_contact));
|
|
||||||
} else if (contactName == null) {
|
|
||||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
|
||||||
R.string.interfering));
|
|
||||||
} else {
|
|
||||||
showToastAndFinish();
|
|
||||||
}
|
|
||||||
} 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();
|
|
||||||
connected = s.getConnected();
|
|
||||||
connectionFailed = s.getConnectionFailed();
|
|
||||||
localCompared = s.getLocalCompared();
|
|
||||||
remoteCompared = s.getRemoteCompared();
|
|
||||||
localMatched = s.getLocalMatched();
|
|
||||||
remoteMatched = s.getRemoteMatched();
|
|
||||||
contactName = s.getContactName();
|
|
||||||
// Set the appropriate view for the state
|
|
||||||
if (localInvitationCode == -1) {
|
|
||||||
setView(new ChooseIdentityView(this));
|
|
||||||
} else if (remoteInvitationCode == -1) {
|
|
||||||
setView(new InvitationCodeView(this));
|
|
||||||
} else if (connectionFailed) {
|
|
||||||
setView(new ErrorView(AddContactActivity.this,
|
|
||||||
R.string.connection_failed,
|
|
||||||
R.string.could_not_find_contact));
|
|
||||||
} else if (connected && localConfirmationCode == -1) {
|
|
||||||
setView(new ConfirmationCodeView(this, CONNECTED));
|
|
||||||
} else if (localConfirmationCode == -1) {
|
|
||||||
setView(new InvitationCodeView(this, true));
|
|
||||||
} else if (!localCompared) {
|
|
||||||
setView(new ConfirmationCodeView(this));
|
|
||||||
} else if (!remoteCompared) {
|
|
||||||
setView(new ConfirmationCodeView(this, WAIT_FOR_CONTACT));
|
|
||||||
} else if (localMatched && remoteMatched) {
|
|
||||||
if (contactName == null) {
|
|
||||||
setView(new ConfirmationCodeView(this, DETAILS));
|
|
||||||
} else {
|
|
||||||
showToastAndFinish();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
|
||||||
R.string.interfering));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectActivity(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showToastAndFinish() {
|
|
||||||
String format = getString(R.string.contact_added_toast);
|
|
||||||
String text = String.format(format, contactName);
|
|
||||||
Toast.makeText(this, text, LENGTH_LONG).show();
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
view.populate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle state) {
|
|
||||||
super.onSaveInstanceState(state);
|
|
||||||
if (localAuthorId != null) {
|
|
||||||
byte[] b = localAuthorId.getBytes();
|
|
||||||
state.putByteArray("briar.LOCAL_AUTHOR_ID", b);
|
|
||||||
}
|
|
||||||
state.putInt("briar.LOCAL_CODE", localInvitationCode);
|
|
||||||
state.putInt("briar.REMOTE_CODE", remoteInvitationCode);
|
|
||||||
state.putBoolean("briar.FAILED", connectionFailed);
|
|
||||||
state.putString("briar.CONTACT_NAME", contactName);
|
|
||||||
if (task != null) state.putLong("briar.TASK_HANDLE", taskHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (task != null) task.removeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int request, int result, Intent data) {
|
|
||||||
if (request == REQUEST_BLUETOOTH) {
|
|
||||||
if (result != RESULT_CANCELED) reset(new InvitationCodeView(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
void setView(AddContactView view) {
|
|
||||||
this.view = view;
|
|
||||||
view.init(this);
|
|
||||||
setContentView(view);
|
|
||||||
getSupportActionBar().setTitle(R.string.add_contact_title);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(AddContactView view) {
|
|
||||||
// Don't reset localAuthorId
|
|
||||||
task = null;
|
|
||||||
taskHandle = -1;
|
|
||||||
localInvitationCode = -1;
|
|
||||||
localConfirmationCode = remoteConfirmationCode = -1;
|
|
||||||
connected = connectionFailed = false;
|
|
||||||
localCompared = remoteCompared = false;
|
|
||||||
localMatched = remoteMatched = false;
|
|
||||||
contactName = null;
|
|
||||||
setView(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadLocalAuthor() {
|
|
||||||
runOnDbThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
LocalAuthor author = identityManager.getLocalAuthor();
|
|
||||||
long duration = System.currentTimeMillis() - now;
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Loading author took " + duration + " ms");
|
|
||||||
setLocalAuthorId(author.getId());
|
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLocalAuthorId(final AuthorId localAuthorId) {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
AddContactActivity.this.localAuthorId = localAuthorId;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
int getLocalInvitationCode() {
|
|
||||||
if (localInvitationCode == -1)
|
|
||||||
localInvitationCode = crypto.generateBTInvitationCode();
|
|
||||||
return localInvitationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getRemoteInvitationCode() {
|
|
||||||
return remoteInvitationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remoteInvitationCodeEntered(int code) {
|
|
||||||
if (localAuthorId == null) throw new IllegalStateException();
|
|
||||||
if (localInvitationCode == -1) throw new IllegalStateException();
|
|
||||||
remoteInvitationCode = code;
|
|
||||||
|
|
||||||
// change UI to show a progress indicator
|
|
||||||
setView(new InvitationCodeView(this, true));
|
|
||||||
|
|
||||||
task = invitationTaskFactory.createTask(localInvitationCode, code);
|
|
||||||
taskHandle = referenceManager.putReference(task, InvitationTask.class);
|
|
||||||
task.addListener(AddContactActivity.this);
|
|
||||||
// Add a second listener so we can remove the first in onDestroy(),
|
|
||||||
// allowing the activity to be garbage collected if it's destroyed
|
|
||||||
task.addListener(new ReferenceCleaner(referenceManager, taskHandle));
|
|
||||||
task.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getLocalConfirmationCode() {
|
|
||||||
return localConfirmationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remoteConfirmationCodeEntered(int code) {
|
|
||||||
localCompared = true;
|
|
||||||
if (code == remoteConfirmationCode) {
|
|
||||||
localMatched = true;
|
|
||||||
if (remoteMatched) {
|
|
||||||
setView(new ConfirmationCodeView(this, DETAILS));
|
|
||||||
} else if (remoteCompared) {
|
|
||||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
|
||||||
R.string.interfering));
|
|
||||||
} else {
|
|
||||||
setView(new ConfirmationCodeView(this, WAIT_FOR_CONTACT));
|
|
||||||
}
|
|
||||||
task.localConfirmationSucceeded();
|
|
||||||
} else {
|
|
||||||
localMatched = false;
|
|
||||||
setView(new ErrorView(this, R.string.codes_do_not_match,
|
|
||||||
R.string.interfering));
|
|
||||||
task.localConfirmationFailed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionSucceeded() {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
connected = true;
|
|
||||||
setView(new ConfirmationCodeView(AddContactActivity.this,
|
|
||||||
CONNECTED));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionFailed() {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
connectionFailed = true;
|
|
||||||
setView(new ErrorView(AddContactActivity.this,
|
|
||||||
R.string.connection_failed,
|
|
||||||
R.string.could_not_find_contact));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyAgreementSucceeded(final int localCode,
|
|
||||||
final int remoteCode) {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
localConfirmationCode = localCode;
|
|
||||||
remoteConfirmationCode = remoteCode;
|
|
||||||
setView(new ConfirmationCodeView(AddContactActivity.this));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyAgreementFailed() {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
connectionFailed = true;
|
|
||||||
setView(new ErrorView(AddContactActivity.this,
|
|
||||||
R.string.connection_failed,
|
|
||||||
R.string.could_not_find_contact));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteConfirmationSucceeded() {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
remoteCompared = true;
|
|
||||||
remoteMatched = true;
|
|
||||||
if (localMatched) {
|
|
||||||
setView(new ConfirmationCodeView(AddContactActivity.this,
|
|
||||||
DETAILS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteConfirmationFailed() {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
remoteCompared = true;
|
|
||||||
remoteMatched = false;
|
|
||||||
if (localMatched) {
|
|
||||||
setView(new ErrorView(AddContactActivity.this,
|
|
||||||
R.string.codes_do_not_match, R.string.interfering));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pseudonymExchangeSucceeded(final String remoteName) {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
contactName = remoteName;
|
|
||||||
showToastAndFinish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pseudonymExchangeFailed() {
|
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
setView(new ErrorView(AddContactActivity.this,
|
|
||||||
R.string.connection_failed,
|
|
||||||
R.string.could_not_find_contact));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the reference to the invitation task when the task completes.
|
|
||||||
* This class is static to prevent memory leaks.
|
|
||||||
*/
|
|
||||||
private static class ReferenceCleaner implements InvitationListener {
|
|
||||||
|
|
||||||
private final ReferenceManager referenceManager;
|
|
||||||
private final long handle;
|
|
||||||
|
|
||||||
private ReferenceCleaner(ReferenceManager referenceManager,
|
|
||||||
long handle) {
|
|
||||||
this.referenceManager = referenceManager;
|
|
||||||
this.handle = handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionSucceeded() {
|
|
||||||
// Wait for key agreement to succeed or fail
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionFailed() {
|
|
||||||
referenceManager.removeReference(handle, InvitationTask.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyAgreementSucceeded(int localCode, int remoteCode) {
|
|
||||||
// Wait for remote confirmation to succeed or fail
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyAgreementFailed() {
|
|
||||||
referenceManager.removeReference(handle, InvitationTask.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteConfirmationSucceeded() {
|
|
||||||
// Wait for the pseudonym exchange to succeed or fail
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteConfirmationFailed() {
|
|
||||||
referenceManager.removeReference(handle, InvitationTask.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pseudonymExchangeSucceeded(String remoteName) {
|
|
||||||
referenceManager.removeReference(handle, InvitationTask.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pseudonymExchangeFailed() {
|
|
||||||
referenceManager.removeReference(handle, InvitationTask.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.briarproject.briar.android.invitation;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
|
|
||||||
abstract class AddContactView extends LinearLayout {
|
|
||||||
|
|
||||||
static final public int CODE_LEN = 6;
|
|
||||||
protected AddContactActivity container = null;
|
|
||||||
|
|
||||||
AddContactView(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(AddContactActivity container) {
|
|
||||||
this.container = container;
|
|
||||||
populate();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract void populate();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.briarproject.briar.android.invitation;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
|
||||||
|
|
||||||
class ChooseIdentityView extends AddContactView implements OnClickListener {
|
|
||||||
|
|
||||||
ChooseIdentityView(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void populate() {
|
|
||||||
removeAllViews();
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
|
||||||
(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
View view = inflater.inflate(R.layout.invitation_bluetooth_start, this);
|
|
||||||
|
|
||||||
Button continueButton = (Button) view.findViewById(R.id.continueButton);
|
|
||||||
continueButton.setOnClickListener(this);
|
|
||||||
|
|
||||||
container.loadLocalAuthor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
|
||||||
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
|
|
||||||
container.startActivityForResult(i, REQUEST_BLUETOOTH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
package org.briarproject.briar.android.invitation;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
|
||||||
|
|
||||||
class ConfirmationCodeView extends AddContactView {
|
|
||||||
|
|
||||||
public enum ConfirmationState { CONNECTED, ENTER_CODE, WAIT_FOR_CONTACT, DETAILS }
|
|
||||||
private ConfirmationState state;
|
|
||||||
|
|
||||||
ConfirmationCodeView(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
this.state = ConfirmationState.ENTER_CODE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfirmationCodeView(Context ctx, ConfirmationState state) {
|
|
||||||
super(ctx);
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void populate() {
|
|
||||||
removeAllViews();
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
|
||||||
(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
View view = inflater.inflate(R.layout.invitation_bluetooth_confirmation_code, this);
|
|
||||||
|
|
||||||
// local confirmation code
|
|
||||||
TextView code = (TextView) view.findViewById(R.id.codeView);
|
|
||||||
int localCode = container.getLocalConfirmationCode();
|
|
||||||
code.setText(String.format("%06d", localCode));
|
|
||||||
|
|
||||||
if (state != ConfirmationState.ENTER_CODE) {
|
|
||||||
// hide views we no longer need
|
|
||||||
view.findViewById(R.id.enterCodeTextView).setVisibility(View.GONE);
|
|
||||||
view.findViewById(R.id.codeEntryView).setVisibility(View.GONE);
|
|
||||||
view.findViewById(R.id.continueButton).setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// show progress indicator
|
|
||||||
view.findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
// show what we are waiting for
|
|
||||||
TextView connecting = (TextView) view.findViewById(R.id.waitingView);
|
|
||||||
int textId;
|
|
||||||
if (state == ConfirmationState.CONNECTED) {
|
|
||||||
textId = R.string.calculating_confirmation_code;
|
|
||||||
view.findViewById(R.id.yourConfirmationCodeView).setVisibility(View.GONE);
|
|
||||||
view.findViewById(R.id.codeView).setVisibility(View.GONE);
|
|
||||||
} else if (state == ConfirmationState.WAIT_FOR_CONTACT) {
|
|
||||||
textId = R.string.waiting_for_contact;
|
|
||||||
} else {
|
|
||||||
textId = R.string.exchanging_contact_details;
|
|
||||||
}
|
|
||||||
connecting.setText(ctx.getString(textId));
|
|
||||||
connecting.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// handle click on continue button
|
|
||||||
final EditText codeEntry = (EditText) view.findViewById(R.id.codeEntryView);
|
|
||||||
final Button continueButton = (Button) view.findViewById(R.id.continueButton);
|
|
||||||
continueButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
send(codeEntry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// activate continue button only when we have a 6 digit (CODE_LEN) code
|
|
||||||
codeEntry.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
continueButton.setEnabled(codeEntry.getText().length() == CODE_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
codeEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_GO && v.getText().length() == CODE_LEN) {
|
|
||||||
send(v);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void send(TextView codeEntry) {
|
|
||||||
int code = Integer.parseInt(codeEntry.getText().toString());
|
|
||||||
container.remoteConfirmationCodeEntered(code);
|
|
||||||
|
|
||||||
// Hide the soft keyboard
|
|
||||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
((InputMethodManager) o).hideSoftInputFromWindow(codeEntry.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package org.briarproject.briar.android.invitation;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
|
||||||
|
|
||||||
class ErrorView extends AddContactView implements OnClickListener {
|
|
||||||
|
|
||||||
private final int error;
|
|
||||||
private final int explanation;
|
|
||||||
|
|
||||||
ErrorView(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
this.error = R.string.connection_failed;
|
|
||||||
this.explanation = R.string.could_not_find_contact;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorView(Context ctx, int error, int explanation) {
|
|
||||||
super(ctx);
|
|
||||||
this.error = error;
|
|
||||||
this.explanation = explanation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void populate() {
|
|
||||||
removeAllViews();
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
|
||||||
(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
View view = inflater.inflate(R.layout.invitation_error, this);
|
|
||||||
|
|
||||||
TextView errorView = (TextView) view.findViewById(R.id.errorTextView);
|
|
||||||
errorView.setText(ctx.getString(error));
|
|
||||||
|
|
||||||
TextView explanationView = (TextView) view.findViewById(R.id.explanationTextView);
|
|
||||||
explanationView.setText(ctx.getString(explanation));
|
|
||||||
|
|
||||||
Button tryAgainButton = (Button) view.findViewById(R.id.tryAgainButton);
|
|
||||||
tryAgainButton.setOnClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
|
||||||
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
|
|
||||||
container.startActivityForResult(i, REQUEST_BLUETOOTH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
package org.briarproject.briar.android.invitation;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
|
||||||
|
|
||||||
class InvitationCodeView extends AddContactView {
|
|
||||||
|
|
||||||
private boolean waiting;
|
|
||||||
|
|
||||||
InvitationCodeView(Context ctx, boolean waiting) {
|
|
||||||
super(ctx);
|
|
||||||
this.waiting = waiting;
|
|
||||||
}
|
|
||||||
|
|
||||||
InvitationCodeView(Context ctx) {
|
|
||||||
this(ctx, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void populate() {
|
|
||||||
removeAllViews();
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
|
|
||||||
(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
View view = inflater.inflate(R.layout.invitation_bluetooth_invitation_code, this);
|
|
||||||
|
|
||||||
// local invitation code
|
|
||||||
TextView code = (TextView) view.findViewById(R.id.codeView);
|
|
||||||
int localCode = container.getLocalInvitationCode();
|
|
||||||
code.setText(String.format("%06d", localCode));
|
|
||||||
|
|
||||||
if (waiting) {
|
|
||||||
// hide views we no longer need
|
|
||||||
view.findViewById(R.id.enterCodeTextView).setVisibility(View.GONE);
|
|
||||||
view.findViewById(R.id.codeEntryView).setVisibility(View.GONE);
|
|
||||||
view.findViewById(R.id.continueButton).setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// show progress indicator
|
|
||||||
view.findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
// show which code we are waiting for
|
|
||||||
TextView connecting = (TextView) view.findViewById(R.id.waitingView);
|
|
||||||
int remoteCode = container.getRemoteInvitationCode();
|
|
||||||
String format = container.getString(R.string.searching_format);
|
|
||||||
connecting.setText(String.format(format, remoteCode));
|
|
||||||
connecting.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// handle click on continue button
|
|
||||||
final EditText codeEntry = (EditText) view.findViewById(R.id.codeEntryView);
|
|
||||||
final Button continueButton = (Button) view.findViewById(R.id.continueButton);
|
|
||||||
continueButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
send(codeEntry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// activate continue button only when we have a 6 digit (CODE_LEN) code
|
|
||||||
codeEntry.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
continueButton.setEnabled(codeEntry.getText().length() == CODE_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
codeEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_GO && v.getText().length() == CODE_LEN) {
|
|
||||||
send(v);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void send(TextView codeEntry) {
|
|
||||||
int code = Integer.parseInt(codeEntry.getText().toString());
|
|
||||||
container.remoteInvitationCodeEntered(code);
|
|
||||||
|
|
||||||
// Hide the soft keyboard
|
|
||||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
((InputMethodManager) o).hideSoftInputFromWindow(codeEntry.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/connectedView"
|
|
||||||
style="@style/BriarTextTitle"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/connected_to_contact"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:drawableLeft="@drawable/navigation_accept"
|
|
||||||
android:drawableStart="@drawable/navigation_accept"
|
|
||||||
android:gravity="center_vertical"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/yourConfirmationCodeView"
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/your_confirmation_code"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:layout_below="@+id/connectedView"
|
|
||||||
android:layout_centerHorizontal="true"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/codeView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:textSize="50sp"
|
|
||||||
android:textColor="@color/briar_text_secondary"
|
|
||||||
android:layout_below="@+id/yourConfirmationCodeView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
tools:text="1337"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/waitingView"
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/searching_format"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:layout_below="@+id/codeView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:gravity="center_horizontal"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_below="@+id/waitingView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/enterCodeTextView"
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/enter_confirmation_code"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:layout_below="@+id/codeView"
|
|
||||||
android:layout_centerHorizontal="true"/>
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/codeEntryView"
|
|
||||||
layout="@layout/view_code_entry"
|
|
||||||
android:layout_below="@+id/enterCodeTextView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/continueButton"
|
|
||||||
style="@style/BriarButton.Default"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/continue_button"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:enabled="false"
|
|
||||||
android:layout_below="@+id/codeEntryView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_margin="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/yourCodeView"
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/your_invitation_code"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:layout_centerHorizontal="true"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/codeView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:textSize="50sp"
|
|
||||||
android:textColor="@color/briar_text_secondary"
|
|
||||||
android:layout_below="@+id/yourCodeView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
tools:text="1337"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/waitingView"
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/searching_format"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:layout_below="@+id/codeView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:gravity="center_horizontal"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_below="@+id/waitingView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/enterCodeTextView"
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/enter_invitation_code"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:layout_below="@+id/codeView"
|
|
||||||
android:layout_centerHorizontal="true"/>
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/codeEntryView"
|
|
||||||
layout="@layout/view_code_entry"
|
|
||||||
android:layout_below="@+id/enterCodeTextView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/continueButton"
|
|
||||||
style="@style/BriarButton.Default"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/continue_button"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:enabled="false"
|
|
||||||
android:layout_below="@+id/codeEntryView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_margin="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/your_nickname"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinner"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/border_spinner"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:spinnerMode="dropdown"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_xlarge"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/bluetooth"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_xlarge"
|
|
||||||
android:text="@string/face_to_face"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/continueButton"
|
|
||||||
style="@style/BriarButton.Default"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:text="@string/continue_button"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/errorTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/connection_failed"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
android:textColor="@color/briar_text_primary"
|
|
||||||
android:drawableStart="@drawable/alerts_and_states_error"
|
|
||||||
android:drawableLeft="@drawable/alerts_and_states_error"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:padding="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/explanationTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/interfering"
|
|
||||||
android:textColor="@color/briar_text_primary"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:padding="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/tryAgainButton"
|
|
||||||
style="@style/BriarButton.Default"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/try_again_button"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_margin="@dimen/margin_medium"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<EditText
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/codeEntryView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="number"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:textSize="@dimen/text_size_xlarge"
|
|
||||||
android:ems="4"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:maxLength="6"
|
|
||||||
android:layout_margin="@dimen/margin_medium"
|
|
||||||
android:imeOptions="actionGo"
|
|
||||||
tools:text="123456"/>
|
|
||||||
@@ -98,24 +98,12 @@
|
|||||||
|
|
||||||
<!-- Adding Contacts -->
|
<!-- Adding Contacts -->
|
||||||
<string name="add_contact_title">Add a Contact</string>
|
<string name="add_contact_title">Add a Contact</string>
|
||||||
<string name="your_nickname">Choose the identity you want to use:</string>
|
|
||||||
<string name="face_to_face">You must meet up with the person you want to add as a contact.\n\nThis will prevent anyone from impersonating you or reading your messages in future.</string>
|
<string name="face_to_face">You must meet up with the person you want to add as a contact.\n\nThis will prevent anyone from impersonating you or reading your messages in future.</string>
|
||||||
<string name="continue_button">Continue</string>
|
<string name="continue_button">Continue</string>
|
||||||
<string name="your_invitation_code">Your invitation code is</string>
|
|
||||||
<string name="enter_invitation_code">Please enter your contact\'s invitation code:</string>
|
|
||||||
<string name="searching_format">Searching for contact with invitation code %06d\u2026</string>
|
|
||||||
<string name="connection_failed">Connection failed</string>
|
<string name="connection_failed">Connection failed</string>
|
||||||
<string name="could_not_find_contact">Briar could not find your contact nearby</string>
|
|
||||||
<string name="try_again_button">Try Again</string>
|
<string name="try_again_button">Try Again</string>
|
||||||
<string name="connected_to_contact">Connected to contact</string>
|
|
||||||
<string name="calculating_confirmation_code">Calculating confirmation code\u2026</string>
|
|
||||||
<string name="your_confirmation_code">Your confirmation code is</string>
|
|
||||||
<string name="enter_confirmation_code">Please enter your contact\'s confirmation code:</string>
|
|
||||||
<string name="waiting_for_contact">Waiting for contact\u2026</string>
|
|
||||||
<string name="waiting_for_contact_to_scan">Waiting for contact to scan and connect\u2026</string>
|
<string name="waiting_for_contact_to_scan">Waiting for contact to scan and connect\u2026</string>
|
||||||
<string name="exchanging_contact_details">Exchanging contact details\u2026</string>
|
<string name="exchanging_contact_details">Exchanging contact details\u2026</string>
|
||||||
<string name="codes_do_not_match">Codes do not match</string>
|
|
||||||
<string name="interfering">This could mean that someone is trying to interfere with your connection</string>
|
|
||||||
<string name="contact_added_toast">Contact added: %s</string>
|
<string name="contact_added_toast">Contact added: %s</string>
|
||||||
<string name="contact_already_exists">Contact %s already exists</string>
|
<string name="contact_already_exists">Contact %s already exists</string>
|
||||||
<string name="contact_exchange_failed">Contact exchange failed</string>
|
<string name="contact_exchange_failed">Contact exchange failed</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user