mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 04:39:54 +01:00
Merge branch 'social-backup-shard-return' into 'social-backup-poc'
Social backup shard return See merge request briar/briar!1433
This commit is contained in:
@@ -157,25 +157,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.socialbackup.recover.RecoverActivity"
|
android:name="org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity"
|
||||||
android:label="@string/activity_name_recovery"
|
|
||||||
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity"
|
|
||||||
android:label="@string/activity_name_recovery"
|
|
||||||
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity"
|
|
||||||
android:label="@string/activity_name_custodian_help_recovery"
|
android:label="@string/activity_name_custodian_help_recovery"
|
||||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
|
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
@@ -183,6 +165,16 @@
|
|||||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity"
|
||||||
|
android:label="@string/activity_name_recovery"
|
||||||
|
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.conversation.ConversationActivity"
|
android:name="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
|||||||
import org.briarproject.briar.android.reporting.DevReportModule;
|
import org.briarproject.briar.android.reporting.DevReportModule;
|
||||||
import org.briarproject.briar.android.settings.SettingsModule;
|
import org.briarproject.briar.android.settings.SettingsModule;
|
||||||
import org.briarproject.briar.android.sharing.SharingModule;
|
import org.briarproject.briar.android.sharing.SharingModule;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardModule;
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardModule;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardModule;
|
||||||
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
|
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
|
||||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
@@ -92,7 +93,8 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
GroupListModule.class,
|
GroupListModule.class,
|
||||||
GroupConversationModule.class,
|
GroupConversationModule.class,
|
||||||
SharingModule.class,
|
SharingModule.class,
|
||||||
ReturnShardModule.class
|
OwnerReturnShardModule.class,
|
||||||
|
CustodianReturnShardModule.class
|
||||||
})
|
})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.RecoverActivity;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity;
|
|
||||||
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
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_CLEAR_TOP;
|
||||||
@@ -47,7 +46,7 @@ public class NewOrRecoverActivity extends BaseActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void recoverAccountChosen() {
|
public void recoverAccountChosen() {
|
||||||
finish();
|
finish();
|
||||||
Intent i = new Intent(this, ReturnShardActivity.class);
|
Intent i = new Intent(this, OwnerReturnShardActivity.class);
|
||||||
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
|
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
|
||||||
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
|
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
|
|||||||
@@ -80,18 +80,20 @@ import org.briarproject.briar.android.sharing.ShareBlogFragment;
|
|||||||
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
||||||
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
||||||
import org.briarproject.briar.android.sharing.SharingModule;
|
import org.briarproject.briar.android.sharing.SharingModule;
|
||||||
//import org.briarproject.briar.android.socialbackup.CustodianDisplayFragment;
|
import org.briarproject.briar.android.socialbackup.recover.CustodianRecoveryModeExplainerFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity;
|
|
||||||
import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
|
import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
|
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardSuccessFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.RecoverActivity;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeMainFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
|
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
|
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
|
||||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity;
|
|
||||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardFragment;
|
|
||||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||||
import org.briarproject.briar.android.test.TestDataActivity;
|
import org.briarproject.briar.android.test.TestDataActivity;
|
||||||
|
|
||||||
@@ -198,9 +200,11 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(NewOrRecoverActivity newOrRecoverActivity);
|
void inject(NewOrRecoverActivity newOrRecoverActivity);
|
||||||
|
|
||||||
void inject(CustodianHelpRecoverActivity custodianHelpRecoverActivity);
|
void inject(CustodianReturnShardActivity custodianReturnShardActivity);
|
||||||
|
|
||||||
void inject(ReturnShardActivity returnShardActivity);
|
void inject(OwnerReturnShardActivity ownerReturnShardActivity);
|
||||||
|
|
||||||
|
void inject(OwnerRecoveryModeMainFragment ownerRecoveryModeMainFragment);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
|
|
||||||
@@ -264,8 +268,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(DistributedBackupActivity distributedBackupActivity);
|
void inject(DistributedBackupActivity distributedBackupActivity);
|
||||||
|
|
||||||
void inject(RecoverActivity recoverActivity);
|
|
||||||
|
|
||||||
void inject(DatabaseComponent databaseComponent);
|
void inject(DatabaseComponent databaseComponent);
|
||||||
|
|
||||||
void inject(CustodianSelectorFragment custodianSelectorFragment);
|
void inject(CustodianSelectorFragment custodianSelectorFragment);
|
||||||
@@ -278,5 +280,11 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(NewOrRecoverFragment newOrRecoverFragment);
|
void inject(NewOrRecoverFragment newOrRecoverFragment);
|
||||||
|
|
||||||
void inject(ReturnShardFragment returnShardFragment);
|
void inject(CustodianRecoveryModeExplainerFragment custodianRecoveryModeExplainerFragment);
|
||||||
|
|
||||||
|
void inject(CustodianReturnShardFragment custodianReturnShardFragment);
|
||||||
|
|
||||||
|
void inject(OwnerReturnShardFragment ownerReturnShardFragment);
|
||||||
|
|
||||||
|
void inject(CustodianReturnShardSuccessFragment custodianReturnShardSuccessFragment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache
|
|||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity;
|
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.android.view.ImagePreview;
|
import org.briarproject.briar.android.view.ImagePreview;
|
||||||
@@ -399,7 +399,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
return true;
|
return true;
|
||||||
case R.id.action_help_recover_account:
|
case R.id.action_help_recover_account:
|
||||||
if (contactId == null) return false;
|
if (contactId == null) return false;
|
||||||
Intent i = new Intent(this, CustodianHelpRecoverActivity.class);
|
Intent i = new Intent(this, CustodianReturnShardActivity.class);
|
||||||
i.putExtra(CONTACT_ID, contactId.getInt());
|
i.putExtra(CONTACT_ID, contactId.getInt());
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package org.briarproject.briar.android.socialbackup;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
|
||||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity;
|
|
||||||
import org.briarproject.briar.api.socialbackup.MessageEncoder;
|
|
||||||
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
|
||||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
|
||||||
|
|
||||||
public class CustodianHelpRecoverActivity extends BriarActivity implements
|
|
||||||
BaseFragment.BaseFragmentListener, CustodianScanQrButtonListener {
|
|
||||||
@Override
|
|
||||||
public void injectActivity(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String RETURN_SHARD_PAYLOAD = "ReturnShardPayload";
|
|
||||||
|
|
||||||
private ContactId contactId;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SocialBackupManager socialBackupManager;
|
|
||||||
|
|
||||||
// @Inject
|
|
||||||
// public MessageEncoder messageEncoder;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public DatabaseComponent db;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_recover); // TODO change this
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
int id = intent.getIntExtra(CONTACT_ID, -1);
|
|
||||||
if (id == -1) throw new IllegalStateException("No ContactId");
|
|
||||||
contactId = new ContactId(id);
|
|
||||||
|
|
||||||
// check if we have a shard for this secret owner
|
|
||||||
try {
|
|
||||||
db.transaction(false, txn -> {
|
|
||||||
if (!socialBackupManager.amCustodian(txn, contactId)) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
CustodianRecoveryModeExplainerFragment fragment =
|
|
||||||
new CustodianRecoveryModeExplainerFragment();
|
|
||||||
showInitialFragment(fragment);
|
|
||||||
});
|
|
||||||
} catch (DbException e) {
|
|
||||||
// TODO improve this
|
|
||||||
Toast.makeText(this,
|
|
||||||
"You do not hold a backup shard from this contact",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scanQrButtonClicked() {
|
|
||||||
try {
|
|
||||||
db.transaction(false, txn -> {
|
|
||||||
byte[] returnShardPayloadBytes = socialBackupManager
|
|
||||||
.getReturnShardPayloadBytes(txn, contactId);
|
|
||||||
|
|
||||||
Intent i = new Intent(this, ReturnShardActivity.class);
|
|
||||||
i.putExtra(RETURN_SHARD_PAYLOAD, returnShardPayloadBytes);
|
|
||||||
startActivity(i);
|
|
||||||
});
|
|
||||||
} catch (DbException e) {
|
|
||||||
Toast.makeText(this,
|
|
||||||
"Error reading social backup from storage",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package org.briarproject.briar.android.socialbackup;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
public class CustodianRecoveryModeExplainerFragment extends BaseFragment {
|
|
||||||
|
|
||||||
protected CustodianScanQrButtonListener listener;
|
|
||||||
|
|
||||||
public static final String TAG = CustodianRecoveryModeExplainerFragment.class.getName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
requireActivity().setTitle(R.string.title_help_recover);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable
|
|
||||||
ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.fragment_recovery_custodian_explainer,
|
|
||||||
container, false);
|
|
||||||
|
|
||||||
Button button = view.findViewById(R.id.button);
|
|
||||||
button.setOnClickListener(e -> listener.scanQrButtonClicked());
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
listener = (CustodianScanQrButtonListener) context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUniqueTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package org.briarproject.briar.android.socialbackup;
|
package org.briarproject.briar.android.socialbackup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.socialbackup.Shard;
|
|
||||||
import org.briarproject.briar.api.socialbackup.DarkCrystal;
|
import org.briarproject.briar.api.socialbackup.DarkCrystal;
|
||||||
|
import org.briarproject.briar.api.socialbackup.Shard;
|
||||||
import org.magmacollective.darkcrystal.secretsharingwrapper.SecretSharingWrapper;
|
import org.magmacollective.darkcrystal.secretsharingwrapper.SecretSharingWrapper;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
@@ -13,14 +14,12 @@ import java.util.Random;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.Provides;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES;
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class DarkCrystalImpl implements DarkCrystal {
|
public class DarkCrystalImpl implements DarkCrystal {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DarkCrystalImpl() {
|
DarkCrystalImpl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +29,8 @@ public class DarkCrystalImpl implements DarkCrystal {
|
|||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
byte[] secretId = new byte[SECRET_ID_BYTES];
|
byte[] secretId = new byte[SECRET_ID_BYTES];
|
||||||
random.nextBytes(secretId);
|
random.nextBytes(secretId);
|
||||||
List<byte[]> shardsBytes = SecretSharingWrapper.share(secret.getBytes(), numShards, threshold);
|
List<byte[]> shardsBytes = SecretSharingWrapper
|
||||||
|
.share(secret.getBytes(), numShards, threshold);
|
||||||
List<Shard> shards = new ArrayList<>(numShards);
|
List<Shard> shards = new ArrayList<>(numShards);
|
||||||
for (byte[] shardBytes : shardsBytes) {
|
for (byte[] shardBytes : shardsBytes) {
|
||||||
shards.add(new Shard(secretId, shardBytes));
|
shards.add(new Shard(secretId, shardBytes));
|
||||||
@@ -44,9 +44,10 @@ public class DarkCrystalImpl implements DarkCrystal {
|
|||||||
// Check each shard has the same secret Id
|
// Check each shard has the same secret Id
|
||||||
byte[] secretId = shards.get(0).getSecretId();
|
byte[] secretId = shards.get(0).getSecretId();
|
||||||
for (Shard shard : shards) {
|
for (Shard shard : shards) {
|
||||||
if (!Arrays.equals(shard.getSecretId(), secretId)) throw new GeneralSecurityException();
|
if (!Arrays.equals(shard.getSecretId(), secretId))
|
||||||
|
throw new GeneralSecurityException();
|
||||||
}
|
}
|
||||||
List<byte[]> shardsBytes = new ArrayList<>(shards.size());
|
List<byte[]> shardsBytes = new ArrayList<>(shards.size());
|
||||||
for (Shard shard : shards) {
|
for (Shard shard : shards) {
|
||||||
shardsBytes.add(shard.getShard());
|
shardsBytes.add(shard.getShard());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class DistributedBackupActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void contactsSelected(Collection<ContactId> contacts) {
|
public void contactsSelected(Collection<ContactId> contacts) {
|
||||||
Toast.makeText(this,
|
Toast.makeText(this,
|
||||||
String.format("selected %d contacts", contacts.size()),
|
String.format("Selected %d contacts", contacts.size()),
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
custodians = contacts;
|
custodians = contacts;
|
||||||
ThresholdSelectorFragment fragment =
|
ThresholdSelectorFragment fragment =
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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 CustodianRecoveryModeExplainerFragment extends BaseFragment {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private CustodianReturnShardViewModel viewModel;
|
||||||
|
|
||||||
|
public static final String TAG =
|
||||||
|
CustodianRecoveryModeExplainerFragment.class.getName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(CustodianReturnShardViewModel.class);
|
||||||
|
}
|
||||||
|
// @Override
|
||||||
|
// public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
// super.onCreate(savedInstanceState);
|
||||||
|
// requireActivity().setTitle(R.string.title_help_recover);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable
|
||||||
|
ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View view =
|
||||||
|
inflater.inflate(R.layout.fragment_recovery_custodian_explainer,
|
||||||
|
container, false);
|
||||||
|
|
||||||
|
Button button = view.findViewById(R.id.button);
|
||||||
|
button.setOnClickListener(e -> viewModel.onContinueClicked());
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
|
|
||||||
|
public class CustodianReturnShardActivity extends BriarActivity
|
||||||
|
implements BaseFragment.BaseFragmentListener {
|
||||||
|
|
||||||
|
private CustodianReturnShardViewModel viewModel;
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(CustodianReturnShardActivity.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(CustodianReturnShardViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_fragment_container);
|
||||||
|
if (state == null) {
|
||||||
|
Intent intent = getIntent();
|
||||||
|
int id = intent.getIntExtra(CONTACT_ID, -1);
|
||||||
|
if (id == -1) throw new IllegalStateException("No ContactId");
|
||||||
|
ContactId contactId = new ContactId(id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
viewModel.start(contactId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO improve this
|
||||||
|
Toast.makeText(this,
|
||||||
|
"It looks like you are not connected to a Wifi network",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} catch (DbException e) {
|
||||||
|
Toast.makeText(this,
|
||||||
|
"You do not hold a backup piece for this contact",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
showInitialFragment(new CustodianRecoveryModeExplainerFragment());
|
||||||
|
}
|
||||||
|
viewModel.getShowCameraFragment().observeEvent(this, show -> {
|
||||||
|
if (show) showCameraFragment();
|
||||||
|
});
|
||||||
|
viewModel.getSuccessDismissed().observeEvent(this, dismissed -> {
|
||||||
|
if (dismissed) finish();
|
||||||
|
});
|
||||||
|
viewModel.getState()
|
||||||
|
.observe(this, this::onReturnShardStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onReturnShardStateChanged(CustodianTask.State state) {
|
||||||
|
if (state instanceof CustodianTask.State.Success) {
|
||||||
|
CustodianReturnShardSuccessFragment fragment = new CustodianReturnShardSuccessFragment();
|
||||||
|
showNextFragment(fragment);
|
||||||
|
} else if (state instanceof CustodianTask.State.Failure) {
|
||||||
|
// TODO error fragment here
|
||||||
|
// TODO handle reason
|
||||||
|
Toast.makeText(this,
|
||||||
|
"Backup piece transfer failed",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCameraFragment() {
|
||||||
|
// FIXME #824
|
||||||
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
|
if (fm.findFragmentByTag(CustodianReturnShardFragment.TAG) == null) {
|
||||||
|
BaseFragment f = CustodianReturnShardFragment.newInstance();
|
||||||
|
fm.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
|
.addToBackStack(f.getUniqueTag())
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -17,6 +16,7 @@ import org.briarproject.briar.android.contact.add.nearby.CameraException;
|
|||||||
import org.briarproject.briar.android.contact.add.nearby.CameraView;
|
import org.briarproject.briar.android.contact.add.nearby.CameraView;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.QrCodeView;
|
import org.briarproject.briar.android.view.QrCodeView;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -38,26 +38,25 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ReturnShardFragment extends BaseFragment
|
public class CustodianReturnShardFragment extends BaseFragment
|
||||||
implements QrCodeView.FullscreenListener {
|
implements QrCodeView.FullscreenListener {
|
||||||
|
|
||||||
public static final String TAG = org.briarproject.briar.android.contact.add.nearby.AddNearbyContactFragment.class.getName();
|
public static final String TAG = CustodianReturnShardFragment.class.getName();
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
private static final Logger LOG = Logger.getLogger(TAG);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
private ReturnShardViewModel viewModel;
|
private CustodianReturnShardViewModel viewModel;
|
||||||
private CameraView cameraView;
|
private CameraView cameraView;
|
||||||
private LinearLayout cameraOverlay;
|
private LinearLayout cameraOverlay;
|
||||||
private View statusView;
|
private View statusView;
|
||||||
private QrCodeView qrCodeView;
|
|
||||||
private TextView status;
|
private TextView status;
|
||||||
|
|
||||||
public static ReturnShardFragment newInstance() {
|
public static CustodianReturnShardFragment newInstance() {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
ReturnShardFragment fragment = new ReturnShardFragment();
|
CustodianReturnShardFragment fragment = new CustodianReturnShardFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
@@ -66,7 +65,7 @@ public class ReturnShardFragment extends BaseFragment
|
|||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
.get(ReturnShardViewModel.class);
|
.get(CustodianReturnShardViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -85,8 +84,6 @@ public class ReturnShardFragment extends BaseFragment
|
|||||||
cameraOverlay = view.findViewById(R.id.camera_overlay);
|
cameraOverlay = view.findViewById(R.id.camera_overlay);
|
||||||
statusView = view.findViewById(R.id.status_container);
|
statusView = view.findViewById(R.id.status_container);
|
||||||
status = view.findViewById(R.id.connect_status);
|
status = view.findViewById(R.id.connect_status);
|
||||||
qrCodeView = view.findViewById(R.id.qr_code_view);
|
|
||||||
qrCodeView.setFullscreenListener(this);
|
|
||||||
|
|
||||||
viewModel.getState().observe(getViewLifecycleOwner(),
|
viewModel.getState().observe(getViewLifecycleOwner(),
|
||||||
this::onReturnShardStateChanged);
|
this::onReturnShardStateChanged);
|
||||||
@@ -130,31 +127,31 @@ public class ReturnShardFragment extends BaseFragment
|
|||||||
public void setFullscreen(boolean fullscreen) {
|
public void setFullscreen(boolean fullscreen) {
|
||||||
LinearLayout.LayoutParams statusParams, qrCodeParams;
|
LinearLayout.LayoutParams statusParams, qrCodeParams;
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
// Grow the QR code view to fill its parent
|
|
||||||
statusParams = new LinearLayout.LayoutParams(0, 0, 0f);
|
statusParams = new LinearLayout.LayoutParams(0, 0, 0f);
|
||||||
qrCodeParams = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
|
|
||||||
} else {
|
} else {
|
||||||
// Shrink the QR code view to fill half its parent
|
|
||||||
if (cameraOverlay.getOrientation() == HORIZONTAL) {
|
if (cameraOverlay.getOrientation() == HORIZONTAL) {
|
||||||
statusParams = new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
statusParams = new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
||||||
qrCodeParams = new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
|
||||||
} else {
|
} else {
|
||||||
statusParams = new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
statusParams = new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
||||||
qrCodeParams = new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statusView.setLayoutParams(statusParams);
|
statusView.setLayoutParams(statusParams);
|
||||||
qrCodeView.setLayoutParams(qrCodeParams);
|
|
||||||
cameraOverlay.invalidate();
|
cameraOverlay.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onReturnShardStateChanged(@Nullable ReturnShardState state) {
|
private void onReturnShardStateChanged(@Nullable CustodianTask.State state) {
|
||||||
if (state instanceof ReturnShardState.KeyAgreementListening) {
|
LOG.info("State changed");
|
||||||
Bitmap qrCode =
|
// if (state instanceof CustodianTask.State.Connecting) {
|
||||||
((ReturnShardState.KeyAgreementListening) state).qrCode;
|
// try {
|
||||||
qrCodeView.setQrCode(qrCode);
|
// cameraView.stop();
|
||||||
} else if (state instanceof ReturnShardState.QrCodeScanned) {
|
// } catch (CameraException e) {
|
||||||
|
// logCameraExceptionAndFinish(e);
|
||||||
|
// }
|
||||||
|
// cameraView.setVisibility(INVISIBLE);
|
||||||
|
// statusView.setVisibility(VISIBLE);
|
||||||
|
// status.setText(R.string.connecting_to_device);
|
||||||
|
if (state instanceof CustodianTask.State.SendingShard) {
|
||||||
try {
|
try {
|
||||||
cameraView.stop();
|
cameraView.stop();
|
||||||
} catch (CameraException e) {
|
} catch (CameraException e) {
|
||||||
@@ -162,15 +159,13 @@ public class ReturnShardFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
cameraView.setVisibility(INVISIBLE);
|
cameraView.setVisibility(INVISIBLE);
|
||||||
statusView.setVisibility(VISIBLE);
|
statusView.setVisibility(VISIBLE);
|
||||||
status.setText(R.string.connecting_to_device);
|
status.setText("Sending shard");
|
||||||
} else if (state instanceof ReturnShardState.KeyAgreementWaiting) {
|
} else if (state instanceof CustodianTask.State.ReceivingAck) {
|
||||||
status.setText(R.string.waiting_for_contact_to_scan);
|
status.setText("Receiving Ack");
|
||||||
} else if (state instanceof ReturnShardState.KeyAgreementStarted) {
|
} else if (state instanceof CustodianTask.State.Success) {
|
||||||
qrCodeView.setVisibility(INVISIBLE);
|
// TODO
|
||||||
status.setText(R.string.authenticating_with_device);
|
|
||||||
} else if (state instanceof ReturnShardState.SocialBackupExchangeStarted) {
|
|
||||||
status.setText(R.string.exchanging_contact_details);
|
status.setText(R.string.exchanging_contact_details);
|
||||||
} else if (state instanceof ReturnShardState.Failed) {
|
} else if (state instanceof CustodianTask.State.Failure) {
|
||||||
// the activity will replace this fragment with an error fragment
|
// the activity will replace this fragment with an error fragment
|
||||||
statusView.setVisibility(INVISIBLE);
|
statusView.setVisibility(INVISIBLE);
|
||||||
cameraView.setVisibility(INVISIBLE);
|
cameraView.setVisibility(INVISIBLE);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public abstract class CustodianReturnShardModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(CustodianReturnShardViewModel.class)
|
||||||
|
abstract ViewModel bindCustodianReturnShardViewModel(
|
||||||
|
CustodianReturnShardViewModel custodianReturnShardViewModel);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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 CustodianReturnShardSuccessFragment extends
|
||||||
|
BaseFragment {
|
||||||
|
|
||||||
|
public static final String TAG =
|
||||||
|
CustodianReturnShardFragment.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_done,
|
||||||
|
container, false);
|
||||||
|
|
||||||
|
Button button = view.findViewById(R.id.button);
|
||||||
|
button.setOnClickListener(e -> viewModel.onSuccessDismissed());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.net.wifi.WifiInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.QrCodeDecoder;
|
||||||
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
|
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
public class CustodianReturnShardViewModel extends AndroidViewModel
|
||||||
|
implements QrCodeDecoder.ResultCallback, CustodianTask.Observer {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(CustodianReturnShardViewModel.class.getName());
|
||||||
|
|
||||||
|
private final AndroidExecutor androidExecutor;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
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<Boolean> showCameraFragment =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveEvent<Boolean> successDismissed =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveData<CustodianTask.State> state =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private final CustodianTask task;
|
||||||
|
private byte[] returnShardPayloadBytes;
|
||||||
|
|
||||||
|
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
||||||
|
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CustodianReturnShardViewModel(
|
||||||
|
@NonNull Application app,
|
||||||
|
@IoExecutor Executor ioExecutor,
|
||||||
|
SocialBackupManager socialBackupManager,
|
||||||
|
DatabaseComponent db,
|
||||||
|
CustodianTask task,
|
||||||
|
AndroidExecutor androidExecutor) {
|
||||||
|
super(app);
|
||||||
|
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.socialBackupManager = socialBackupManager;
|
||||||
|
this.db = db;
|
||||||
|
this.task = task;
|
||||||
|
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
|
||||||
|
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress getWifiIpv4Address() {
|
||||||
|
if (wifiManager == null) return null;
|
||||||
|
// If we're connected to a wifi network, return its address
|
||||||
|
WifiInfo info = wifiManager.getConnectionInfo();
|
||||||
|
if (info != null && info.getIpAddress() != 0) {
|
||||||
|
return intToInetAddress(info.getIpAddress());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this is not the right place for this
|
||||||
|
private InetAddress intToInetAddress(int ip) {
|
||||||
|
byte[] ipBytes = new byte[4];
|
||||||
|
ipBytes[0] = (byte) (ip & 0xFF);
|
||||||
|
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
|
||||||
|
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
|
||||||
|
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(ipBytes);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// Should only be thrown if address has illegal length
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
db.transaction(false, txn -> {
|
||||||
|
if (!socialBackupManager.amCustodian(txn, contactId)) {
|
||||||
|
throw new DbException();
|
||||||
|
}
|
||||||
|
returnShardPayloadBytes = socialBackupManager
|
||||||
|
.getReturnShardPayloadBytes(txn, contactId);
|
||||||
|
});
|
||||||
|
task.cancel();
|
||||||
|
task.start(this, returnShardPayloadBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
@Override
|
||||||
|
public void onQrCodeDecoded(Result result) {
|
||||||
|
LOG.info("Got result from decoder");
|
||||||
|
if (qrCodeRead) return;
|
||||||
|
try {
|
||||||
|
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
task.qrCodeDecoded(payloadBytes);
|
||||||
|
});
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOG.log(WARNING, "QR Code Invalid", e);
|
||||||
|
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
|
||||||
|
R.string.qr_code_invalid, LENGTH_LONG).show());
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
task.qrCodeDecoded(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public void onContinueClicked() {
|
||||||
|
wasContinueClicked = true;
|
||||||
|
// checkPermissions.setEvent(true);
|
||||||
|
showCameraFragment.setEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public void onSuccessDismissed() {
|
||||||
|
successDismissed.setEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QrCodeDecoder getQrCodeDecoder() {
|
||||||
|
return qrCodeDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getShowCameraFragment() {
|
||||||
|
return showCameraFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getSuccessDismissed() {
|
||||||
|
return successDismissed;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<CustodianTask.State> getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(CustodianTask.State state) {
|
||||||
|
this.state.postValue(state);
|
||||||
|
// Connecting, SendingShard, ReceivingAck, Success, Failure
|
||||||
|
if (state instanceof CustodianTask.State.SendingShard) {
|
||||||
|
qrCodeRead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
|||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
private ReturnShardViewModel viewModel;
|
private OwnerReturnShardViewModel viewModel;
|
||||||
|
|
||||||
// @Override
|
// @Override
|
||||||
// public void onCreate(@Nullable Bundle savedInstanceState) {
|
// public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@@ -36,7 +36,7 @@ public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
|||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
.get(ReturnShardViewModel.class);
|
.get(OwnerReturnShardViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,11 +47,10 @@ public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
|||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
System.out.println("GOt here *************************************");
|
|
||||||
View view = inflater.inflate(R.layout.fragment_recovery_owner_explainer,
|
View view = inflater.inflate(R.layout.fragment_recovery_owner_explainer,
|
||||||
container, false);
|
container, false);
|
||||||
Button button = view.findViewById(R.id.beginButton);
|
Button button = view.findViewById(R.id.beginButton);
|
||||||
button.setOnClickListener(e -> viewModel.onContinueClicked());
|
button.setOnClickListener(e -> viewModel.onStartClicked());
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.socialbackup;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -9,44 +9,47 @@ import android.widget.Button;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.ScanQrButtonListener;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
public class OwnerRecoveryModeMainFragment extends BaseFragment {
|
public class OwnerRecoveryModeMainFragment extends BaseFragment {
|
||||||
|
|
||||||
protected ScanQrButtonListener listener;
|
|
||||||
|
|
||||||
public static final String NUM_RECOVERED = "num_recovered";
|
public static final String NUM_RECOVERED = "num_recovered";
|
||||||
|
|
||||||
public static final String TAG =
|
public static final String TAG =
|
||||||
OwnerRecoveryModeMainFragment.class.getName();
|
OwnerRecoveryModeMainFragment.class.getName();
|
||||||
|
|
||||||
public static OwnerRecoveryModeMainFragment newInstance(int numRecovered) {
|
@Inject
|
||||||
Bundle args = new Bundle();
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
args.putInt(NUM_RECOVERED, numRecovered);
|
|
||||||
OwnerRecoveryModeMainFragment fragment =
|
|
||||||
new OwnerRecoveryModeMainFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int numShards;
|
private OwnerReturnShardViewModel viewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(OwnerReturnShardViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUniqueTag() {
|
public String getUniqueTag() {
|
||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
// public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
// super.onCreate(savedInstanceState);
|
||||||
requireActivity().setTitle(R.string.title_recovery_mode);
|
// requireActivity().setTitle(R.string.title_recovery_mode);
|
||||||
|
//
|
||||||
Bundle args = requireArguments();
|
// Bundle args = requireArguments();
|
||||||
numShards = args.getInt(NUM_RECOVERED);
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -57,17 +60,10 @@ public class OwnerRecoveryModeMainFragment extends BaseFragment {
|
|||||||
container, false);
|
container, false);
|
||||||
|
|
||||||
TextView textViewCount = view.findViewById(R.id.textViewShardCount);
|
TextView textViewCount = view.findViewById(R.id.textViewShardCount);
|
||||||
textViewCount.setText(String.format("%d", numShards));
|
textViewCount.setText(String.format("%d", viewModel.getNumberOfShards()));
|
||||||
|
|
||||||
Button button = view.findViewById(R.id.button);
|
Button button = view.findViewById(R.id.button);
|
||||||
button.setOnClickListener(e -> listener.scanQrButtonClicked());
|
button.setOnClickListener(e -> viewModel.onContinueClicked());
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
listener = (ScanQrButtonListener) context;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
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 org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class OwnerReturnShardActivity extends BaseActivity
|
||||||
|
implements BaseFragment.BaseFragmentListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(OwnerReturnShardActivity.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private OwnerReturnShardViewModel viewModel;
|
||||||
|
|
||||||
|
// private final ActivityResultLauncher<String[]> permissionLauncher =
|
||||||
|
// registerForActivityResult(
|
||||||
|
// new ActivityResultContracts.RequestMultiplePermissions(),
|
||||||
|
// r ->
|
||||||
|
// permissionManager.onRequestPermissionResult(r,
|
||||||
|
// viewModel::showQrCodeFragmentIfAllowed));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(OwnerReturnShardViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_fragment_container);
|
||||||
|
if (state == null) {
|
||||||
|
showInitialFragment(new OwnerRecoveryModeExplainerFragment());
|
||||||
|
}
|
||||||
|
viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
|
||||||
|
if (show) {
|
||||||
|
viewModel.startListening();
|
||||||
|
showQrCodeFragment();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewModel.getStartClicked().observeEvent(this, start -> {
|
||||||
|
if (start) {
|
||||||
|
showNextFragment(new OwnerRecoveryModeMainFragment());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewModel.getState()
|
||||||
|
.observe(this, this::onReturnShardStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostResume() {
|
||||||
|
super.onPostResume();
|
||||||
|
// viewModel.setIsActivityResumed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
// viewModel.setIsActivityResumed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
// TODO should we cancel the return shard task here?
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showQrCodeFragment() {
|
||||||
|
LOG.info("showQrCodeFragment called");
|
||||||
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
|
if (fm.findFragmentByTag(OwnerReturnShardFragment.TAG) == null) {
|
||||||
|
BaseFragment f = OwnerReturnShardFragment.newInstance();
|
||||||
|
fm.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
|
.addToBackStack(f.getUniqueTag())
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void onReturnShardStateChanged(SecretOwnerTask.State state) {
|
||||||
|
if (state instanceof SecretOwnerTask.State.Success) {
|
||||||
|
ReturnShardPayload shardPayload = ((SecretOwnerTask.State.Success) state).getRemotePayload();
|
||||||
|
boolean added = viewModel.addToShardSet(shardPayload);
|
||||||
|
Toast.makeText(this,
|
||||||
|
"Success - got shard" + (added ? "" : " duplicate"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
if (added && viewModel.canRecover()) {
|
||||||
|
LOG.info("Secret key recovered");
|
||||||
|
int version = 0;
|
||||||
|
try {
|
||||||
|
version = viewModel.recover();
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
LOG.warning("Unable to decrypt backup" + e.toString());
|
||||||
|
Toast.makeText(this,
|
||||||
|
"Unable to decrypt backup",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.warning("Unable to parse backup" + e.getMessage() + e.getStackTrace().toString());
|
||||||
|
Toast.makeText(this,
|
||||||
|
"Unable to parse backup",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Toast.makeText(this,
|
||||||
|
"Account recovered! " + version,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private void showErrorFragment() {
|
||||||
|
// // TODO change this for an appropriate error message fragment
|
||||||
|
// showNextFragment(new AddNearbyContactErrorFragment());
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void runOnDbThread(Runnable runnable) {
|
||||||
|
throw new RuntimeException("Don't use this deprecated method here.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
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.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
import org.briarproject.briar.android.view.QrCodeView;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||||
|
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||||
|
import static android.view.View.INVISIBLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
|
import static android.widget.LinearLayout.HORIZONTAL;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class OwnerReturnShardFragment extends BaseFragment
|
||||||
|
implements QrCodeView.FullscreenListener {
|
||||||
|
|
||||||
|
public static final String TAG = OwnerReturnShardFragment.class.getName();
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(TAG);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private OwnerReturnShardViewModel viewModel;
|
||||||
|
private LinearLayout cameraOverlay;
|
||||||
|
private View statusView;
|
||||||
|
private QrCodeView qrCodeView;
|
||||||
|
private TextView status;
|
||||||
|
|
||||||
|
public static OwnerReturnShardFragment newInstance() {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
OwnerReturnShardFragment fragment = new OwnerReturnShardFragment();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(OwnerReturnShardViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_keyagreement_qr, container,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
cameraOverlay = view.findViewById(R.id.camera_overlay);
|
||||||
|
statusView = view.findViewById(R.id.status_container);
|
||||||
|
status = view.findViewById(R.id.connect_status);
|
||||||
|
qrCodeView = view.findViewById(R.id.qr_code_view);
|
||||||
|
qrCodeView.setFullscreenListener(this);
|
||||||
|
|
||||||
|
viewModel.getState().observe(getViewLifecycleOwner(),
|
||||||
|
this::onReturnShardStateChanged);
|
||||||
|
Bitmap qrCodeBitmap = viewModel.getQrCodeBitmap();
|
||||||
|
if (qrCodeBitmap != null) {
|
||||||
|
qrCodeView.setQrCode(qrCodeBitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
requireActivity()
|
||||||
|
.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFullscreen(boolean fullscreen) {
|
||||||
|
LinearLayout.LayoutParams statusParams, qrCodeParams;
|
||||||
|
if (fullscreen) {
|
||||||
|
// Grow the QR code view to fill its parent
|
||||||
|
statusParams = new LinearLayout.LayoutParams(0, 0, 0f);
|
||||||
|
qrCodeParams =
|
||||||
|
new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT,
|
||||||
|
1f);
|
||||||
|
} else {
|
||||||
|
// Shrink the QR code view to fill half its parent
|
||||||
|
if (cameraOverlay.getOrientation() == HORIZONTAL) {
|
||||||
|
statusParams =
|
||||||
|
new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
||||||
|
qrCodeParams =
|
||||||
|
new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
||||||
|
} else {
|
||||||
|
statusParams =
|
||||||
|
new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
||||||
|
qrCodeParams =
|
||||||
|
new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statusView.setLayoutParams(statusParams);
|
||||||
|
qrCodeView.setLayoutParams(qrCodeParams);
|
||||||
|
cameraOverlay.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onReturnShardStateChanged(
|
||||||
|
@Nullable SecretOwnerTask.State state) {
|
||||||
|
if (state instanceof SecretOwnerTask.State.Listening) {
|
||||||
|
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);
|
||||||
|
} else if (state instanceof SecretOwnerTask.State.Success) {
|
||||||
|
status.setText("Success");
|
||||||
|
} else if (state instanceof SecretOwnerTask.State.Failure) {
|
||||||
|
// the activity will replace this fragment with an error fragment
|
||||||
|
statusView.setVisibility(INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish() {
|
||||||
|
requireActivity().getSupportFragmentManager().popBackStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,12 +9,12 @@ import dagger.multibindings.IntoMap;
|
|||||||
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public abstract class ReturnShardModule {
|
public abstract class OwnerReturnShardModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(ReturnShardViewModel.class)
|
@ViewModelKey(OwnerReturnShardViewModel.class)
|
||||||
abstract ViewModel bindContactExchangeViewModel(
|
abstract ViewModel bindOwnerReturnShardViewModel(
|
||||||
ReturnShardViewModel returnShardViewModel);
|
OwnerReturnShardViewModel ownerReturnShardViewModel);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.wifi.WifiInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class OwnerReturnShardViewModel extends AndroidViewModel
|
||||||
|
implements SecretOwnerTask.Observer {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(OwnerReturnShardViewModel.class.getName());
|
||||||
|
|
||||||
|
@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 MutableLiveEvent<Boolean> showQrCodeFragment =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveData<SecretOwnerTask.State> state =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private final MutableLiveEvent<Boolean> startClicked =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private boolean wasContinueClicked = false;
|
||||||
|
private boolean isActivityResumed = false;
|
||||||
|
private ArrayList<ReturnShardPayload> recoveredShards = new ArrayList<>();
|
||||||
|
private Bitmap qrCodeBitmap;
|
||||||
|
private WifiManager wifiManager;
|
||||||
|
private SecretKey secretKey;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
OwnerReturnShardViewModel(Application app,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
SecretOwnerTask task,
|
||||||
|
DarkCrystal darkCrystal,
|
||||||
|
BackupPayloadDecoder backupPayloadDecoder,
|
||||||
|
@IoExecutor Executor ioExecutor) {
|
||||||
|
super(app);
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.backupPayloadDecoder = backupPayloadDecoder;
|
||||||
|
this.darkCrystal = darkCrystal;
|
||||||
|
this.task = task;
|
||||||
|
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
|
||||||
|
|
||||||
|
// IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress getWifiIpv4Address() {
|
||||||
|
if (wifiManager == null) return null;
|
||||||
|
// If we're connected to a wifi network, return its address
|
||||||
|
WifiInfo info = wifiManager.getConnectionInfo();
|
||||||
|
if (info != null && info.getIpAddress() != 0) {
|
||||||
|
return intToInetAddress(info.getIpAddress());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this is not the right place for this
|
||||||
|
private InetAddress intToInetAddress(int ip) {
|
||||||
|
byte[] ipBytes = new byte[4];
|
||||||
|
ipBytes[0] = (byte) (ip & 0xFF);
|
||||||
|
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
|
||||||
|
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
|
||||||
|
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(ipBytes);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// Should only be thrown if address has illegal length
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
stopListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void onStartClicked() {
|
||||||
|
startClicked.setEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void onContinueClicked() {
|
||||||
|
wasContinueClicked = true;
|
||||||
|
startShardReturn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void startShardReturn() {
|
||||||
|
// If we return to the intro fragment, the continue button needs to be
|
||||||
|
// clicked again before showing the QR code fragment
|
||||||
|
wasContinueClicked = false;
|
||||||
|
// If we return to the intro fragment, we may need to enable wifi and
|
||||||
|
// hasEnabledWifi = false;
|
||||||
|
showQrCodeFragment.setEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public void startListening() {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
task.start(this, getWifiIpv4Address());
|
||||||
|
});
|
||||||
|
// KeyAgreementTask oldTask = task;
|
||||||
|
// KeyAgreementTask newTask = keyAgreementTaskProvider.get();
|
||||||
|
// task = newTask;
|
||||||
|
// ioExecutor.execute(() -> {
|
||||||
|
// if (oldTask != null) oldTask.stopListening();
|
||||||
|
// newTask.listen();
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void stopListening() {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
task.cancel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true in onPostResume() and false in onPause(). This prevents the
|
||||||
|
* QR code fragment from being shown if onRequestPermissionsResult() is
|
||||||
|
* called while the activity is paused, which could cause a crash due to
|
||||||
|
* https://issuetracker.google.com/issues/37067655.
|
||||||
|
* TODO check if this is still happening with new permission requesting
|
||||||
|
*/
|
||||||
|
void setIsActivityResumed(boolean resumed) {
|
||||||
|
isActivityResumed = resumed;
|
||||||
|
// Workaround for
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=190966
|
||||||
|
// showQrCodeFragmentIfAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getShowQrCodeFragment() {
|
||||||
|
return showQrCodeFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getStartClicked() {
|
||||||
|
return startClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<SecretOwnerTask.State> getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getQrCodeBitmap() {
|
||||||
|
LOG.info("getting qrCodeBitmap");
|
||||||
|
return qrCodeBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumberOfShards() {
|
||||||
|
return recoveredShards.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(SecretOwnerTask.State state) {
|
||||||
|
if (state instanceof SecretOwnerTask.State.Listening) {
|
||||||
|
DisplayMetrics dm =
|
||||||
|
getApplication().getResources().getDisplayMetrics();
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
byte[] payloadBytes = ((SecretOwnerTask.State.Listening) state)
|
||||||
|
.getLocalPayload();
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Local QR code payload is " + payloadBytes.length
|
||||||
|
+ " bytes");
|
||||||
|
}
|
||||||
|
// Use ISO 8859-1 to encode bytes directly as a string
|
||||||
|
String content = new String(payloadBytes, ISO_8859_1);
|
||||||
|
qrCodeBitmap = QrCodeUtils.createQrCode(dm, content);
|
||||||
|
this.state.postValue(state);
|
||||||
|
});
|
||||||
|
} else if (state instanceof SecretOwnerTask.State.Success) {
|
||||||
|
// startClicked.setEvent(true);
|
||||||
|
this.state.postValue(state);
|
||||||
|
// TODO do same for failure
|
||||||
|
} else {
|
||||||
|
this.state.postValue(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO figure out how to actually use a 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canRecover() {
|
||||||
|
ArrayList<Shard> 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();
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
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 org.briarproject.briar.android.socialbackup.ExplainerDismissedListener;
|
|
||||||
import org.briarproject.briar.android.socialbackup.OwnerRecoveryModeMainFragment;
|
|
||||||
import org.briarproject.briar.android.socialbackup.ScanQrButtonListener;
|
|
||||||
|
|
||||||
public class RecoverActivity extends BaseActivity implements
|
|
||||||
BaseFragment.BaseFragmentListener, ExplainerDismissedListener,
|
|
||||||
ScanQrButtonListener {
|
|
||||||
|
|
||||||
private int numRecovered;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_recover);
|
|
||||||
|
|
||||||
numRecovered = 0; // TODO - retrieve this from somewhere
|
|
||||||
|
|
||||||
// only show the explainer if we have no shards
|
|
||||||
if (numRecovered == 0) {
|
|
||||||
OwnerRecoveryModeExplainerFragment fragment =
|
|
||||||
new OwnerRecoveryModeExplainerFragment();
|
|
||||||
showInitialFragment(fragment);
|
|
||||||
} else {
|
|
||||||
OwnerRecoveryModeMainFragment fragment =
|
|
||||||
OwnerRecoveryModeMainFragment.newInstance(numRecovered);
|
|
||||||
showInitialFragment(fragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectActivity(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void explainerDismissed() {
|
|
||||||
OwnerRecoveryModeMainFragment fragment =
|
|
||||||
OwnerRecoveryModeMainFragment.newInstance(numRecovered);
|
|
||||||
showNextFragment(fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scanQrButtonClicked() {
|
|
||||||
// TODO
|
|
||||||
Toast.makeText(this,
|
|
||||||
"coming soon...",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public void runOnDbThread(Runnable runnable) {
|
|
||||||
throw new RuntimeException("Don't use this deprecated method here.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
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.contact.add.nearby.AddNearbyContactErrorFragment;
|
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
|
||||||
import org.briarproject.briar.android.util.RequestBluetoothDiscoverable;
|
|
||||||
import org.briarproject.briar.api.socialbackup.BackupPayload;
|
|
||||||
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
|
||||||
import org.briarproject.briar.api.socialbackup.Shard;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
|
||||||
import static org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity.RETURN_SHARD_PAYLOAD;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class ReturnShardActivity extends BaseActivity
|
|
||||||
implements BaseFragment.BaseFragmentListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(ReturnShardActivity.class.getName());
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
// @Inject
|
|
||||||
// MessageParser messageParser;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ClientHelper clientHelper;
|
|
||||||
|
|
||||||
private ReturnShardViewModel viewModel;
|
|
||||||
private AddNearbyContactPermissionManager permissionManager;
|
|
||||||
|
|
||||||
private final ActivityResultLauncher<String[]> permissionLauncher =
|
|
||||||
registerForActivityResult(
|
|
||||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
|
||||||
r ->
|
|
||||||
permissionManager.onRequestPermissionResult(r,
|
|
||||||
viewModel::showQrCodeFragmentIfAllowed));
|
|
||||||
private final ActivityResultLauncher<Integer> bluetoothLauncher =
|
|
||||||
registerForActivityResult(new RequestBluetoothDiscoverable(),
|
|
||||||
this::onBluetoothDiscoverableResult);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectActivity(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
|
||||||
.get(ReturnShardViewModel.class);
|
|
||||||
permissionManager = new AddNearbyContactPermissionManager(this,
|
|
||||||
permissionLauncher::launch, viewModel.isBluetoothSupported());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO the following two methods should be injected from messageParser
|
|
||||||
private Shard parseShardMessage(BdfList body) throws FormatException {
|
|
||||||
// Message type, secret ID, shard
|
|
||||||
byte[] secretId = body.getRaw(1);
|
|
||||||
byte[] shard = body.getRaw(2);
|
|
||||||
return new Shard(secretId, shard);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReturnShardPayload parseReturnShardPayload(BdfList body)
|
|
||||||
throws FormatException {
|
|
||||||
checkSize(body, 2);
|
|
||||||
Shard shard = parseShardMessage(body.getList(0));
|
|
||||||
org.briarproject.briar.api.socialbackup.BackupPayload backupPayload =
|
|
||||||
new BackupPayload(body.getRaw(1));
|
|
||||||
return new ReturnShardPayload(shard, backupPayload);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle state) {
|
|
||||||
super.onCreate(state);
|
|
||||||
|
|
||||||
byte[] returnShardPayloadBytes =
|
|
||||||
getIntent().getByteArrayExtra(RETURN_SHARD_PAYLOAD);
|
|
||||||
if (returnShardPayloadBytes != null) {
|
|
||||||
try {
|
|
||||||
ReturnShardPayload returnShardPayload = parseReturnShardPayload(
|
|
||||||
clientHelper.toList(returnShardPayloadBytes));
|
|
||||||
viewModel.setSending(true);
|
|
||||||
viewModel.setReturnShardPayload(returnShardPayload);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
Toast.makeText(this,
|
|
||||||
"Error reading social backup",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setContentView(R.layout.activity_fragment_container);
|
|
||||||
if (state == null) {
|
|
||||||
showInitialFragment(getExplainerFragment());
|
|
||||||
}
|
|
||||||
viewModel.getCheckPermissions().observeEvent(this, check ->
|
|
||||||
permissionManager.checkPermissions());
|
|
||||||
viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r ->
|
|
||||||
requestBluetoothDiscoverable()); // never false
|
|
||||||
viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
|
|
||||||
if (show) showQrCodeFragment();
|
|
||||||
});
|
|
||||||
viewModel.getState()
|
|
||||||
.observe(this, this::onReturnShardStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseFragment getExplainerFragment() {
|
|
||||||
return new OwnerRecoveryModeExplainerFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
// Permissions may have been granted manually while we were stopped
|
|
||||||
permissionManager.resetPermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostResume() {
|
|
||||||
super.onPostResume();
|
|
||||||
viewModel.setIsActivityResumed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
viewModel.setIsActivityResumed(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onBluetoothDiscoverableResult(boolean discoverable) {
|
|
||||||
if (discoverable) {
|
|
||||||
LOG.info("Bluetooth discoverability was accepted");
|
|
||||||
viewModel.setBluetoothDecision(
|
|
||||||
ReturnShardViewModel.BluetoothDecision.ACCEPTED);
|
|
||||||
} else {
|
|
||||||
LOG.info("Bluetooth discoverability was refused");
|
|
||||||
viewModel.setBluetoothDecision(
|
|
||||||
ReturnShardViewModel.BluetoothDecision.REFUSED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if (viewModel.getState()
|
|
||||||
.getValue() instanceof ReturnShardState.Failed) {
|
|
||||||
// re-create this activity when going back in failed state
|
|
||||||
Intent i = new Intent(this, ReturnShardActivity.class);
|
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
startActivity(i);
|
|
||||||
} else {
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestBluetoothDiscoverable() {
|
|
||||||
if (!viewModel.isBluetoothSupported()) {
|
|
||||||
viewModel.setBluetoothDecision(
|
|
||||||
ReturnShardViewModel.BluetoothDecision.NO_ADAPTER);
|
|
||||||
} else {
|
|
||||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
|
||||||
if (i.resolveActivity(getPackageManager()) != null) {
|
|
||||||
LOG.info("Asking for Bluetooth discoverability");
|
|
||||||
viewModel.setBluetoothDecision(
|
|
||||||
ReturnShardViewModel.BluetoothDecision.WAITING);
|
|
||||||
bluetoothLauncher.launch(120); // 2min discoverable
|
|
||||||
} else {
|
|
||||||
viewModel.setBluetoothDecision(
|
|
||||||
ReturnShardViewModel.BluetoothDecision.NO_ADAPTER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showQrCodeFragment() {
|
|
||||||
// FIXME #824
|
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
|
||||||
if (fm.findFragmentByTag(ReturnShardFragment.TAG) == null) {
|
|
||||||
BaseFragment f = ReturnShardFragment.newInstance();
|
|
||||||
fm.beginTransaction()
|
|
||||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
|
||||||
.addToBackStack(f.getUniqueTag())
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onReturnShardStateChanged(ReturnShardState state) {
|
|
||||||
if (state instanceof ReturnShardState.SocialBackupExchangeFinished) {
|
|
||||||
ReturnShardState.SocialBackupExchangeResult result =
|
|
||||||
((ReturnShardState.SocialBackupExchangeFinished) state).result;
|
|
||||||
onSocialBackupExchangeResult(result);
|
|
||||||
} else if (state instanceof ReturnShardState.Failed) {
|
|
||||||
Boolean qrCodeTooOld =
|
|
||||||
((ReturnShardState.Failed) state).qrCodeTooOld;
|
|
||||||
onAddingContactFailed(qrCodeTooOld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSocialBackupExchangeResult(
|
|
||||||
ReturnShardState.SocialBackupExchangeResult result) {
|
|
||||||
if (result instanceof ReturnShardState.SocialBackupExchangeResult.Success) {
|
|
||||||
// String text = getString(R.string.contact_added_toast, contactName);
|
|
||||||
Toast.makeText(this, "Shard return successful", LENGTH_LONG).show();
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
} else if (result instanceof ReturnShardState.SocialBackupExchangeResult.Error) {
|
|
||||||
showErrorFragment();
|
|
||||||
} else throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) {
|
|
||||||
if (qrCodeTooOld == null) {
|
|
||||||
showErrorFragment();
|
|
||||||
} else {
|
|
||||||
String msg;
|
|
||||||
if (qrCodeTooOld) {
|
|
||||||
msg = getString(R.string.qr_code_too_old,
|
|
||||||
getString(R.string.app_name));
|
|
||||||
} else {
|
|
||||||
msg = getString(R.string.qr_code_too_new,
|
|
||||||
getString(R.string.app_name));
|
|
||||||
}
|
|
||||||
showNextFragment(AddNearbyContactErrorFragment.newInstance(msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showErrorFragment() {
|
|
||||||
showNextFragment(new AddNearbyContactErrorFragment());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public void runOnDbThread(Runnable runnable) {
|
|
||||||
throw new RuntimeException("Don't use this deprecated method here.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
abstract class ReturnShardState {
|
|
||||||
|
|
||||||
static class KeyAgreementListening extends
|
|
||||||
ReturnShardState {
|
|
||||||
final Bitmap qrCode;
|
|
||||||
|
|
||||||
KeyAgreementListening(Bitmap qrCode) {
|
|
||||||
this.qrCode = qrCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class QrCodeScanned extends
|
|
||||||
ReturnShardState {
|
|
||||||
}
|
|
||||||
|
|
||||||
static class KeyAgreementWaiting extends ReturnShardState {
|
|
||||||
}
|
|
||||||
|
|
||||||
static class KeyAgreementStarted extends ReturnShardState {
|
|
||||||
}
|
|
||||||
|
|
||||||
static class SocialBackupExchangeStarted extends ReturnShardState {
|
|
||||||
}
|
|
||||||
|
|
||||||
static class SocialBackupExchangeFinished extends ReturnShardState {
|
|
||||||
final SocialBackupExchangeResult
|
|
||||||
result;
|
|
||||||
|
|
||||||
SocialBackupExchangeFinished(
|
|
||||||
SocialBackupExchangeResult result) {
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Failed extends ReturnShardState {
|
|
||||||
/**
|
|
||||||
* Non-null if failed due to the scanned QR code version.
|
|
||||||
* True if the app producing the code is too old.
|
|
||||||
* False if the scanning app is too old.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
final Boolean qrCodeTooOld;
|
|
||||||
|
|
||||||
Failed(@Nullable Boolean qrCodeTooOld) {
|
|
||||||
this.qrCodeTooOld = qrCodeTooOld;
|
|
||||||
}
|
|
||||||
|
|
||||||
Failed() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract static class SocialBackupExchangeResult {
|
|
||||||
static class Success extends SocialBackupExchangeResult {
|
|
||||||
Success() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Error extends SocialBackupExchangeResult {
|
|
||||||
@Nullable
|
|
||||||
final Author duplicateAuthor;
|
|
||||||
|
|
||||||
Error(@Nullable Author duplicateAuthor) {
|
|
||||||
this.duplicateAuthor = duplicateAuthor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // end ContactExchangeResult
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,545 +0,0 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.google.zxing.Result;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
|
||||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
|
||||||
import org.briarproject.bramble.api.plugin.Plugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.android.contact.add.nearby.QrCodeDecoder;
|
|
||||||
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.ReturnShardPayload;
|
|
||||||
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
|
|
||||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class ReturnShardViewModel extends AndroidViewModel
|
|
||||||
implements EventListener, QrCodeDecoder.ResultCallback {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(ReturnShardViewModel.class.getName());
|
|
||||||
|
|
||||||
// TODO deduplicate
|
|
||||||
enum BluetoothDecision {
|
|
||||||
/**
|
|
||||||
* We haven't asked the user about Bluetooth discoverability.
|
|
||||||
*/
|
|
||||||
UNKNOWN,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The device doesn't have a Bluetooth adapter.
|
|
||||||
*/
|
|
||||||
NO_ADAPTER,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We're waiting for the user to accept or refuse discoverability.
|
|
||||||
*/
|
|
||||||
WAITING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user has accepted discoverability.
|
|
||||||
*/
|
|
||||||
ACCEPTED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user has refused discoverability.
|
|
||||||
*/
|
|
||||||
REFUSED
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
|
||||||
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
|
||||||
|
|
||||||
private boolean sending;
|
|
||||||
private ReturnShardPayload returnShardPayload;
|
|
||||||
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final AndroidExecutor androidExecutor;
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
private final PayloadEncoder payloadEncoder;
|
|
||||||
private final PayloadParser payloadParser;
|
|
||||||
private final Provider<KeyAgreementTask> keyAgreementTaskProvider;
|
|
||||||
private final SocialBackupExchangeManager socialBackupExchangeManager;
|
|
||||||
private final SocialBackupManager socialBackupManager;
|
|
||||||
private final ConnectionManager connectionManager;
|
|
||||||
|
|
||||||
private final MutableLiveEvent<Boolean> checkPermissions =
|
|
||||||
new MutableLiveEvent<>();
|
|
||||||
private final MutableLiveEvent<Boolean> requestBluetoothDiscoverable =
|
|
||||||
new MutableLiveEvent<>();
|
|
||||||
private final MutableLiveEvent<Boolean> showQrCodeFragment =
|
|
||||||
new MutableLiveEvent<>();
|
|
||||||
private final MutableLiveData<ReturnShardState> state =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
|
|
||||||
final QrCodeDecoder qrCodeDecoder;
|
|
||||||
final BroadcastReceiver
|
|
||||||
bluetoothReceiver = new BluetoothStateReceiver();
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final BluetoothAdapter bt;
|
|
||||||
@Nullable
|
|
||||||
private final Plugin wifiPlugin, bluetoothPlugin;
|
|
||||||
|
|
||||||
// UiThread
|
|
||||||
private BluetoothDecision
|
|
||||||
bluetoothDecision = BluetoothDecision.UNKNOWN;
|
|
||||||
|
|
||||||
private boolean wasContinueClicked = false;
|
|
||||||
private boolean isActivityResumed = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records whether we've enabled the wifi plugin so we don't enable it more
|
|
||||||
* than once.
|
|
||||||
*/
|
|
||||||
private boolean hasEnabledWifi = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records whether we've enabled the Bluetooth plugin so we don't enable it
|
|
||||||
* more than once.
|
|
||||||
*/
|
|
||||||
private boolean hasEnabledBluetooth = false;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private KeyAgreementTask task;
|
|
||||||
private volatile boolean gotLocalPayload = false, gotRemotePayload = false;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ReturnShardViewModel(Application app,
|
|
||||||
EventBus eventBus,
|
|
||||||
AndroidExecutor androidExecutor,
|
|
||||||
@IoExecutor Executor ioExecutor,
|
|
||||||
PluginManager pluginManager,
|
|
||||||
PayloadEncoder payloadEncoder,
|
|
||||||
PayloadParser payloadParser,
|
|
||||||
Provider<KeyAgreementTask> keyAgreementTaskProvider,
|
|
||||||
SocialBackupExchangeManager socialBackupExchangeManager,
|
|
||||||
SocialBackupManager socialBackupManager,
|
|
||||||
ConnectionManager connectionManager) {
|
|
||||||
super(app);
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.androidExecutor = androidExecutor;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
this.payloadEncoder = payloadEncoder;
|
|
||||||
this.payloadParser = payloadParser;
|
|
||||||
this.keyAgreementTaskProvider = keyAgreementTaskProvider;
|
|
||||||
this.socialBackupExchangeManager = socialBackupExchangeManager;
|
|
||||||
this.socialBackupManager = socialBackupManager;
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
bt = BluetoothAdapter.getDefaultAdapter();
|
|
||||||
wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID);
|
|
||||||
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
|
|
||||||
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
|
|
||||||
eventBus.addListener(this);
|
|
||||||
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
|
|
||||||
getApplication().registerReceiver(bluetoothReceiver, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
super.onCleared();
|
|
||||||
getApplication().unregisterReceiver(bluetoothReceiver);
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
stopListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onContinueClicked() {
|
|
||||||
if (bluetoothDecision == BluetoothDecision.REFUSED) {
|
|
||||||
bluetoothDecision = BluetoothDecision.UNKNOWN; // Ask again
|
|
||||||
}
|
|
||||||
wasContinueClicked = true;
|
|
||||||
checkPermissions.setEvent(true);
|
|
||||||
showQrCodeFragmentIfAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
boolean isBluetoothSupported() {
|
|
||||||
return bt != null && bluetoothPlugin != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
boolean isWifiReady() {
|
|
||||||
if (wifiPlugin == null) return true; // Continue without wifi
|
|
||||||
Plugin.State state = wifiPlugin.getState();
|
|
||||||
// Wait for plugin to become enabled
|
|
||||||
return state == ACTIVE || state == INACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
boolean isBluetoothReady() {
|
|
||||||
if (bt == null || bluetoothPlugin == null) {
|
|
||||||
// Continue without Bluetooth
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
|
|
||||||
bluetoothDecision == BluetoothDecision.WAITING ||
|
|
||||||
bluetoothDecision == BluetoothDecision.REFUSED) {
|
|
||||||
// Wait for user to accept
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
|
||||||
// Wait for adapter to become discoverable
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Wait for plugin to become active
|
|
||||||
return bluetoothPlugin.getState() == ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void enableWifiIfWeShould() {
|
|
||||||
if (hasEnabledWifi) return;
|
|
||||||
if (wifiPlugin == null) return;
|
|
||||||
Plugin.State state = wifiPlugin.getState();
|
|
||||||
if (state == STARTING_STOPPING || state == DISABLED) {
|
|
||||||
LOG.info("Enabling wifi plugin");
|
|
||||||
hasEnabledWifi = true;
|
|
||||||
pluginManager.setPluginEnabled(LanTcpConstants.ID, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void enableBluetoothIfWeShould() {
|
|
||||||
if (bluetoothDecision != BluetoothDecision.ACCEPTED)
|
|
||||||
return;
|
|
||||||
if (hasEnabledBluetooth) return;
|
|
||||||
if (bluetoothPlugin == null || !isBluetoothSupported()) return;
|
|
||||||
Plugin.State state = bluetoothPlugin.getState();
|
|
||||||
if (state == STARTING_STOPPING || state == DISABLED) {
|
|
||||||
LOG.info("Enabling Bluetooth plugin");
|
|
||||||
hasEnabledBluetooth = true;
|
|
||||||
pluginManager.setPluginEnabled(BluetoothConstants.ID, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void startAddingContact() {
|
|
||||||
// If we return to the intro fragment, the continue button needs to be
|
|
||||||
// clicked again before showing the QR code fragment
|
|
||||||
wasContinueClicked = false;
|
|
||||||
// If we return to the intro fragment, ask for Bluetooth
|
|
||||||
// discoverability again before showing the QR code fragment
|
|
||||||
bluetoothDecision = BluetoothDecision.UNKNOWN;
|
|
||||||
// If we return to the intro fragment, we may need to enable wifi and
|
|
||||||
// Bluetooth again
|
|
||||||
hasEnabledWifi = false;
|
|
||||||
hasEnabledBluetooth = false;
|
|
||||||
// start to listen with a KeyAgreementTask
|
|
||||||
startListening();
|
|
||||||
showQrCodeFragment.setEvent(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this once Bluetooth and Wi-Fi are ready to be used.
|
|
||||||
* It is possible to call this more than once over the ViewModel's lifetime.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
private void startListening() {
|
|
||||||
KeyAgreementTask oldTask = task;
|
|
||||||
KeyAgreementTask newTask = keyAgreementTaskProvider.get();
|
|
||||||
task = newTask;
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
if (oldTask != null) oldTask.stopListening();
|
|
||||||
newTask.listen();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void stopListening() {
|
|
||||||
KeyAgreementTask oldTask = task;
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
if (oldTask != null) oldTask.stopListening();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof TransportStateEvent) {
|
|
||||||
TransportStateEvent t = (TransportStateEvent) e;
|
|
||||||
if (t.getTransportId().equals(BluetoothConstants.ID)) {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Bluetooth state changed to " + t.getState());
|
|
||||||
}
|
|
||||||
showQrCodeFragmentIfAllowed();
|
|
||||||
} else if (t.getTransportId().equals(LanTcpConstants.ID)) {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Wifi state changed to " + t.getState());
|
|
||||||
}
|
|
||||||
showQrCodeFragmentIfAllowed();
|
|
||||||
}
|
|
||||||
} else if (e instanceof KeyAgreementListeningEvent) {
|
|
||||||
LOG.info("KeyAgreementListeningEvent received");
|
|
||||||
KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e;
|
|
||||||
onLocalPayloadReceived(event.getLocalPayload());
|
|
||||||
} else if (e instanceof KeyAgreementWaitingEvent) {
|
|
||||||
LOG.info("KeyAgreementWaitingEvent received");
|
|
||||||
state.setValue(new ReturnShardState.KeyAgreementWaiting());
|
|
||||||
} else if (e instanceof KeyAgreementStartedEvent) {
|
|
||||||
LOG.info("KeyAgreementStartedEvent received");
|
|
||||||
state.setValue(new ReturnShardState.KeyAgreementStarted());
|
|
||||||
} else if (e instanceof KeyAgreementFinishedEvent) {
|
|
||||||
LOG.info("KeyAgreementFinishedEvent received");
|
|
||||||
KeyAgreementResult result =
|
|
||||||
((KeyAgreementFinishedEvent) e).getResult();
|
|
||||||
startContactExchange(result);
|
|
||||||
state.setValue(new ReturnShardState.SocialBackupExchangeStarted());
|
|
||||||
} else if (e instanceof KeyAgreementAbortedEvent) {
|
|
||||||
LOG.info("KeyAgreementAbortedEvent received");
|
|
||||||
resetPayloadFlags();
|
|
||||||
state.setValue(new ReturnShardState.Failed());
|
|
||||||
} else if (e instanceof KeyAgreementFailedEvent) {
|
|
||||||
LOG.info("KeyAgreementFailedEvent received");
|
|
||||||
resetPayloadFlags();
|
|
||||||
state.setValue(new ReturnShardState.Failed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("StatementWithEmptyBody")
|
|
||||||
@UiThread
|
|
||||||
void showQrCodeFragmentIfAllowed() {
|
|
||||||
boolean permissionsGranted = areEssentialPermissionsGranted(
|
|
||||||
getApplication(), isBluetoothSupported());
|
|
||||||
if (isActivityResumed && wasContinueClicked && permissionsGranted) {
|
|
||||||
if (isWifiReady() && isBluetoothReady()) {
|
|
||||||
LOG.info("Wifi and Bluetooth are ready");
|
|
||||||
startAddingContact();
|
|
||||||
} else {
|
|
||||||
enableWifiIfWeShould();
|
|
||||||
if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
|
|
||||||
requestBluetoothDiscoverable.setEvent(true);
|
|
||||||
} else if (bluetoothDecision == BluetoothDecision.REFUSED) {
|
|
||||||
// Ask again when the user clicks "continue"
|
|
||||||
} else {
|
|
||||||
enableBluetoothIfWeShould();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This sets the QR code by setting the state to KeyAgreementListening.
|
|
||||||
*/
|
|
||||||
private void onLocalPayloadReceived(Payload localPayload) {
|
|
||||||
if (gotLocalPayload) return;
|
|
||||||
DisplayMetrics dm = getApplication().getResources().getDisplayMetrics();
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
byte[] payloadBytes = payloadEncoder.encode(localPayload);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Local payload is " + payloadBytes.length
|
|
||||||
+ " bytes");
|
|
||||||
}
|
|
||||||
// Use ISO 8859-1 to encode bytes directly as a string
|
|
||||||
String content = new String(payloadBytes, ISO_8859_1);
|
|
||||||
Bitmap qrCode = QrCodeUtils.createQrCode(dm, content);
|
|
||||||
gotLocalPayload = true;
|
|
||||||
state.postValue(new ReturnShardState.KeyAgreementListening(qrCode));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@IoExecutor
|
|
||||||
public void onQrCodeDecoded(Result result) {
|
|
||||||
LOG.info("Got result from decoder"+gotLocalPayload+gotRemotePayload);
|
|
||||||
// Ignore results until the KeyAgreementTask is ready
|
|
||||||
if (!gotLocalPayload || gotRemotePayload) return;
|
|
||||||
try {
|
|
||||||
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
|
|
||||||
Payload remotePayload = payloadParser.parse(payloadBytes);
|
|
||||||
gotRemotePayload = true;
|
|
||||||
requireNonNull(task).connectAndRunProtocol(remotePayload);
|
|
||||||
state.postValue(new ReturnShardState.QrCodeScanned());
|
|
||||||
} catch (UnsupportedVersionException e) {
|
|
||||||
resetPayloadFlags();
|
|
||||||
state.postValue(new ReturnShardState.Failed(e.isTooOld()));
|
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
LOG.log(WARNING, "QR Code Invalid", e);
|
|
||||||
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
|
|
||||||
R.string.qr_code_invalid, LENGTH_LONG).show());
|
|
||||||
resetPayloadFlags();
|
|
||||||
state.postValue(new ReturnShardState.Failed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetPayloadFlags() {
|
|
||||||
gotRemotePayload = false;
|
|
||||||
gotLocalPayload = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void startContactExchange(KeyAgreementResult result) {
|
|
||||||
TransportId t = result.getTransportId();
|
|
||||||
DuplexTransportConnection conn = result.getConnection();
|
|
||||||
SecretKey masterKey = result.getMasterKey();
|
|
||||||
boolean alice = result.wasAlice();
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
try {
|
|
||||||
if (sending) {
|
|
||||||
socialBackupExchangeManager.sendReturnShard(conn, masterKey, alice, returnShardPayload);
|
|
||||||
} else {
|
|
||||||
returnShardPayload = socialBackupExchangeManager.receiveReturnShard(conn, masterKey, alice);
|
|
||||||
}
|
|
||||||
ReturnShardState.SocialBackupExchangeResult.Success
|
|
||||||
success =
|
|
||||||
new ReturnShardState.SocialBackupExchangeResult.Success();
|
|
||||||
state.postValue(
|
|
||||||
new ReturnShardState.SocialBackupExchangeFinished(success));
|
|
||||||
} catch (ContactExistsException e) {
|
|
||||||
tryToClose(conn);
|
|
||||||
ReturnShardState.SocialBackupExchangeResult.Error
|
|
||||||
error = new ReturnShardState.SocialBackupExchangeResult.Error(
|
|
||||||
e.getRemoteAuthor());
|
|
||||||
state.postValue(
|
|
||||||
new ReturnShardState.SocialBackupExchangeFinished(error));
|
|
||||||
} catch (DbException | IOException e) {
|
|
||||||
tryToClose(conn);
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
ReturnShardState.SocialBackupExchangeResult.Error
|
|
||||||
error =
|
|
||||||
new ReturnShardState.SocialBackupExchangeResult.Error(null);
|
|
||||||
state.postValue(
|
|
||||||
new ReturnShardState.SocialBackupExchangeFinished(error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
|
||||||
@UiThread
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, -1);
|
|
||||||
LOG.info("Bluetooth scan mode changed: " + scanMode);
|
|
||||||
showQrCodeFragmentIfAllowed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn) {
|
|
||||||
try {
|
|
||||||
conn.getReader().dispose(true, true);
|
|
||||||
conn.getWriter().dispose(true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set to true in onPostResume() and false in onPause(). This prevents the
|
|
||||||
* QR code fragment from being shown if onRequestPermissionsResult() is
|
|
||||||
* called while the activity is paused, which could cause a crash due to
|
|
||||||
* https://issuetracker.google.com/issues/37067655.
|
|
||||||
* TODO check if this is still happening with new permission requesting
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void setIsActivityResumed(boolean resumed) {
|
|
||||||
isActivityResumed = resumed;
|
|
||||||
// Workaround for
|
|
||||||
// https://code.google.com/p/android/issues/detail?id=190966
|
|
||||||
showQrCodeFragmentIfAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void setBluetoothDecision(BluetoothDecision decision) {
|
|
||||||
bluetoothDecision = decision;
|
|
||||||
showQrCodeFragmentIfAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveEvent<Boolean> getCheckPermissions() {
|
|
||||||
return checkPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveEvent<Boolean> getRequestBluetoothDiscoverable() {
|
|
||||||
return requestBluetoothDiscoverable;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveEvent<Boolean> getShowQrCodeFragment() {
|
|
||||||
return showQrCodeFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveData<ReturnShardState> getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSending(boolean sending) {
|
|
||||||
this.sending = sending;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReturnShardPayload(ReturnShardPayload returnShardPayload) {
|
|
||||||
this.returnShardPayload = returnShardPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
QrCodeDecoder getQrCodeDecoder() {
|
|
||||||
return qrCodeDecoder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,17 @@
|
|||||||
android:paddingRight="@dimen/margin_large"
|
android:paddingRight="@dimen/margin_large"
|
||||||
android:paddingBottom="@dimen/margin_medium">
|
android:paddingBottom="@dimen/margin_medium">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/backup_done_dismiss"
|
||||||
|
android:textSize="24sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textView2" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView2"
|
android:id="@+id/textView2"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -675,13 +675,13 @@
|
|||||||
|
|
||||||
<!-- recovery from the secret owner's POV -->
|
<!-- recovery from the secret owner's POV -->
|
||||||
|
|
||||||
<string name="recovery_explainer">You need to meet your trusted contacts in-person to receive pieces by scanning QR codes</string>
|
<string name="recovery_explainer">You need to meet your trusted contacts in-person to receive pieces</string>
|
||||||
<string name="recovery_begin">Begin</string>
|
<string name="recovery_begin">Begin</string>
|
||||||
<string name="recovery_failed_to_receive">Failed to receive backup piece</string>
|
<string name="recovery_failed_to_receive">Failed to receive backup piece</string>
|
||||||
<string name="recovery_helpful_suggestions">Please check that bluetooth is swtiched on and that no-one but your trusted contact is able to scan the QR code</string>
|
<string name="recovery_helpful_suggestions">Please check that bluetooth is swtiched on and that no-one but your trusted contact is able to scan the QR code</string>
|
||||||
<string name="recovery_retry">Retry</string>
|
<string name="recovery_retry">Retry</string>
|
||||||
<string name="recovery_recovered_shards">Recovered backup pieces:</string>
|
<string name="recovery_recovered_shards">Recovered backup pieces:</string>
|
||||||
<string name="recovery_scan_qr_code">Scan QR code</string>
|
<string name="recovery_scan_qr_code">Show QR code</string>
|
||||||
<string name="recovery_recovering_account">Recovering account…</string>
|
<string name="recovery_recovering_account">Recovering account…</string>
|
||||||
<string name="recovery_shard_received">Account backup piece received</string>
|
<string name="recovery_shard_received">Account backup piece received</string>
|
||||||
<string name="recovery_account_recovered">Account recovered</string>
|
<string name="recovery_account_recovered">Account recovered</string>
|
||||||
|
|||||||
@@ -1,15 +1,34 @@
|
|||||||
package org.briarproject.briar.api.socialbackup;
|
package org.briarproject.briar.api.socialbackup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ReturnShardPayload {
|
public class ReturnShardPayload {
|
||||||
private final Shard shard;
|
private final Shard shard;
|
||||||
private final BackupPayload backupPayload;
|
private final BackupPayload backupPayload;
|
||||||
|
|
||||||
|
// TODO this does not belong here
|
||||||
|
private static Shard parseShardMessage(BdfList body) throws FormatException {
|
||||||
|
// Message type, secret ID, shard
|
||||||
|
byte[] secretId = body.getRaw(1);
|
||||||
|
byte[] shard = body.getRaw(2);
|
||||||
|
return new Shard(secretId, shard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReturnShardPayload fromList(BdfList body) throws FormatException {
|
||||||
|
checkSize(body, 2);
|
||||||
|
Shard shard = parseShardMessage(body.getList(0));
|
||||||
|
BackupPayload backupPayload = new BackupPayload(body.getRaw(1));
|
||||||
|
return new ReturnShardPayload(shard, backupPayload);
|
||||||
|
}
|
||||||
|
|
||||||
public ReturnShardPayload(Shard shard, BackupPayload backupPayload) {
|
public ReturnShardPayload(Shard shard, BackupPayload backupPayload) {
|
||||||
this.shard = shard;
|
this.shard = shard;
|
||||||
this.backupPayload = backupPayload;
|
this.backupPayload = backupPayload;
|
||||||
@@ -22,4 +41,9 @@ public class ReturnShardPayload {
|
|||||||
public BackupPayload getBackupPayload() {
|
public BackupPayload getBackupPayload() {
|
||||||
return backupPayload;
|
return backupPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals(ReturnShardPayload otherReturnShardPayload) {
|
||||||
|
return shard.equals(otherReturnShardPayload.getShard()) && backupPayload
|
||||||
|
.equals(otherReturnShardPayload.getBackupPayload());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.briarproject.briar.api.socialbackup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -22,4 +24,9 @@ public class Shard {
|
|||||||
public byte[] getShard() {
|
public byte[] getShard() {
|
||||||
return shard;
|
return shard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals(Shard otherShard) {
|
||||||
|
return Arrays.equals(secretId, otherShard.getSecretId()) &&
|
||||||
|
Arrays.equals(shard, otherShard.getShard());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.briarproject.briar.api.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface CustodianTask {
|
||||||
|
|
||||||
|
void start(Observer observer, byte[] payload);
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
void qrCodeDecoded(byte[] qrCodePayload);
|
||||||
|
|
||||||
|
interface Observer {
|
||||||
|
void onStateChanged(State state);
|
||||||
|
}
|
||||||
|
|
||||||
|
class State {
|
||||||
|
|
||||||
|
public static class Connecting extends State {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SendingShard extends State {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ReceivingAck extends State {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Success extends State {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Failure extends State {
|
||||||
|
|
||||||
|
public enum Reason {
|
||||||
|
QR_CODE_INVALID,
|
||||||
|
QR_CODE_TOO_OLD,
|
||||||
|
QR_CODE_TOO_NEW,
|
||||||
|
NO_CONNECTION,
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Reason reason;
|
||||||
|
|
||||||
|
public Failure(Reason reason) {
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reason getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.briarproject.briar.api.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface SecretOwnerTask {
|
||||||
|
|
||||||
|
void start(Observer observer, InetAddress inetAddress);
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
interface Observer {
|
||||||
|
void onStateChanged(State state);
|
||||||
|
}
|
||||||
|
|
||||||
|
class State {
|
||||||
|
|
||||||
|
public static class Listening extends State {
|
||||||
|
|
||||||
|
private final byte[] localPayload;
|
||||||
|
|
||||||
|
public Listening(byte[] localPayload) {
|
||||||
|
this.localPayload = localPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getLocalPayload() {
|
||||||
|
return localPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ReceivingShard extends State {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SendingAck extends State {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Success extends State {
|
||||||
|
private final ReturnShardPayload remotePayload;
|
||||||
|
|
||||||
|
public Success(ReturnShardPayload remotePayload) { this.remotePayload = remotePayload; }
|
||||||
|
|
||||||
|
public ReturnShardPayload getRemotePayload() { return remotePayload; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Failure extends State {
|
||||||
|
|
||||||
|
public enum Reason {
|
||||||
|
CANCELLED,
|
||||||
|
SECURITY,
|
||||||
|
NO_CONNECTION,
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Reason reason;
|
||||||
|
|
||||||
|
public Failure(Reason reason) {
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reason getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
|
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 java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface BackupPayloadDecoder {
|
||||||
|
SocialBackup decodeBackupPayload(
|
||||||
|
SecretKey secret,
|
||||||
|
BackupPayload backupPayload) throws FormatException,
|
||||||
|
GeneralSecurityException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||||
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
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 java.security.GeneralSecurityException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.AUTH_TAG_BYTES;
|
||||||
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.NONCE_BYTES;
|
||||||
|
|
||||||
|
public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private final Provider<AuthenticatedCipher> cipherProvider;
|
||||||
|
private final SecureRandom secureRandom;
|
||||||
|
private final MessageParser messageParser;
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(BackupPayloadDecoderImpl.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BackupPayloadDecoderImpl(ClientHelper clientHelper,
|
||||||
|
Provider<AuthenticatedCipher> cipherProvider,
|
||||||
|
SecureRandom secureRandom,
|
||||||
|
MessageParser messageParser) {
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
this.cipherProvider = cipherProvider;
|
||||||
|
this.secureRandom = secureRandom;
|
||||||
|
this.messageParser = messageParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocialBackup decodeBackupPayload(
|
||||||
|
SecretKey secret,
|
||||||
|
BackupPayload backupPayload)
|
||||||
|
throws FormatException, GeneralSecurityException {
|
||||||
|
|
||||||
|
byte[] ciphertextWithNonce = backupPayload.getBytes();
|
||||||
|
byte[] nonce = new byte[NONCE_BYTES];
|
||||||
|
System.arraycopy(ciphertextWithNonce, 0, nonce, 0, NONCE_BYTES);
|
||||||
|
|
||||||
|
byte[] ciphertext = new byte[ciphertextWithNonce.length - NONCE_BYTES];
|
||||||
|
System.arraycopy(ciphertextWithNonce, nonce.length, ciphertext, 0,
|
||||||
|
ciphertext.length);
|
||||||
|
|
||||||
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
|
cipher.init(false, secret, nonce);
|
||||||
|
byte[] plaintext =
|
||||||
|
new byte[ciphertext.length - AUTH_TAG_BYTES];
|
||||||
|
int decrypted = cipher.process(ciphertext, 0,
|
||||||
|
ciphertext.length, plaintext, 0);
|
||||||
|
if (decrypted != plaintext.length) throw new AssertionError();
|
||||||
|
LOG.info("Backup payload decrypted");
|
||||||
|
|
||||||
|
BdfList backup = clientHelper.toList(plaintext);
|
||||||
|
int version = backup.getLong(0).intValue();
|
||||||
|
LOG.info("Backup payload has version number " + version);
|
||||||
|
|
||||||
|
BdfList bdfIdentity = backup.getList(1);
|
||||||
|
BdfList bdfContactData = backup.getList(2);
|
||||||
|
|
||||||
|
Author a = clientHelper
|
||||||
|
.parseAndValidateAuthor(bdfIdentity.getList(0));
|
||||||
|
PrivateKey signaturePrivateKey =
|
||||||
|
new SignaturePrivateKey(bdfIdentity.getRaw(1));
|
||||||
|
LocalAuthor localAuthor =
|
||||||
|
new LocalAuthor(a.getId(), a.getFormatVersion(), a.getName(),
|
||||||
|
a.getPublicKey(), signaturePrivateKey);
|
||||||
|
LOG.info("LocalAuthor parsed successfully. Name is " + a.getName());
|
||||||
|
|
||||||
|
PublicKey handshakePublicKey =
|
||||||
|
new AgreementPublicKey(bdfIdentity.getRaw(2));
|
||||||
|
PrivateKey handShakePrivateKey =
|
||||||
|
new AgreementPrivateKey(bdfIdentity.getRaw(3));
|
||||||
|
|
||||||
|
Long created = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Identity identity = new Identity(localAuthor, handshakePublicKey,
|
||||||
|
handShakePrivateKey, created);
|
||||||
|
LOG.info("New identity created");
|
||||||
|
|
||||||
|
List<ContactData> contactDataList = new ArrayList();
|
||||||
|
|
||||||
|
for (int i = 0; i < bdfContactData.size(); i++) {
|
||||||
|
BdfList bdfData = bdfContactData.getList(i);
|
||||||
|
|
||||||
|
Author author =
|
||||||
|
clientHelper.parseAndValidateAuthor(bdfData.getList(0));
|
||||||
|
LOG.info("Contact author parsed");
|
||||||
|
|
||||||
|
String alias = bdfData.getOptionalString(1);
|
||||||
|
LOG.info("Contact alias is: " + alias);
|
||||||
|
|
||||||
|
// 2 - public key or null
|
||||||
|
byte[] handshakePublicKeyBytes = bdfData.getOptionalRaw(2);
|
||||||
|
PublicKey contactHandshakePublicKey = (handshakePublicKeyBytes == null)
|
||||||
|
? null
|
||||||
|
: new AgreementPublicKey(handshakePublicKeyBytes);
|
||||||
|
LOG.info("Contact handshake pk parsed");
|
||||||
|
|
||||||
|
// 3 - properties dictionary
|
||||||
|
Map<TransportId, TransportProperties> properties = clientHelper
|
||||||
|
.parseAndValidateTransportPropertiesMap(
|
||||||
|
bdfData.getDictionary(3));
|
||||||
|
LOG.info("Contact transport properties parsed");
|
||||||
|
|
||||||
|
// 4 shard or null
|
||||||
|
BdfList shardList = bdfData.getOptionalList(4);
|
||||||
|
Shard shard = (shardList == null) ? null :
|
||||||
|
messageParser.parseShardMessage(shardList);
|
||||||
|
// TODO validate shard
|
||||||
|
LOG.info("Contact shard parsed");
|
||||||
|
|
||||||
|
ContactId contactId = new ContactId(i);
|
||||||
|
Contact contact =
|
||||||
|
new Contact(contactId, author, author.getId(), alias,
|
||||||
|
contactHandshakePublicKey, false);
|
||||||
|
ContactData contactData =
|
||||||
|
new ContactData(contact, properties, shard);
|
||||||
|
contactDataList.add(contactData);
|
||||||
|
LOG.info("Contact added");
|
||||||
|
}
|
||||||
|
LOG.info("All contacts added");
|
||||||
|
return new SocialBackup(identity, contactDataList, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,7 +84,10 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
|
|||||||
int encrypted = cipher.process(plaintext, 0, plaintext.length,
|
int encrypted = cipher.process(plaintext, 0, plaintext.length,
|
||||||
ciphertext, 0);
|
ciphertext, 0);
|
||||||
if (encrypted != ciphertext.length) throw new AssertionError();
|
if (encrypted != ciphertext.length) throw new AssertionError();
|
||||||
return new org.briarproject.briar.api.socialbackup.BackupPayload(ciphertext);
|
byte[] ciphertextWithNonce = new byte[ciphertext.length + nonce.length];
|
||||||
|
System.arraycopy(nonce, 0, ciphertextWithNonce, 0, nonce.length);
|
||||||
|
System.arraycopy(ciphertext, 0, ciphertextWithNonce, nonce.length, ciphertext.length);
|
||||||
|
return new org.briarproject.briar.api.socialbackup.BackupPayload(ciphertextWithNonce);
|
||||||
} catch (FormatException | GeneralSecurityException e) {
|
} catch (FormatException | GeneralSecurityException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SocialBackup {
|
||||||
|
private Identity identity;
|
||||||
|
private List<ContactData> contacts;
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
SocialBackup (Identity identity, List<ContactData> contacts, int version) {
|
||||||
|
this.identity = identity;
|
||||||
|
this.contacts = contacts;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Identity getIdentity() {
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ContactData> getContacts() {
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,10 @@ import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
|||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
|
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
|
||||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
import org.briarproject.briar.socialbackup.recovery.CustodianTaskImpl;
|
||||||
|
import org.briarproject.briar.socialbackup.recovery.SecretOwnerTaskImpl;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -83,17 +87,36 @@ public class SocialBackupModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
org.briarproject.briar.api.socialbackup.MessageEncoder messageEncoder(MessageEncoderImpl messageEncoder) {
|
BackupPayloadDecoder backupPayloadDecoder(
|
||||||
|
BackupPayloadDecoderImpl backupPayloadDecoder) {
|
||||||
|
return backupPayloadDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
org.briarproject.briar.api.socialbackup.MessageEncoder messageEncoder(
|
||||||
|
MessageEncoderImpl messageEncoder) {
|
||||||
return messageEncoder;
|
return messageEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
org.briarproject.briar.api.socialbackup.MessageParser messageParser(MessageParserImpl messageParser) {
|
org.briarproject.briar.api.socialbackup.MessageParser messageParser(
|
||||||
|
MessageParserImpl messageParser) {
|
||||||
return messageParser;
|
return messageParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
SocialBackupExchangeManager socialBackupExchangeManager(SocialBackupExchangeManagerImpl socialBackupExchangeManager) {
|
SocialBackupExchangeManager socialBackupExchangeManager(
|
||||||
|
SocialBackupExchangeManagerImpl socialBackupExchangeManager) {
|
||||||
return socialBackupExchangeManager;
|
return socialBackupExchangeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
SecretOwnerTask secretOwnerTask(SecretOwnerTaskImpl secretOwnerTask) {
|
||||||
|
return secretOwnerTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
CustodianTask custodianTask(CustodianTaskImpl custodianTask) {
|
||||||
|
return custodianTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package org.briarproject.briar.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
public class CustodianTaskImpl extends ReturnShardTaskImpl
|
||||||
|
implements CustodianTask {
|
||||||
|
|
||||||
|
private boolean cancelled = false;
|
||||||
|
private Observer observer;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private InetSocketAddress remoteSocketAddress;
|
||||||
|
private Socket socket;
|
||||||
|
private final AuthenticatedCipher cipher;
|
||||||
|
private byte[] payload;
|
||||||
|
// private final StreamReaderFactory streamReaderFactory;
|
||||||
|
// private final StreamWriterFactory streamWriterFactory;
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(CustodianTaskImpl.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CustodianTaskImpl(CryptoComponent crypto, ClientHelper clientHelper,
|
||||||
|
AuthenticatedCipher cipher, StreamReaderFactory streamReaderFactory,
|
||||||
|
StreamWriterFactory streamWriterFactory) {
|
||||||
|
super(cipher, crypto);
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
// this.streamReaderFactory = streamReaderFactory;
|
||||||
|
// this.streamWriterFactory = streamWriterFactory;
|
||||||
|
|
||||||
|
this.cipher = cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Observer observer, byte[] payload) {
|
||||||
|
this.observer = observer;
|
||||||
|
this.payload = payload;
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Connecting());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
cancelled = true;
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// The reason here is OTHER rather than NO_CONNECTION because
|
||||||
|
// the socket could fail to close because it is already closed
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.OTHER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (observer != null) {
|
||||||
|
observer.onStateChanged(
|
||||||
|
new CustodianTask.State.Failure(State.Failure.Reason.OTHER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void qrCodeDecoded(byte[] qrCodePayloadRaw) {
|
||||||
|
try {
|
||||||
|
BdfList qrCodePayload = clientHelper.toList(qrCodePayloadRaw);
|
||||||
|
AgreementPublicKey remotePublicKey =
|
||||||
|
new AgreementPublicKey(qrCodePayload.getRaw(0));
|
||||||
|
byte[] addressRaw = qrCodePayload.getRaw(1);
|
||||||
|
int port = qrCodePayload.getLong(2).intValue();
|
||||||
|
remoteSocketAddress =
|
||||||
|
new InetSocketAddress(InetAddress.getByAddress(addressRaw),
|
||||||
|
port);
|
||||||
|
deriveSharedSecret(remotePublicKey, addressRaw);
|
||||||
|
|
||||||
|
LOG.info("Qr code payload parsed successfully");
|
||||||
|
} catch (Exception e) {
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.QR_CODE_INVALID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connectAndSendShard();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectAndSendShard() {
|
||||||
|
observer.onStateChanged(new CustodianTask.State.SendingShard());
|
||||||
|
try {
|
||||||
|
LOG.info("Connecting to secret owner " + remoteSocketAddress);
|
||||||
|
socket = new Socket();
|
||||||
|
socket.connect(remoteSocketAddress, TIMEOUT);
|
||||||
|
LOG.info("Connected to secret owner " + remoteSocketAddress);
|
||||||
|
|
||||||
|
OutputStream outputStream = socket.getOutputStream();
|
||||||
|
|
||||||
|
byte[] payloadNonce = generateNonce();
|
||||||
|
|
||||||
|
byte[] payloadEncrypted = encrypt(payload, payloadNonce);
|
||||||
|
outputStream.write(localKeyPair.getPublic().getEncoded());
|
||||||
|
outputStream.write(payloadNonce);
|
||||||
|
outputStream.write(ByteBuffer.allocate(4)
|
||||||
|
.putInt(payloadEncrypted.length)
|
||||||
|
.array());
|
||||||
|
LOG.info("Written payload header");
|
||||||
|
|
||||||
|
outputStream.write(payloadEncrypted);
|
||||||
|
|
||||||
|
// OutputStream encryptedOutputStream = streamWriterFactory
|
||||||
|
// .createContactExchangeStreamWriter(outputStream,
|
||||||
|
// sharedSecret).getOutputStream();
|
||||||
|
// encryptedOutputStream.write(payload);
|
||||||
|
|
||||||
|
LOG.info("Written payload");
|
||||||
|
|
||||||
|
observer.onStateChanged(new CustodianTask.State.ReceivingAck());
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (e instanceof SocketTimeoutException) {
|
||||||
|
LOG.warning("Timed out connecting to secret owner");
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.NO_CONNECTION));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.warning("IO Error connecting to secret owner " + e.getMessage());
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.QR_CODE_INVALID));
|
||||||
|
return;
|
||||||
|
// }
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
LOG.warning("Security error "+ e.getMessage());
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.OTHER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
receiveAck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receiveAck() {
|
||||||
|
try {
|
||||||
|
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
|
||||||
|
// InputStream inputStream = streamReaderFactory
|
||||||
|
// .createContactExchangeStreamReader(socket.getInputStream(),
|
||||||
|
// sharedSecret);
|
||||||
|
byte[] ackNonce = read(inputStream, NONCE_LENGTH);
|
||||||
|
byte[] ackMessageEncrypted =
|
||||||
|
read(inputStream, 3 + cipher.getMacBytes());
|
||||||
|
byte[] ackMessage = decrypt(ackMessageEncrypted, ackNonce);
|
||||||
|
String ackMessageString = new String(ackMessage);
|
||||||
|
LOG.info("Received ack message: " + new String(ackMessage));
|
||||||
|
if (!ackMessageString.equals("ack"))
|
||||||
|
throw new GeneralSecurityException("Bad ack message");
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Success());
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warning("IO Error reading ack " + e.getMessage());
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.QR_CODE_INVALID));
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
LOG.warning("Security Error reading ack " + e.getMessage());
|
||||||
|
observer.onStateChanged(new CustodianTask.State.Failure(
|
||||||
|
State.Failure.Reason.OTHER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.briarproject.briar.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class ReturnShardTaskImpl {
|
||||||
|
private final AuthenticatedCipher cipher;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final SecureRandom secureRandom;
|
||||||
|
final int PORT = 3002;
|
||||||
|
final int TIMEOUT = 120 * 1000;
|
||||||
|
final int NONCE_LENGTH = 24; // TODO get these constants
|
||||||
|
final int AGREEMENT_PUBLIC_KEY_LENGTH = 32;
|
||||||
|
SecretKey sharedSecret;
|
||||||
|
final KeyPair localKeyPair;
|
||||||
|
|
||||||
|
ReturnShardTaskImpl(AuthenticatedCipher cipher, CryptoComponent crypto) {
|
||||||
|
this.cipher = cipher;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.secureRandom = crypto.getSecureRandom();
|
||||||
|
localKeyPair = crypto.generateAgreementKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] generateNonce() {
|
||||||
|
byte[] nonce = new byte[NONCE_LENGTH];
|
||||||
|
secureRandom.nextBytes(nonce);
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deriveSharedSecret(AgreementPublicKey remotePublicKey, byte[] context)
|
||||||
|
throws
|
||||||
|
GeneralSecurityException {
|
||||||
|
sharedSecret =
|
||||||
|
crypto.deriveSharedSecret("ShardReturn", remotePublicKey,
|
||||||
|
localKeyPair, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encrypt(byte[] message, byte[] nonce)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
cipher.init(true, sharedSecret, nonce);
|
||||||
|
byte[] cipherText = new byte[message.length + cipher.getMacBytes()];
|
||||||
|
cipher.process(message, 0, message.length, cipherText, 0);
|
||||||
|
return cipherText;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] decrypt(byte[] cipherText, byte[] nonce)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
cipher.init(false, sharedSecret, nonce);
|
||||||
|
byte[] message = new byte[cipherText.length - cipher.getMacBytes()];
|
||||||
|
cipher.process(cipherText, 0, cipherText.length, message, 0);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] read(DataInputStream dis, int length)
|
||||||
|
throws IOException {
|
||||||
|
byte[] output = new byte[length];
|
||||||
|
dis.readFully(output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package org.briarproject.briar.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.briar.api.socialbackup.MessageParser;
|
||||||
|
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
public class SecretOwnerTaskImpl extends ReturnShardTaskImpl
|
||||||
|
implements SecretOwnerTask {
|
||||||
|
|
||||||
|
private boolean cancelled = false;
|
||||||
|
private InetSocketAddress socketAddress;
|
||||||
|
private ClientHelper clientHelper;
|
||||||
|
private Observer observer;
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private Socket socket;
|
||||||
|
private MessageParser messageParser;
|
||||||
|
|
||||||
|
// private final StreamReaderFactory streamReaderFactory;
|
||||||
|
// private final StreamWriterFactory streamWriterFactory;
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(SecretOwnerTaskImpl.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SecretOwnerTaskImpl(AuthenticatedCipher cipher, CryptoComponent crypto,
|
||||||
|
ClientHelper clientHelper) {
|
||||||
|
super(cipher, crypto);
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
// this.streamReaderFactory = streamReaderFactory;
|
||||||
|
// this.streamWriterFactory = streamWriterFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Observer observer, InetAddress inetAddress) {
|
||||||
|
this.observer = observer;
|
||||||
|
if (inetAddress == null) {
|
||||||
|
LOG.warning("Cannot retrieve local IP address, failing.");
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.NO_CONNECTION));
|
||||||
|
}
|
||||||
|
LOG.info("InetAddress is " + inetAddress);
|
||||||
|
socketAddress = new InetSocketAddress(inetAddress, PORT);
|
||||||
|
|
||||||
|
// If we have a socket already open, close it and start fresh
|
||||||
|
if (serverSocket != null) {
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening on socketAddress
|
||||||
|
try {
|
||||||
|
LOG.info("Binding socket");
|
||||||
|
serverSocket = new ServerSocket();
|
||||||
|
serverSocket.bind(socketAddress);
|
||||||
|
LOG.info("Binding socket done");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warning(
|
||||||
|
"IO Error when listening on local socket" + e.getMessage());
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.NO_CONNECTION));
|
||||||
|
// TODO could try incrementing the port number
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO add version number
|
||||||
|
BdfList payloadList = new BdfList();
|
||||||
|
payloadList.add(localKeyPair.getPublic().getEncoded());
|
||||||
|
payloadList.add(socketAddress.getAddress().getAddress());
|
||||||
|
payloadList.add(socketAddress.getPort());
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Listening(clientHelper.toByteArray(payloadList)));
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.warning("Error encoding QR code");
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.OTHER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.info("Receiving payload");
|
||||||
|
receivePayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receivePayload() {
|
||||||
|
try {
|
||||||
|
LOG.info("Waiting for a connection...");
|
||||||
|
socket = serverSocket.accept();
|
||||||
|
LOG.info("Client connected");
|
||||||
|
observer.onStateChanged(new State.ReceivingShard());
|
||||||
|
|
||||||
|
DataInputStream inputStream =
|
||||||
|
new DataInputStream(socket.getInputStream());
|
||||||
|
|
||||||
|
AgreementPublicKey remotePublicKey =
|
||||||
|
new AgreementPublicKey(
|
||||||
|
read(inputStream, AGREEMENT_PUBLIC_KEY_LENGTH));
|
||||||
|
LOG.info("Read remote public key");
|
||||||
|
|
||||||
|
byte[] addressRaw = socketAddress.getAddress().getAddress();
|
||||||
|
deriveSharedSecret(remotePublicKey, addressRaw);
|
||||||
|
|
||||||
|
byte[] payloadNonce = read(inputStream, NONCE_LENGTH);
|
||||||
|
LOG.info("Read payload nonce");
|
||||||
|
|
||||||
|
byte[] payloadLengthRaw = read(inputStream, 4);
|
||||||
|
int payloadLength = ByteBuffer.wrap(payloadLengthRaw).getInt();
|
||||||
|
LOG.info("Expected payload length " + payloadLength + " bytes");
|
||||||
|
|
||||||
|
byte[] payloadRaw = read(inputStream, payloadLength);
|
||||||
|
|
||||||
|
// InputStream clearInputStream = streamReaderFactory.createContactExchangeStreamReader(inputStream, sharedSecret);
|
||||||
|
|
||||||
|
// byte[] payloadClear = read(clearInputStream, payloadLength);
|
||||||
|
byte[] payloadClear = decrypt(payloadRaw, payloadNonce);
|
||||||
|
ReturnShardPayload returnShardPayload = ReturnShardPayload
|
||||||
|
.fromList(clientHelper.toList(payloadClear));
|
||||||
|
|
||||||
|
LOG.info("Payload decrypted and parsed successfully");
|
||||||
|
|
||||||
|
// StreamWriter streamWriter = streamWriterFactory.createContactExchangeStreamWriter(socket.getOutputStream(), sharedSecret);
|
||||||
|
// OutputStream outputStream = streamWriter.getOutputStream();
|
||||||
|
|
||||||
|
DataOutputStream outputStream =
|
||||||
|
new DataOutputStream(socket.getOutputStream());
|
||||||
|
byte[] ackNonce = generateNonce();
|
||||||
|
outputStream.write(ackNonce);
|
||||||
|
|
||||||
|
byte[] ackMessage = encrypt("ack".getBytes(), ackNonce);
|
||||||
|
outputStream.write(ackMessage);
|
||||||
|
LOG.info("Acknowledgement sent");
|
||||||
|
|
||||||
|
serverSocket.close();
|
||||||
|
|
||||||
|
observer.onStateChanged(new State.Success(returnShardPayload));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warning("IO Error receiving payload " + e.getMessage());
|
||||||
|
// TODO reasons
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.NO_CONNECTION));
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
LOG.warning("Security Error receiving payload " + e.getMessage());
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.SECURITY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
cancelled = true;
|
||||||
|
LOG.info("Cancel called, failing...");
|
||||||
|
if (serverSocket != null) {
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.OTHER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer != null) {
|
||||||
|
observer.onStateChanged(
|
||||||
|
new State.Failure(State.Failure.Reason.OTHER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package org.briarproject.briar.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||||
|
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.ReturnShardPayload;
|
||||||
|
import org.briarproject.briar.api.socialbackup.Shard;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.fail;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class ReturnShardIntegrationTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
private final File testDir = getTestDirectory();
|
||||||
|
private final File ownerDir = new File(testDir, "owner");
|
||||||
|
private final File custodianDir = new File(testDir, "custodian");
|
||||||
|
|
||||||
|
private ReturnShardIntegrationTestComponent owner, custodian;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
// Create the devices
|
||||||
|
owner = DaggerReturnShardIntegrationTestComponent.builder()
|
||||||
|
.testDatabaseConfigModule(
|
||||||
|
new TestDatabaseConfigModule(ownerDir)).build();
|
||||||
|
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||||
|
.injectEagerSingletons(owner);
|
||||||
|
custodian = DaggerReturnShardIntegrationTestComponent.builder()
|
||||||
|
.testDatabaseConfigModule(new TestDatabaseConfigModule(
|
||||||
|
custodianDir))
|
||||||
|
.build();
|
||||||
|
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||||
|
.injectEagerSingletons(custodian);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnShard() {
|
||||||
|
SecretOwnerTask secretOwnerTask = owner.getSecretOwnerTask();
|
||||||
|
CustodianTask custodianTask = custodian.getCustodianTask();
|
||||||
|
byte[] payload = "its nice to be important but its more important to be nice".getBytes();
|
||||||
|
|
||||||
|
Shard shard = new Shard("secretid".getBytes(), "shard".getBytes());
|
||||||
|
BackupPayload backupPayload = new BackupPayload("backup payload".getBytes());
|
||||||
|
ReturnShardPayload returnShardPayload = new ReturnShardPayload(shard, backupPayload);
|
||||||
|
|
||||||
|
// payloadBytes = clientHelper
|
||||||
|
|
||||||
|
SecretOwnerTask.Observer ownerObserver =
|
||||||
|
state -> {
|
||||||
|
if (state instanceof SecretOwnerTask.State.Listening) {
|
||||||
|
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");
|
||||||
|
} 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);
|
||||||
|
} else if (state instanceof CustodianTask.State.Failure) {
|
||||||
|
fail();
|
||||||
|
} else {
|
||||||
|
System.out.println(
|
||||||
|
"custodian: " + state.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
owner.getIoExecutor().execute(() -> {
|
||||||
|
try {
|
||||||
|
secretOwnerTask
|
||||||
|
.start(ownerObserver, InetAddress.getLocalHost());
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
custodian.getIoExecutor().execute(() -> {
|
||||||
|
try {
|
||||||
|
custodianTask.start(custodianObserver, payload);
|
||||||
|
} 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transferQrCode(CustodianTask custodianTask, byte[] payload) {
|
||||||
|
custodian.getIoExecutor().execute(() -> {
|
||||||
|
try {
|
||||||
|
custodianTask.qrCodeDecoded(payload);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tearDown(ReturnShardIntegrationTestComponent device)
|
||||||
|
throws Exception {
|
||||||
|
// Stop the lifecycle manager
|
||||||
|
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||||
|
lifecycleManager.stopServices();
|
||||||
|
lifecycleManager.waitForShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
tearDown(owner);
|
||||||
|
tearDown(custodian);
|
||||||
|
deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.briar.socialbackup.recovery;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||||
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
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.recovery.CustodianTask;
|
||||||
|
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||||
|
import org.briarproject.briar.socialbackup.SocialBackupModule;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {
|
||||||
|
BrambleCoreIntegrationTestModule.class,
|
||||||
|
BrambleCoreModule.class,
|
||||||
|
SocialBackupModule.class
|
||||||
|
})
|
||||||
|
interface ReturnShardIntegrationTestComponent
|
||||||
|
extends BrambleCoreIntegrationTestEagerSingletons {
|
||||||
|
|
||||||
|
ConnectionManager getConnectionManager();
|
||||||
|
|
||||||
|
ContactExchangeManager getContactExchangeManager();
|
||||||
|
|
||||||
|
ContactManager getContactManager();
|
||||||
|
|
||||||
|
EventBus getEventBus();
|
||||||
|
|
||||||
|
IdentityManager getIdentityManager();
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
Executor getIoExecutor();
|
||||||
|
|
||||||
|
LifecycleManager getLifecycleManager();
|
||||||
|
|
||||||
|
SecretOwnerTask getSecretOwnerTask();
|
||||||
|
|
||||||
|
CustodianTask getCustodianTask();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user