diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 7b9dcd72e..3d081536f 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -31,15 +31,6 @@
-
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java
index f33b44594..4acbee525 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -43,6 +44,17 @@ public interface AccountManager {
*/
boolean createAccount(String name, String password);
+ /**
+ * Restores a given identity by registering it with the
+ * {@link IdentityManager}. Creates a database key, encrypts it with the
+ * given password and stores it on disk. {@link #accountExists()} will
+ * return true after this method returns true.
+ * @param identity
+ * @param password
+ * @return
+ */
+ boolean restoreAccount(Identity identity, String password);
+
/**
* Deletes all account state from disk. {@link #accountExists()} will
* return false after this method returns.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
index 0330d443c..d1eb4e913 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException;
+import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DbException;
@@ -48,6 +49,9 @@ public interface ContactManager {
SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException;
+ ContactId addContact(Transaction txn, Author remote, AuthorId local,
+ PublicKey handshake, boolean verified) throws DbException;
+
/**
* Stores a contact associated with the given local and remote pseudonyms,
* replacing the given pending contact, derives and stores handshake mode
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java
index 1f586ada5..538d775c9 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java
@@ -24,6 +24,19 @@ public interface HandshakeManager {
HandshakeResult handshake(PendingContactId p, InputStream in,
StreamWriter out) throws DbException, IOException;
+ /**
+ * Handshakes with the given contact. Returns an ephemeral master key
+ * authenticated with both parties' handshake key pairs and a flag
+ * indicating whether the local peer is Alice or Bob.
+ *
+ * @param in An incoming stream for the handshake, which must be secured in
+ * handshake mode
+ * @param out An outgoing stream for the handshake, which must be secured
+ * in handshake mode
+ */
+ HandshakeResult handshake(ContactId c, InputStream in, StreamWriter out)
+ throws DbException, IOException;
+
class HandshakeResult {
private final SecretKey masterKey;
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
index 50f7d8aa0..b23bbb96a 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
@@ -35,6 +35,20 @@ public interface KeyManager {
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
+ /**
+ * Derives and stores a set of rotation mode transport keys for
+ * communicating with the given contact over each transport and returns the
+ * key set IDs.
+ *
+ * {@link StreamContext StreamContexts} for the contact can be created
+ * after this method has returned.
+ *
+ * @param alice True if the local party is Alice
+ * @param active Whether the derived keys can be used for outgoing streams
+ */
+ Map addRotationKeys(ContactId c, SecretKey rootKey,
+ long timestamp, boolean alice, boolean active) throws DbException;
+
/**
* Informs the key manager that a new contact has been added. Derives and
* stores a set of handshake mode transport keys for communicating with the
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java
index 4636dfad3..1e1fa71c7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java
@@ -176,6 +176,18 @@ class AccountManagerImpl implements AccountManager {
}
}
+ public boolean restoreAccount(Identity identity, String password) {
+ synchronized (stateChangeLock) {
+ if (hasDatabaseKey())
+ throw new AssertionError("Already have a database key");
+ identityManager.registerIdentity(identity);
+ SecretKey key = crypto.generateSecretKey();
+ if (!encryptAndStoreDatabaseKey(key, password)) return false;
+ databaseKey = key;
+ return true;
+ }
+ }
+
@GuardedBy("stateChangeLock")
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
byte[] plaintext = key.getBytes();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
index 2a50033b1..63e72cbb0 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
@@ -76,7 +76,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new IncomingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
- t, d));
+ t, d, handshakeManager));
}
@Override
@@ -101,7 +101,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
- secureRandom, c, t, d));
+ secureRandom, handshakeManager, c, t, d));
}
@Override
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/DuplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/DuplexSyncConnection.java
index 33b90c68f..b833505f2 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/DuplexSyncConnection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/DuplexSyncConnection.java
@@ -3,6 +3,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java
index abf34ca0f..1ce4af531 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java
@@ -2,6 +2,8 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.HandshakeManager;
+import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -13,9 +15,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
+import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
+import java.io.InputStream;
import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING;
@@ -24,6 +28,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
+ private final HandshakeManager handshakeManager;
+
+ // FIXME: Exchange timestamp as part of handshake protocol?
+ private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
IncomingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
@@ -32,10 +40,12 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId,
- DuplexTransportConnection connection) {
+ DuplexTransportConnection connection,
+ HandshakeManager handshakeManager) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
+ this.handshakeManager = handshakeManager;
}
@Override
@@ -54,10 +64,22 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
return;
}
if (ctx.isHandshakeMode()) {
- // TODO: Support handshake mode for contacts
- LOG.warning("Received handshake tag, expected rotation mode");
- onReadError(true);
- return;
+ if (!performHandshake(ctx, contactId)) {
+ LOG.warning("Handshake failed");
+ return;
+ }
+ // Allocate a rotation mode stream context
+ ctx = allocateStreamContext(contactId, transportId);
+ if (ctx == null) {
+ LOG.warning("Could not allocate stream context");
+ onWriteError();
+ return;
+ }
+ if (ctx.isHandshakeMode()) {
+ LOG.warning("Got handshake mode context after handshaking");
+ onWriteError();
+ return;
+ }
}
connectionRegistry.registerIncomingConnection(contactId, transportId,
this);
@@ -103,5 +125,33 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
onWriteError();
}
}
+
+ private boolean performHandshake(StreamContext ctxIn, ContactId contactId) {
+ // Allocate the outgoing stream context
+ StreamContext ctxOut =
+ allocateStreamContext(contactId, transportId);
+ if (ctxOut == null) {
+ LOG.warning("Could not allocate stream context");
+ onReadError(true);
+ return false;
+ }
+ try {
+ InputStream in = streamReaderFactory.createStreamReader(
+ reader.getInputStream(), ctxIn);
+ // Flush the output stream to send the outgoing stream header
+ StreamWriter out = streamWriterFactory.createStreamWriter(
+ writer.getOutputStream(), ctxOut);
+ out.getOutputStream().flush();
+ HandshakeManager.HandshakeResult result =
+ handshakeManager.handshake(contactId, in, out);
+ keyManager.addRotationKeys(contactId, result.getMasterKey(),
+ TIMESTAMP, result.isAlice(), true);
+ return true;
+ } catch (IOException | DbException e) {
+ logException(LOG, WARNING, e);
+ onReadError(true);
+ return false;
+ }
+ }
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java
index d7f777b7b..9ffb974c6 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -14,9 +15,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
+import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
+import java.io.InputStream;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
@@ -28,21 +31,31 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class OutgoingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
+ // FIXME: Exchange timestamp as part of handshake protocol?
+ private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
+
private final SecureRandom secureRandom;
+ private final HandshakeManager handshakeManager;
private final ContactId contactId;
- OutgoingDuplexSyncConnection(KeyManager keyManager,
+ OutgoingDuplexSyncConnection(
+ KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
- Executor ioExecutor, SecureRandom secureRandom, ContactId contactId,
- TransportId transportId, DuplexTransportConnection connection) {
+ Executor ioExecutor,
+ SecureRandom secureRandom,
+ HandshakeManager handshakeManager,
+ ContactId contactId,
+ TransportId transportId,
+ DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
this.secureRandom = secureRandom;
+ this.handshakeManager = handshakeManager;
this.contactId = contactId;
}
@@ -56,10 +69,22 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
return;
}
if (ctx.isHandshakeMode()) {
- // TODO: Support handshake mode for contacts
- LOG.warning("Cannot use handshake mode stream context");
- onWriteError();
- return;
+ if (!performHandshake(ctx)) {
+ LOG.warning("Handshake failed");
+ return;
+ }
+ // Allocate a rotation mode stream context
+ ctx = allocateStreamContext(contactId, transportId);
+ if (ctx == null) {
+ LOG.warning("Could not allocate stream context");
+ onWriteError();
+ return;
+ }
+ if (ctx.isHandshakeMode()) {
+ LOG.warning("Got handshake mode context after handshaking");
+ onWriteError();
+ return;
+ }
}
// Start the incoming session on another thread
Priority priority = generatePriority();
@@ -127,6 +152,57 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
}
}
+ private boolean performHandshake(StreamContext ctxOut) {
+ // Flush the output stream to send the outgoing stream header
+ StreamWriter out;
+ try {
+ out = streamWriterFactory.createStreamWriter(
+ writer.getOutputStream(), ctxOut);
+ out.getOutputStream().flush();
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
+ onWriteError();
+ return false;
+ }
+ // Read and recognise the tag
+ StreamContext ctxIn = recogniseTag(reader, transportId);
+ // Unrecognised tags are suspicious in this case
+ if (ctxIn == null) {
+ LOG.warning("Unrecognised tag for returning stream");
+ onReadError();
+ return false;
+ }
+ // Check that the stream comes from the expected contact
+ ContactId inContactId = ctxIn.getContactId();
+ if (contactId == null) {
+ LOG.warning("Expected contact tag, got rendezvous tag");
+ onReadError();
+ return false;
+ }
+ if (!inContactId.equals(contactId)) {
+ LOG.warning("Wrong contact ID for returning stream");
+ onReadError();
+ return false;
+ }
+ // TODO: Register the connection, close it if it's redundant
+ // Handshake and exchange contacts
+ try {
+ InputStream in = streamReaderFactory.createStreamReader(
+ reader.getInputStream(), ctxIn);
+ HandshakeManager.HandshakeResult result =
+ handshakeManager.handshake(contactId, in, out);
+ keyManager.addRotationKeys(contactId, result.getMasterKey(),
+ TIMESTAMP, result.isAlice(), true);
+ return true;
+ } catch (IOException | DbException e) {
+ logException(LOG, WARNING, e);
+ onWriteError();
+ return false;
+ } finally {
+ // TODO: Unregister the connection
+ }
+ }
+
private void onReadError() {
// 'Recognised' is always true for outgoing connections
onReadError(true);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
index 3c11e80f6..f7992aa66 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
@@ -119,6 +119,15 @@ class ContactManagerImpl implements ContactManager, EventListener {
verified, active));
}
+ @Override
+ public ContactId addContact(Transaction txn, Author remote, AuthorId local,
+ PublicKey handshake, boolean verified) throws DbException {
+ ContactId c = db.addContact(txn, remote, local, handshake, verified);
+ Contact contact = db.getContact(txn, c);
+ for (ContactHook hook : hooks) hook.addingContact(txn, contact);
+ return c;
+ }
+
@Override
public String getHandshakeLink() throws DbException {
KeyPair keyPair = db.transactionWithResult(true,
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java
index 7edeaf786..068685ff7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java
@@ -3,6 +3,8 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.Predicate;
+import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact;
@@ -88,6 +90,27 @@ class HandshakeManagerImpl implements HandshakeManager {
});
PublicKey theirStaticPublicKey = keys.getFirst();
KeyPair ourStaticKeyPair = keys.getSecond();
+ return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
+ }
+
+ @Override
+ public HandshakeResult handshake(ContactId c, InputStream in,
+ StreamWriter out) throws DbException, IOException {
+ Pair keys = db.transactionWithResult(true, txn -> {
+ Contact contact = contactManager.getContact(txn, c);
+ PublicKey handshakePublicKey = contact.getHandshakePublicKey();
+ if (handshakePublicKey == null) throw new DbException();
+ KeyPair keyPair = identityManager.getHandshakeKeys(txn);
+ return new Pair<>(handshakePublicKey, keyPair);
+ });
+ PublicKey theirStaticPublicKey = keys.getFirst();
+ KeyPair ourStaticKeyPair = keys.getSecond();
+ return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
+ }
+
+ private HandshakeResult handshake(PublicKey theirStaticPublicKey,
+ KeyPair ourStaticKeyPair, InputStream in, StreamWriter out)
+ throws IOException {
boolean alice = transportCrypto.isAlice(theirStaticPublicKey,
ourStaticKeyPair);
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index 58aeb7cea..d240d644f 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
@@ -101,9 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
}
@Override
- public Map addRotationKeys(
- Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
- boolean alice, boolean active) throws DbException {
+ public Map addRotationKeys(Transaction txn,
+ ContactId c, SecretKey rootKey, long timestamp, boolean alice,
+ boolean active) throws DbException {
Map ids = new HashMap<>();
for (Entry e : managers.entrySet()) {
TransportId t = e.getKey();
@@ -114,6 +114,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return ids;
}
+ @Override
+ public Map addRotationKeys(ContactId c,
+ SecretKey rootKey, long timestamp, boolean alice, boolean active)
+ throws DbException {
+ return db.transactionWithResult(false, txn ->
+ addRotationKeys(txn, c, rootKey, timestamp, alice, active));
+ }
+
@Override
public Map addContact(Transaction txn, ContactId c,
PublicKey theirPublicKey, KeyPair ourKeyPair)
@@ -137,7 +145,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto
- .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
+ .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index 7596c8532..5f0972bdc 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -174,6 +174,14 @@
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
+
+
+ {
+ if (clicked) beginTransfer();
+ });
+
viewModel.getShowCameraFragment().observeEvent(this, show -> {
if (show) showCameraFragment();
});
viewModel.getSuccessDismissed().observeEvent(this, dismissed -> {
if (dismissed) finish();
});
+ viewModel.getErrorTryAgain().observeEvent(this, tryAgain -> {
+ if (tryAgain) beginTransfer();
+ });
viewModel.getState()
.observe(this, this::onReturnShardStateChanged);
}
+ private void beginTransfer() {
+ try {
+ viewModel.beginTransfer();
+ } catch (IOException e) {
+ Toast.makeText(this,
+ "It looks like you are not connected to a Wifi network",
+ Toast.LENGTH_SHORT).show();
+ FragmentManager fm = getSupportFragmentManager();
+ if (fm.findFragmentByTag(CustodianReturnShardFragment.TAG) == null) {
+ BaseFragment f = new CustodianReturnShardErrorFragment();
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, f, f.getUniqueTag())
+ .addToBackStack(f.getUniqueTag())
+ .commit();
+ }
+ }
+ }
+
+
private void onReturnShardStateChanged(CustodianTask.State state) {
if (state instanceof CustodianTask.State.Success) {
CustodianReturnShardSuccessFragment fragment = new CustodianReturnShardSuccessFragment();
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardErrorFragment.java
new file mode 100644
index 000000000..5c2b78472
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardErrorFragment.java
@@ -0,0 +1,57 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class CustodianReturnShardErrorFragment extends BaseFragment {
+ public static final String TAG =
+ CustodianReturnShardErrorFragment.class.getName();
+
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private CustodianReturnShardViewModel viewModel;
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
+ .get(CustodianReturnShardViewModel.class);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_recovery_custodian_error_explainer,
+ container, false);
+ Button button = view.findViewById(R.id.button);
+ button.setOnClickListener(e -> viewModel.onErrorTryAgain());
+ // TODO cancel button
+// button.setOnClickListener(e -> viewModel.onErrorCancelled());
+ return view;
+ }
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java
index d3970c2ed..d68915362 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java
@@ -51,13 +51,14 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
private final SocialBackupManager socialBackupManager;
private final DatabaseComponent db;
final QrCodeDecoder qrCodeDecoder;
- private boolean wasContinueClicked = false;
private boolean qrCodeRead = false;
private WifiManager wifiManager;
+ private final MutableLiveEvent continueClicked = new MutableLiveEvent<>();
private final MutableLiveEvent showCameraFragment =
new MutableLiveEvent<>();
private final MutableLiveEvent successDismissed =
new MutableLiveEvent<>();
+ private final MutableLiveEvent errorTryAgain = new MutableLiveEvent<>();
private final MutableLiveData state =
new MutableLiveData<>();
private final CustodianTask task;
@@ -110,12 +111,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
}
}
- public void start(ContactId contactId) throws DbException, IOException {
- InetAddress inetAddress = getWifiIpv4Address();
- LOG.info("Client InetAddress: " + inetAddress);
- if (inetAddress == null)
- throw new IOException("Cannot get IP on local wifi");
-
+ public void start(ContactId contactId) throws DbException {
+ // TODO this should be transactionWithResult
db.transaction(false, txn -> {
if (!socialBackupManager.amCustodian(txn, contactId)) {
throw new DbException();
@@ -123,8 +120,6 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
returnShardPayloadBytes = socialBackupManager
.getReturnShardPayloadBytes(txn, contactId);
});
- task.cancel();
- task.start(this, returnShardPayloadBytes);
}
@IoExecutor
@@ -149,11 +144,31 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
}
}
+ public void beginTransfer() throws IOException {
+ InetAddress inetAddress = getWifiIpv4Address();
+ LOG.info("Client InetAddress: " + inetAddress);
+ if (inetAddress == null)
+ throw new IOException("Cannot get IP on local wifi");
+
+ task.cancel();
+ task.start(this, returnShardPayloadBytes);
+ //TODO camera permissions
+ showCameraFragment.setEvent(true);
+ }
@UiThread
public void onContinueClicked() {
- wasContinueClicked = true;
+ continueClicked.setEvent(true);
// checkPermissions.setEvent(true);
- showCameraFragment.setEvent(true);
+ }
+
+ @UiThread
+ public void onErrorCancelled() {
+ errorTryAgain.postEvent(false);
+ }
+
+ @UiThread
+ public void onErrorTryAgain() {
+ errorTryAgain.postEvent(true);
}
@UiThread
@@ -186,4 +201,10 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
qrCodeRead = true;
}
}
+
+ public MutableLiveEvent getErrorTryAgain() {
+ return errorTryAgain;
+ }
+
+ public MutableLiveEvent getContinueClicked() { return continueClicked; }
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeErrorFragment.java
new file mode 100644
index 000000000..7c4981266
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeErrorFragment.java
@@ -0,0 +1,57 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class OwnerRecoveryModeErrorFragment extends BaseFragment {
+ public static final String TAG =
+ OwnerRecoveryModeErrorFragment.class.getName();
+
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private OwnerReturnShardViewModel viewModel;
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
+ .get(OwnerReturnShardViewModel.class);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_recovery_custodian_error_explainer,
+ container, false);
+ Button button = view.findViewById(R.id.button);
+ button.setOnClickListener(e -> viewModel.onErrorTryAgain());
+ // TODO cancel button
+// button.setOnClickListener(e -> viewModel.onErrorCancelled());
+ return view;
+ }
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java
index 2c9bcdd53..ac88c52ac 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java
@@ -1,5 +1,6 @@
package org.briarproject.briar.android.socialbackup.recover;
+import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;
@@ -23,6 +24,10 @@ import javax.inject.Inject;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static java.util.logging.Logger.getLogger;
@MethodsNotNullByDefault
@@ -72,6 +77,12 @@ public class OwnerReturnShardActivity extends BaseActivity
showNextFragment(new OwnerRecoveryModeMainFragment());
}
});
+ viewModel.getSuccessDismissed().observeEvent(this, success -> {
+ if (success) onSuccessDismissed();
+ });
+ viewModel.getErrorTryAgain().observeEvent(this, tryAgain -> {
+ if (tryAgain) onBackPressed();
+ });
viewModel.getState()
.observe(this, this::onReturnShardStateChanged);
}
@@ -120,6 +131,13 @@ public class OwnerReturnShardActivity extends BaseActivity
}
}
+ private void onSuccessDismissed() {
+ finish();
+ Intent i = new Intent(this, RestoreAccountActivity.class);
+ i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
+ FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
+ startActivity(i);
+ }
private void onReturnShardStateChanged(SecretOwnerTask.State state) {
if (state instanceof SecretOwnerTask.State.Success) {
@@ -131,9 +149,8 @@ public class OwnerReturnShardActivity extends BaseActivity
Toast.LENGTH_SHORT).show();
if (added && viewModel.canRecover()) {
LOG.info("Secret key recovered");
- int version = 0;
try {
- version = viewModel.recover();
+ viewModel.recover();
} catch (GeneralSecurityException e) {
LOG.warning("Unable to decrypt backup" + e.toString());
Toast.makeText(this,
@@ -148,20 +165,16 @@ public class OwnerReturnShardActivity extends BaseActivity
Toast.LENGTH_LONG).show();
return;
}
- Toast.makeText(this,
- "Account recovered! " + version,
- Toast.LENGTH_LONG).show();
- finish();
+ showNextFragment(new OwnerReturnShardSuccessFragment());
return;
}
onBackPressed();
} else if (state instanceof SecretOwnerTask.State.Failure) {
- // TODO error screen, handle reason
- Toast.makeText(this,
- "Shard return failed!",
- Toast.LENGTH_SHORT).show();
- onBackPressed();
-// showNextFragment(new OwnerRecoveryModeExplainerFragment());
+// Toast.makeText(this,
+// "Shard return failed!",
+// Toast.LENGTH_SHORT).show();
+// onBackPressed();
+ showNextFragment(new OwnerRecoveryModeErrorFragment());
}
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardFragment.java
index 686f14cf7..d66043907 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardFragment.java
@@ -68,7 +68,7 @@ public class OwnerReturnShardFragment extends BaseFragment
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_keyagreement_qr, container,
+ return inflater.inflate(R.layout.fragment_recovery_owner_qr, container,
false);
}
@@ -134,13 +134,13 @@ public class OwnerReturnShardFragment extends BaseFragment
private void onReturnShardStateChanged(
@Nullable SecretOwnerTask.State state) {
if (state instanceof SecretOwnerTask.State.Listening) {
+ status.setText(R.string.waiting_for_contact_to_scan);
Bitmap qrCode = viewModel.getQrCodeBitmap();
qrCodeView.setQrCode(qrCode);
} else if (state instanceof SecretOwnerTask.State.ReceivingShard) {
- statusView.setVisibility(VISIBLE);
status.setText(R.string.connecting_to_device);
} else if (state instanceof SecretOwnerTask.State.SendingAck) {
- status.setText(R.string.waiting_for_contact_to_scan);
+ status.setText(R.string.recovery_sending_ack);
} else if (state instanceof SecretOwnerTask.State.Success) {
status.setText("Success");
} else if (state instanceof SecretOwnerTask.State.Failure) {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardModule.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardModule.java
index b2f2da6e4..a68e587a6 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardModule.java
@@ -1,6 +1,7 @@
package org.briarproject.briar.android.socialbackup.recover;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
+import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
@@ -17,4 +18,9 @@ public abstract class OwnerReturnShardModule {
abstract ViewModel bindOwnerReturnShardViewModel(
OwnerReturnShardViewModel ownerReturnShardViewModel);
+ @Binds
+ @IntoMap
+ @ViewModelKey(RestoreAccountViewModel.class)
+ abstract ViewModel bindRestoreAccountViewModel(
+ RestoreAccountViewModel restoreAccountViewModel);
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardSuccessFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardSuccessFragment.java
new file mode 100644
index 000000000..d7707ec94
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardSuccessFragment.java
@@ -0,0 +1,54 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+public class OwnerReturnShardSuccessFragment extends BaseFragment {
+
+ public static final String TAG =
+ OwnerReturnShardSuccessFragment.class.getName();
+
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private OwnerReturnShardViewModel viewModel;
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
+ .get(OwnerReturnShardViewModel.class);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_recovery_owner_success,
+ container, false);
+
+ Button button = view.findViewById(R.id.button);
+ button.setOnClickListener(e -> viewModel.onSuccessDismissed());
+
+ return view;
+ }
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java
index 91a4cc7af..b476cc488 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java
@@ -14,19 +14,14 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.contact.add.nearby.QrCodeUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
-import org.briarproject.briar.api.socialbackup.BackupPayload;
-import org.briarproject.briar.api.socialbackup.DarkCrystal;
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
-import org.briarproject.briar.api.socialbackup.Shard;
+import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
-import org.briarproject.briar.socialbackup.BackupPayloadDecoder;
-import org.briarproject.briar.socialbackup.SocialBackup;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
-import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -51,23 +46,22 @@ class OwnerReturnShardViewModel extends AndroidViewModel
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
-// private ReturnShardPayload returnShardPayload;
-
private final AndroidExecutor androidExecutor;
private final Executor ioExecutor;
private final SecretOwnerTask task;
- private final DarkCrystal darkCrystal;
- private final BackupPayloadDecoder backupPayloadDecoder;
+ private final RestoreAccount restoreAccount;
+ private final MutableLiveEvent errorTryAgain =
+ new MutableLiveEvent<>();
private final MutableLiveEvent showQrCodeFragment =
new MutableLiveEvent<>();
+ private final MutableLiveEvent successDismissed = new MutableLiveEvent<>();
private final MutableLiveData state =
new MutableLiveData<>();
private final MutableLiveEvent startClicked =
new MutableLiveEvent<>();
private boolean wasContinueClicked = false;
private boolean isActivityResumed = false;
- private ArrayList recoveredShards = new ArrayList<>();
private Bitmap qrCodeBitmap;
private WifiManager wifiManager;
private SecretKey secretKey;
@@ -76,14 +70,12 @@ class OwnerReturnShardViewModel extends AndroidViewModel
OwnerReturnShardViewModel(Application app,
AndroidExecutor androidExecutor,
SecretOwnerTask task,
- DarkCrystal darkCrystal,
- BackupPayloadDecoder backupPayloadDecoder,
+ RestoreAccount restoreAccount,
@IoExecutor Executor ioExecutor) {
super(app);
this.androidExecutor = androidExecutor;
this.ioExecutor = ioExecutor;
- this.backupPayloadDecoder = backupPayloadDecoder;
- this.darkCrystal = darkCrystal;
+ this.restoreAccount = restoreAccount;
this.task = task;
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
@@ -132,6 +124,11 @@ class OwnerReturnShardViewModel extends AndroidViewModel
startShardReturn();
}
+ @UiThread
+ void onSuccessDismissed() {
+ successDismissed.setEvent(true);
+ }
+
@UiThread
void startShardReturn() {
// If we return to the intro fragment, the continue button needs to be
@@ -163,6 +160,10 @@ class OwnerReturnShardViewModel extends AndroidViewModel
});
}
+ @UiThread
+ public void onErrorTryAgain() {
+ errorTryAgain.setEvent(true);
+ }
/**
* Set to true in onPostResume() and false in onPause(). This prevents the
@@ -196,7 +197,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel
}
public int getNumberOfShards() {
- return recoveredShards.size();
+ return restoreAccount.getNumberOfShards();
}
@Override
@@ -227,38 +228,22 @@ class OwnerReturnShardViewModel extends AndroidViewModel
// TODO figure out how to actually use a hash set for these objects
public boolean addToShardSet(ReturnShardPayload toAdd) {
- boolean found = false;
- for (ReturnShardPayload returnShardPayload : recoveredShards) {
- if (toAdd.equals(returnShardPayload)) {
- found = true;
- break;
- }
- }
- if (!found) recoveredShards.add(toAdd);
- return !found;
+ return restoreAccount.addReturnShardPayload(toAdd);
}
public boolean canRecover() {
- ArrayList shards = new ArrayList();
- for (ReturnShardPayload returnShardPayload : recoveredShards) {
- // TODO check shards all have same secret id
- shards.add(returnShardPayload.getShard());
- }
- try {
- secretKey = darkCrystal.combineShards(shards);
- } catch (GeneralSecurityException e) {
- // TODO handle error message
- return false;
- }
- return true;
+ return restoreAccount.canRecover();
}
public int recover() throws FormatException, GeneralSecurityException {
- if (secretKey == null) throw new GeneralSecurityException();
- // TODO find backup with highest version number
- BackupPayload backupPayload = recoveredShards.get(0).getBackupPayload();
- SocialBackup decodedBackup = backupPayloadDecoder.decodeBackupPayload(secretKey, backupPayload);
- int version = decodedBackup.getVersion();
- return version;
+ return restoreAccount.recover();
+ }
+
+ public MutableLiveEvent getSuccessDismissed() {
+ return successDismissed;
+ }
+
+ public MutableLiveEvent getErrorTryAgain() {
+ return errorTryAgain;
}
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java
new file mode 100644
index 000000000..2c9a3fefe
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java
@@ -0,0 +1,81 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.activity.BaseActivity;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
+import static org.briarproject.briar.android.socialbackup.recover.RestoreAccountViewModel.State;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class RestoreAccountActivity extends BaseActivity
+ implements BaseFragment.BaseFragmentListener {
+
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+ RestoreAccountViewModel viewModel;
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+
+ viewModel = new ViewModelProvider(this, viewModelFactory)
+ .get(RestoreAccountViewModel.class);
+ viewModel.getState().observeEvent(this, this::onStateChanged);
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle state) {
+ super.onCreate(state);
+ setContentView(R.layout.activity_fragment_container);
+ }
+
+ private void onStateChanged(RestoreAccountViewModel.State state) {
+ if (state == State.SET_PASSWORD) {
+ showInitialFragment(RestoreAccountSetPasswordFragment.newInstance());
+ } else if (state == State.DOZE) {
+ showDozeFragment();
+ } else if (state == State.CREATED || state == State.FAILED) {
+ // TODO: Show an error if failed
+ showApp();
+ }
+ }
+
+ @TargetApi(23)
+ void showDozeFragment() {
+ showNextFragment(RestoreAccountDozeFragment.newInstance());
+ }
+
+ void showApp() {
+ Intent i = new Intent(this, ENTRY_ACTIVITY);
+ i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME |
+ FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(i);
+ supportFinishAfterTransition();
+ overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out);
+ }
+
+ @Override
+ @Deprecated
+ public void runOnDbThread(Runnable runnable) {
+ throw new RuntimeException("Don't use this deprecated method here.");
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountDozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountDozeFragment.java
new file mode 100644
index 000000000..6c079646d
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountDozeFragment.java
@@ -0,0 +1,118 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ProgressBar;
+
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.account.DozeView;
+import org.briarproject.briar.android.account.HuaweiView;
+import org.briarproject.briar.android.account.PowerView;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.util.UiUtils;
+
+import androidx.annotation.Nullable;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
+import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class RestoreAccountDozeFragment extends RestoreAccountFragment
+ implements PowerView.OnCheckedChangedListener {
+
+ private final static String TAG = RestoreAccountDozeFragment.class.getName();
+
+ private DozeView dozeView;
+ private HuaweiView huaweiView;
+ private Button next;
+ private boolean secondAttempt = false;
+
+ public static RestoreAccountDozeFragment newInstance() {
+ return new RestoreAccountDozeFragment();
+ }
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ requireActivity().setTitle(getString(R.string.setup_doze_title));
+ setHasOptionsMenu(false);
+ View v = inflater.inflate(R.layout.fragment_setup_doze, container,
+ false);
+ dozeView = v.findViewById(R.id.dozeView);
+ dozeView.setOnCheckedChangedListener(this);
+ huaweiView = v.findViewById(R.id.huaweiView);
+ huaweiView.setOnCheckedChangedListener(this);
+ next = v.findViewById(R.id.next);
+ ProgressBar progressBar = v.findViewById(R.id.progress);
+
+ dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
+ next.setOnClickListener(this);
+
+ viewModel.getIsCreatingAccount()
+ .observe(getViewLifecycleOwner(), isCreatingAccount -> {
+ if (isCreatingAccount) {
+ next.setVisibility(INVISIBLE);
+ progressBar.setVisibility(VISIBLE);
+ }
+ });
+
+ return v;
+ }
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+
+ @Override
+ protected String getHelpText() {
+ return getString(R.string.setup_doze_explanation);
+ }
+
+ @Override
+ public void onActivityResult(int request, int result,
+ @Nullable Intent data) {
+ super.onActivityResult(request, result, data);
+ if (request == REQUEST_DOZE_WHITELISTING) {
+ if (!dozeView.needsToBeShown() || secondAttempt) {
+ dozeView.setChecked(true);
+ } else if (getContext() != null) {
+ secondAttempt = true;
+ showOnboardingDialog(getContext(), getHelpText());
+ }
+ }
+ }
+
+ @Override
+ public void onCheckedChanged() {
+ next.setEnabled(dozeView.isChecked() && huaweiView.isChecked());
+ }
+
+ @SuppressLint("BatteryLife")
+ private void askForDozeWhitelisting() {
+ if (getContext() == null) return;
+ Intent i = UiUtils.getDozeWhitelistingIntent(getContext());
+ startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
+ }
+
+ @Override
+ public void onClick(View view) {
+ viewModel.dozeExceptionConfirmed();
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountFragment.java
new file mode 100644
index 000000000..329df7ee2
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountFragment.java
@@ -0,0 +1,91 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import javax.inject.Inject;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
+import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
+import static org.briarproject.briar.android.util.UiUtils.enterPressed;
+import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+abstract class RestoreAccountFragment extends BaseFragment implements TextWatcher,
+ TextView.OnEditorActionListener, View.OnClickListener {
+
+ private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
+
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+ RestoreAccountViewModel viewModel;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ viewModel = new ViewModelProvider(requireActivity())
+ .get(RestoreAccountViewModel.class);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.help_action, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.action_help) {
+ showOnboardingDialog(getContext(), getHelpText());
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ protected abstract String getHelpText();
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+ // noop
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ // noop
+ }
+
+ @Override
+ public boolean onEditorAction(TextView textView, int actionId,
+ @Nullable KeyEvent keyEvent) {
+ if (actionId == IME_ACTION_NEXT || actionId == IME_ACTION_DONE ||
+ enterPressed(actionId, keyEvent)) {
+ onClick(textView);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ // noop
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountSetPasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountSetPasswordFragment.java
new file mode 100644
index 000000000..c75b3bbed
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountSetPasswordFragment.java
@@ -0,0 +1,131 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.ProgressBar;
+
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.android.material.textfield.TextInputLayout;
+
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.login.StrengthMeter;
+
+import javax.annotation.Nullable;
+
+import static android.content.Context.INPUT_METHOD_SERVICE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
+import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
+import static org.briarproject.briar.android.util.UiUtils.setError;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class RestoreAccountSetPasswordFragment extends RestoreAccountFragment {
+ private final static String TAG = RestoreAccountSetPasswordFragment.class.getName();
+
+ private TextInputLayout passwordEntryWrapper;
+ private TextInputLayout passwordConfirmationWrapper;
+ private TextInputEditText passwordEntry;
+ private TextInputEditText passwordConfirmation;
+ private StrengthMeter strengthMeter;
+ private Button nextButton;
+
+ public static RestoreAccountSetPasswordFragment newInstance() {
+ return new RestoreAccountSetPasswordFragment();
+ }
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ requireActivity().setTitle(getString(R.string.setup_password_intro));
+ View v = inflater.inflate(R.layout.fragment_setup_password, container,
+ false);
+
+ strengthMeter = v.findViewById(R.id.strength_meter);
+ passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
+ passwordEntry = v.findViewById(R.id.password_entry);
+ passwordConfirmationWrapper =
+ v.findViewById(R.id.password_confirm_wrapper);
+ passwordConfirmation = v.findViewById(R.id.password_confirm);
+ nextButton = v.findViewById(R.id.next);
+ ProgressBar progressBar = v.findViewById(R.id.progress);
+
+ passwordEntry.addTextChangedListener(this);
+ passwordConfirmation.addTextChangedListener(this);
+ nextButton.setOnClickListener(this);
+
+ if (!viewModel.needToShowDozeFragment()) {
+ nextButton.setText(R.string.create_account_button);
+ passwordConfirmation.setImeOptions(IME_ACTION_DONE);
+ }
+
+ viewModel.getIsCreatingAccount()
+ .observe(getViewLifecycleOwner(), isCreatingAccount -> {
+ if (isCreatingAccount) {
+ nextButton.setVisibility(INVISIBLE);
+ progressBar.setVisibility(VISIBLE);
+ // this also avoids the keyboard popping up
+ passwordEntry.setFocusable(false);
+ passwordConfirmation.setFocusable(false);
+ }
+ });
+
+ return v;
+ }
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+
+ @Override
+ protected String getHelpText() {
+ return getString(R.string.setup_password_explanation);
+ }
+
+ @Override
+ public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
+ String password1 = passwordEntry.getText().toString();
+ String password2 = passwordConfirmation.getText().toString();
+ boolean passwordsMatch = password1.equals(password2);
+
+ strengthMeter
+ .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
+ float strength = viewModel.estimatePasswordStrength(password1);
+ strengthMeter.setStrength(strength);
+ boolean strongEnough = strength >= QUITE_WEAK;
+
+ setError(passwordEntryWrapper, getString(R.string.password_too_weak),
+ password1.length() > 0 && !strongEnough);
+ setError(passwordConfirmationWrapper,
+ getString(R.string.passwords_do_not_match),
+ password2.length() > 0 && !passwordsMatch);
+
+ boolean enabled = passwordsMatch && strongEnough;
+ nextButton.setEnabled(enabled);
+ passwordConfirmation.setOnEditorActionListener(enabled ? this : null);
+ }
+
+ @Override
+ public void onClick(View view) {
+ IBinder token = passwordEntry.getWindowToken();
+ Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
+ ((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
+ viewModel.setPassword(passwordEntry.getText().toString());
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java
new file mode 100644
index 000000000..beeaf22f4
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java
@@ -0,0 +1,133 @@
+package org.briarproject.briar.android.socialbackup.recover;
+
+import android.app.Application;
+
+import org.briarproject.bramble.api.account.AccountManager;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.identity.Identity;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.android.account.DozeHelper;
+import org.briarproject.briar.android.viewmodel.LiveEvent;
+import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
+import org.briarproject.briar.api.socialbackup.SocialBackup;
+import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
+
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import static java.util.logging.Logger.getLogger;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+class RestoreAccountViewModel extends AndroidViewModel {
+
+ enum State {SET_PASSWORD, DOZE, CREATED, FAILED}
+
+ private static final Logger LOG =
+ getLogger(RestoreAccountViewModel.class.getName());
+
+ @Nullable
+ private String password;
+ private final MutableLiveEvent
+ state = new MutableLiveEvent<>();
+ private final MutableLiveData isCreatingAccount =
+ new MutableLiveData<>(false);
+
+ private final AccountManager accountManager;
+ private final ContactManager contactManager;
+ private final Executor ioExecutor;
+ private final PasswordStrengthEstimator strengthEstimator;
+ private final DozeHelper dozeHelper;
+ private final RestoreAccount restoreAccount;
+
+ @Inject
+ RestoreAccountViewModel(Application app,
+ AccountManager accountManager,
+ ContactManager contactManager,
+ RestoreAccount restoreAccount,
+ @IoExecutor Executor ioExecutor,
+ PasswordStrengthEstimator strengthEstimator,
+ DozeHelper dozeHelper) {
+ super(app);
+ this.accountManager = accountManager;
+ this.contactManager = contactManager;
+ this.ioExecutor = ioExecutor;
+ this.strengthEstimator = strengthEstimator;
+ this.dozeHelper = dozeHelper;
+ this.restoreAccount = restoreAccount;
+
+ ioExecutor.execute(() -> {
+ if (accountManager.accountExists()) {
+ throw new AssertionError();
+ } else {
+ state.postEvent(State.SET_PASSWORD);
+ }
+ });
+ }
+
+ LiveEvent getState() {
+ return state;
+ }
+
+ LiveData getIsCreatingAccount() {
+ return isCreatingAccount;
+ }
+
+ void setPassword(String password) {
+ this.password = password;
+ if (needToShowDozeFragment()) {
+ state.setEvent(State.DOZE);
+ } else {
+ createAccount();
+ }
+ }
+
+ float estimatePasswordStrength(String password) {
+ return strengthEstimator.estimateStrength(password);
+ }
+
+ boolean needToShowDozeFragment() {
+ return dozeHelper.needToShowDozeFragment(getApplication());
+ }
+
+ void dozeExceptionConfirmed() {
+ createAccount();
+ }
+
+ private void createAccount() {
+ if (password == null) throw new IllegalStateException();
+ isCreatingAccount.setValue(true);
+ SocialBackup socialBackup = restoreAccount.getSocialBackup();
+ if (socialBackup == null) {
+ LOG.warning("Cannot retrieve social backup");
+ state.postEvent(State.FAILED);
+ }
+ Identity identity = socialBackup.getIdentity();
+ ioExecutor.execute(() -> {
+ if (accountManager.restoreAccount(identity, password)) {
+ LOG.info("Restored account");
+ try {
+ restoreAccount.addContactsToDb();
+ } catch (DbException e) {
+ LOG.warning("Cannot retrieve social backup");
+ state.postEvent(State.FAILED);
+ }
+ state.postEvent(State.CREATED);
+ } else {
+ LOG.warning("Failed to create account");
+ state.postEvent(State.FAILED);
+ }
+ });
+ }
+}
diff --git a/briar-android/src/main/res/drawable/qr_code_social_backup.xml b/briar-android/src/main/res/drawable/qr_code_social_backup.xml
new file mode 100644
index 000000000..549060c01
--- /dev/null
+++ b/briar-android/src/main/res/drawable/qr_code_social_backup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/drawable/qr_code_social_backup_error.xml b/briar-android/src/main/res/drawable/qr_code_social_backup_error.xml
new file mode 100644
index 000000000..59ca78963
--- /dev/null
+++ b/briar-android/src/main/res/drawable/qr_code_social_backup_error.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml b/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml
index 4f43bd7a3..28a4fe417 100644
--- a/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml
+++ b/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml
@@ -31,7 +31,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1"
- app:srcCompat="@drawable/qr_code_error" />
+ app:srcCompat="@drawable/qr_code_social_backup_error" />
+ app:srcCompat="@drawable/qr_code_social_backup" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/textViewExplain" />
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml
index 3409ec942..a56a726e0 100644
--- a/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml
@@ -31,7 +31,21 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
- app:srcCompat="@drawable/qr_code_intro" />
+ app:srcCompat="@drawable/qr_code_social_backup" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/textViewExplain" />
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_qr.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_qr.xml
new file mode 100644
index 000000000..486d899f2
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_qr.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_success.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_success.xml
new file mode 100644
index 000000000..6aa088376
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_success.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index b13b2de09..169d513f3 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -676,19 +676,23 @@
You need to meet your trusted contacts in-person to receive pieces
+ Your trusted contact must scan a QR code to initiate the transfer.\n\nYou must both be connected to the same wifi networkBeginFailed to receive backup piece
- Please check that bluetooth is swtiched on and that no-one but your trusted contact is able to scan the QR code
+ Please check that you are both connected to the same wifi network and try againRetryRecovered backup pieces:Show QR codeRecovering account…Account backup piece receivedAccount recovered
+ You must now set a new password for your account
+ Sending acknowledgementYou need to meet in-person to transfer backup piece
+ Make sure you are both connected to the same wifi network, and scan the QR code on your contact\'s device to begin transferring the backup pieceFailed to send backup pieceScan codeAccount backup piece transmitted
@@ -716,7 +720,7 @@
Social Backup
- Old Social Backup
+ Restore AccountYou have received a social backup shard.
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/ContactData.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/ContactData.java
similarity index 85%
rename from briar-core/src/main/java/org/briarproject/briar/socialbackup/ContactData.java
rename to briar-api/src/main/java/org/briarproject/briar/api/socialbackup/ContactData.java
index 10778858a..d1c846390 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/ContactData.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/ContactData.java
@@ -1,10 +1,9 @@
-package org.briarproject.briar.socialbackup;
+package org.briarproject.briar.api.socialbackup;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
-import org.briarproject.briar.api.socialbackup.Shard;
import java.util.Map;
@@ -13,14 +12,14 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
-class ContactData {
+public class ContactData {
private final Contact contact;
private final Map properties;
@Nullable
private final Shard shard;
- ContactData(Contact contact,
+ public ContactData(Contact contact,
Map properties,
@Nullable Shard shard) {
this.contact = contact;
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackup.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java
similarity index 77%
rename from briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackup.java
rename to briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java
index 341d04816..1be5a0822 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackup.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java
@@ -1,4 +1,4 @@
-package org.briarproject.briar.socialbackup;
+package org.briarproject.briar.api.socialbackup;
import org.briarproject.bramble.api.identity.Identity;
@@ -9,7 +9,7 @@ public class SocialBackup {
private List contacts;
private int version;
- SocialBackup (Identity identity, List contacts, int version) {
+ public SocialBackup (Identity identity, List contacts, int version) {
this.identity = identity;
this.contacts = contacts;
this.version = version;
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java
new file mode 100644
index 000000000..db56ad81d
--- /dev/null
+++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java
@@ -0,0 +1,24 @@
+package org.briarproject.briar.api.socialbackup.recovery;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
+import org.briarproject.briar.api.socialbackup.SocialBackup;
+
+import java.security.GeneralSecurityException;
+
+public interface RestoreAccount {
+
+
+ int getNumberOfShards();
+
+ boolean addReturnShardPayload(ReturnShardPayload toAdd);
+
+ boolean canRecover();
+
+ int recover() throws FormatException, GeneralSecurityException;
+
+ SocialBackup getSocialBackup();
+
+ void addContactsToDb() throws DbException;
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoder.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoder.java
index 835869dac..640aac4a9 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoder.java
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.socialbackup.BackupPayload;
+import org.briarproject.briar.api.socialbackup.SocialBackup;
import java.security.GeneralSecurityException;
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java
index a2672016b..4171d8154 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.MessageParser;
import org.briarproject.briar.api.socialbackup.Shard;
+import org.briarproject.briar.api.socialbackup.SocialBackup;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
@@ -54,7 +55,7 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
this.messageParser = messageParser;
}
- public SocialBackup decodeBackupPayload(
+ public org.briarproject.briar.api.socialbackup.SocialBackup decodeBackupPayload(
SecretKey secret,
BackupPayload backupPayload)
throws FormatException, GeneralSecurityException {
@@ -103,7 +104,7 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
handShakePrivateKey, created);
LOG.info("New identity created");
- List contactDataList = new ArrayList();
+ List contactDataList = new ArrayList();
for (int i = 0; i < bdfContactData.size(); i++) {
BdfList bdfData = bdfContactData.getList(i);
@@ -139,8 +140,8 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
Contact contact =
new Contact(contactId, author, author.getId(), alias,
contactHandshakePublicKey, false);
- ContactData contactData =
- new ContactData(contact, properties, shard);
+ org.briarproject.briar.api.socialbackup.ContactData contactData =
+ new org.briarproject.briar.api.socialbackup.ContactData(contact, properties, shard);
contactDataList.add(contactData);
LOG.info("Contact added");
}
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java
index b65f027da..ed8e852b2 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java
@@ -3,12 +3,14 @@ package org.briarproject.briar.socialbackup;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.briar.api.socialbackup.BackupPayload;
+import org.briarproject.briar.api.socialbackup.ContactData;
import java.util.List;
@NotNullByDefault
interface BackupPayloadEncoder {
- org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
+ BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
List contactData, int version);
}
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java
index f813c4efd..7e867f697 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java
@@ -46,7 +46,7 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
@Override
public org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret,
- Identity identity, List contactData, int version) {
+ Identity identity, List contactData, int version) {
// Encode the local identity
BdfList bdfIdentity = new BdfList();
LocalAuthor localAuthor = identity.getLocalAuthor();
@@ -56,7 +56,7 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
bdfIdentity.add(identity.getHandshakePrivateKey().getEncoded());
// Encode the contact data
BdfList bdfContactData = new BdfList();
- for (ContactData cd : contactData) {
+ for (org.briarproject.briar.api.socialbackup.ContactData cd : contactData) {
BdfList bdfData = new BdfList();
Contact contact = cd.getContact();
bdfData.add(clientHelper.toList(contact.getAuthor()));
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java
index b7a58e7d5..cadf7b29e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java
@@ -52,6 +52,7 @@ import org.briarproject.briar.api.socialbackup.ShardMessageHeader;
import org.briarproject.briar.api.socialbackup.ShardReceivedEvent;
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
import org.briarproject.briar.client.ConversationClientImpl;
+import org.briarproject.briar.api.socialbackup.ContactData;
import java.util.ArrayList;
import java.util.Collection;
@@ -278,7 +279,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl
}
// Create the encrypted backup payload
SecretKey secret = crypto.generateSecretKey();
- List contactData = loadContactData(txn);
+ List contactData = loadContactData(txn);
BackupPayload payload =
createBackupPayload(txn, secret, contactData, 0);
// Create the shards
@@ -415,17 +416,17 @@ class SocialBackupManagerImpl extends ConversationClientImpl
}
private BackupPayload createBackupPayload(Transaction txn,
- SecretKey secret, List contactData, int version)
+ SecretKey secret, List contactData, int version)
throws DbException {
Identity identity = identityManager.getIdentity(txn);
return backupPayloadEncoder.encodeBackupPayload(secret, identity,
contactData, version);
}
- private List loadContactData(Transaction txn)
+ private List loadContactData(Transaction txn)
throws DbException {
Collection contacts = contactManager.getContacts(txn);
- List contactData = new ArrayList<>();
+ List contactData = new ArrayList<>();
for (Contact c : contacts) {
// Skip contacts that are in the process of being removed
Group contactGroup = getContactGroup(c);
@@ -433,7 +434,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl
Map props =
getTransportProperties(txn, c.getId());
Shard shard = getRemoteShard(txn, contactGroup.getId());
- contactData.add(new ContactData(c, props, shard));
+ contactData.add(new org.briarproject.briar.api.socialbackup.ContactData(c, props, shard));
}
return contactData;
}
@@ -513,7 +514,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl
throw new DbException(e);
}
}
- private void updateBackup(Transaction txn, List contactData)
+ private void updateBackup(Transaction txn, List contactData)
throws DbException {
BackupMetadata backupMetadata = requireNonNull(getBackupMetadata(txn));
int newVersion = backupMetadata.getVersion() + 1;
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java
index 0a5530e8f..086f9322e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java
@@ -11,8 +11,10 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
+import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
import org.briarproject.briar.socialbackup.recovery.CustodianTaskImpl;
+import org.briarproject.briar.socialbackup.recovery.RestoreAccountImpl;
import org.briarproject.briar.socialbackup.recovery.SecretOwnerTaskImpl;
import javax.inject.Inject;
@@ -119,4 +121,10 @@ public class SocialBackupModule {
CustodianTask custodianTask(CustodianTaskImpl custodianTask) {
return custodianTask;
}
+
+ @Provides
+ @Singleton
+ RestoreAccount restoreAccount(RestoreAccountImpl restoreAccount) {
+ return restoreAccount;
+ }
}
diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java
new file mode 100644
index 000000000..5b05e5d1e
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java
@@ -0,0 +1,136 @@
+package org.briarproject.briar.socialbackup.recovery;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.db.DatabaseComponent;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.identity.AuthorId;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager;
+import org.briarproject.briar.api.socialbackup.BackupPayload;
+import org.briarproject.briar.api.socialbackup.ContactData;
+import org.briarproject.briar.api.socialbackup.DarkCrystal;
+import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
+import org.briarproject.briar.api.socialbackup.Shard;
+import org.briarproject.briar.api.socialbackup.SocialBackup;
+import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
+import org.briarproject.briar.socialbackup.BackupPayloadDecoder;
+
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static java.util.logging.Logger.getLogger;
+
+public class RestoreAccountImpl implements RestoreAccount {
+ private final ArrayList recoveredShards = new ArrayList<>();
+ private final DarkCrystal darkCrystal;
+ private final Executor ioExecutor;
+ private final DatabaseComponent db;
+ private final ContactManager contactManager;
+ private final LifecycleManager lifecycleManager;
+ private SecretKey secretKey;
+ private final BackupPayloadDecoder backupPayloadDecoder;
+ private SocialBackup socialBackup;
+
+ private static final Logger LOG =
+ getLogger(RestoreAccountImpl.class.getName());
+
+ @Inject
+ RestoreAccountImpl(DarkCrystal darkCrystal,
+ BackupPayloadDecoder backupPayloadDecoder, DatabaseComponent db,
+ @IoExecutor Executor ioExecutor,
+ ContactManager contactManager,
+ LifecycleManager lifecycleManager) {
+ this.darkCrystal = darkCrystal;
+ this.backupPayloadDecoder = backupPayloadDecoder;
+ this.db = db;
+ this.ioExecutor = ioExecutor;
+ this.lifecycleManager = lifecycleManager;
+ this.contactManager = contactManager;
+ }
+
+ public int getNumberOfShards() {
+ return recoveredShards.size();
+ }
+
+ // TODO figure out how to actually use a hash set for these objects
+ public boolean addReturnShardPayload(ReturnShardPayload toAdd) {
+ boolean found = false;
+ for (ReturnShardPayload returnShardPayload : recoveredShards) {
+ if (toAdd.equals(returnShardPayload)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) recoveredShards.add(toAdd);
+ return !found;
+ }
+
+ public boolean canRecover() {
+ ArrayList shards = new ArrayList<>();
+ for (ReturnShardPayload returnShardPayload : recoveredShards) {
+ // TODO check shards all have same secret id
+ shards.add(returnShardPayload.getShard());
+ }
+ try {
+ secretKey = darkCrystal.combineShards(shards);
+ } catch (GeneralSecurityException e) {
+ // TODO handle error message
+ return false;
+ }
+ return true;
+ }
+
+ public int recover() throws FormatException, GeneralSecurityException {
+ if (secretKey == null) throw new GeneralSecurityException();
+ // Find backup with highest version number
+ int highestVersion = -1;
+ for (ReturnShardPayload returnShardPayload : recoveredShards) {
+ BackupPayload backupPayload = returnShardPayload.getBackupPayload();
+ SocialBackup s = backupPayloadDecoder
+ .decodeBackupPayload(secretKey, backupPayload);
+ if (s.getVersion() > highestVersion) {
+ socialBackup = s;
+ highestVersion = s.getVersion();
+ }
+ }
+ return highestVersion;
+ }
+
+ public SocialBackup getSocialBackup() {
+ return socialBackup;
+ }
+
+ public void addContactsToDb() throws DbException {
+ if (socialBackup == null) throw new DbException();
+ AuthorId localAuthorId = socialBackup.getIdentity().getId();
+
+ ioExecutor.execute(() -> {
+ try {
+ lifecycleManager.waitForDatabase();
+ } catch (InterruptedException e) {
+ LOG.warning("Interrupted when waiting for database");
+ }
+ try {
+ db.transaction(false, txn -> {
+ for (ContactData contactData : socialBackup.getContacts()) {
+ Contact c = contactData.getContact();
+ LOG.info("Adding contact " + c.getAuthor().getName() + " " + c.getAlias());
+ contactManager.addContact(txn, c.getAuthor(), localAuthorId,
+ c.getHandshakePublicKey(), c.isVerified());
+ }
+ });
+ } catch (DbException e) {
+ LOG.warning("Error adding contacts to database");
+ LOG.warning(e.getMessage());
+ }
+ LOG.info("Added all contacts");
+ });
+ }
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTest.java
index 6ac49754a..0fc17af5a 100644
--- a/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTest.java
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.socialbackup.BackupPayload;
+import org.briarproject.briar.api.socialbackup.MessageEncoder;
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
import org.briarproject.briar.api.socialbackup.Shard;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
@@ -15,9 +16,9 @@ import org.junit.Test;
import java.io.File;
import java.net.InetAddress;
-import java.util.Arrays;
-import java.util.concurrent.Executor;
+import java.util.concurrent.CountDownLatch;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -32,6 +33,8 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
private ReturnShardIntegrationTestComponent owner, custodian;
+ private ReturnShardPayload remotePayload;
+
@Before
public void setUp() throws Exception {
assertTrue(testDir.mkdirs());
@@ -50,16 +53,20 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
}
@Test
- public void testReturnShard() {
+ public void testReturnShard() throws Exception {
SecretOwnerTask secretOwnerTask = owner.getSecretOwnerTask();
CustodianTask custodianTask = custodian.getCustodianTask();
- byte[] payload = "its nice to be important but its more important to be nice".getBytes();
+ MessageEncoder messageEncoder = owner.getMessageEncoder();
- Shard shard = new Shard("secretid".getBytes(), "shard".getBytes());
+ CountDownLatch secretOwnerFinished = new CountDownLatch(1);
+ CountDownLatch custodianFinished = new CountDownLatch(1);
+
+ Shard shard = new Shard("secret id".getBytes(), "shard".getBytes());
BackupPayload backupPayload = new BackupPayload("backup payload".getBytes());
ReturnShardPayload returnShardPayload = new ReturnShardPayload(shard, backupPayload);
-// payloadBytes = clientHelper
+ byte[] payloadBytes = messageEncoder.encodeReturnShardPayload(returnShardPayload);
+
SecretOwnerTask.Observer ownerObserver =
state -> {
@@ -67,30 +74,21 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
SecretOwnerTask.State.Listening listening =
(SecretOwnerTask.State.Listening) state;
byte[] qrPayload = listening.getLocalPayload();
- System.out.println(qrPayload.length);
transferQrCode(custodianTask, qrPayload);
} else if (state instanceof SecretOwnerTask.State.Success) {
- ReturnShardPayload remotePayload = ((SecretOwnerTask.State.Success) state).getRemotePayload();
- assertTrue(remotePayload.equals(payload));
- System.out.println("Success");
+ remotePayload = ((SecretOwnerTask.State.Success) state).getRemotePayload();
+ secretOwnerFinished.countDown();
} else if (state instanceof SecretOwnerTask.State.Failure) {
- System.out.println("Owner state: failure");
fail();
- } else {
- System.out.println(
- "owner: " + state.getClass().getSimpleName());
}
};
CustodianTask.Observer custodianObserver =
state -> {
if (state instanceof CustodianTask.State.Success) {
- assertEquals(1, 1);
+ custodianFinished.countDown();
} else if (state instanceof CustodianTask.State.Failure) {
fail();
- } else {
- System.out.println(
- "custodian: " + state.getClass().getSimpleName());
}
};
@@ -105,19 +103,14 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
custodian.getIoExecutor().execute(() -> {
try {
- custodianTask.start(custodianObserver, payload);
+ custodianTask.start(custodianObserver, payloadBytes);
} catch (Exception e) {
fail();
}
});
-
- // TODO how to get the test to wait for the io to finish
- try {
-// Thread.sleep(1000);
- tearDown();
- } catch (Exception e) {
- fail();
- }
+ assertTrue(secretOwnerFinished.await(15000, MILLISECONDS));
+ assertTrue(custodianFinished.await(15000, MILLISECONDS));
+ assertTrue(remotePayload.equals(returnShardPayload));
}
diff --git a/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTestComponent.java
index d282194be..d710b1a50 100644
--- a/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/socialbackup/recovery/ReturnShardIntegrationTestComponent.java
@@ -2,6 +2,7 @@ package org.briarproject.briar.socialbackup.recovery;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule;
+import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactManager;
@@ -10,6 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
+import org.briarproject.briar.api.socialbackup.MessageEncoder;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
import org.briarproject.briar.socialbackup.SocialBackupModule;
@@ -44,6 +46,8 @@ interface ReturnShardIntegrationTestComponent
LifecycleManager getLifecycleManager();
+ MessageEncoder getMessageEncoder();
+
SecretOwnerTask getSecretOwnerTask();
CustodianTask getCustodianTask();