diff --git a/app/src/main/java/lu/circl/mispbump/SyncActivity.java b/app/src/main/java/lu/circl/mispbump/SyncActivity.java index e91a192..2097ac2 100644 --- a/app/src/main/java/lu/circl/mispbump/SyncActivity.java +++ b/app/src/main/java/lu/circl/mispbump/SyncActivity.java @@ -16,16 +16,17 @@ import android.widget.Toast; import com.google.gson.Gson; import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.List; +import lu.circl.mispbump.auxiliary.DialogManager; import lu.circl.mispbump.auxiliary.PreferenceManager; import lu.circl.mispbump.auxiliary.QrCodeGenerator; import lu.circl.mispbump.auxiliary.RandomString; import lu.circl.mispbump.cam.CameraFragment; import lu.circl.mispbump.restful_client.MispRestClient; import lu.circl.mispbump.restful_client.MispServer; -import lu.circl.mispbump.restful_client.MispUser; import lu.circl.mispbump.restful_client.Organisation; import lu.circl.mispbump.restful_client.Server; import lu.circl.mispbump.restful_client.User; @@ -52,11 +53,85 @@ public class SyncActivity extends AppCompatActivity { private FloatingActionButton continueButton; private SyncState currentSyncState = SyncState.publicKeyExchange; + private View.OnClickListener onContinueClicked = new View.OnClickListener() { + @Override + public void onClick(View v) { - private enum SyncState { - publicKeyExchange, - dataExchange - } + cameraFragment.setReadQrEnabled(false); + + switch (currentSyncState) { + case publicKeyExchange: + + DialogManager.confirmProceedDialog(SyncActivity.this, + new DialogManager.IDialogFeedback() { + @Override + public void positive() { + currentSyncState = SyncState.dataExchange; + continueButton.hide(); + cameraFragment.setReadQrEnabled(true); + showInformationQr(); + } + + @Override + public void negative() { + // do nothing, just wait + } + }); + break; + + case dataExchange: + // TODO upload + break; + } + } + }; + /** + * Callback for the camera fragment. + * Delivers the content of a scanned QR code. + */ + private CameraFragment.QrScanCallback onQrCodeScanned = new CameraFragment.QrScanCallback() { + @Override + public void qrScanResult(String qrData) { + cameraFragment.setReadQrEnabled(false); + + switch (currentSyncState) { + case publicKeyExchange: + try { + final PublicKey pk = AESSecurity.publicKeyFromString(qrData); + DialogManager.publicKeyDialog(pk.toString(), SyncActivity.this, + new DialogManager.IDialogFeedback() { + @Override + public void positive() { + aesSecurity.setForeignPublicKey(pk); + continueButton.show(); + } + + @Override + public void negative() { + // enable qr read again to scan another pk + cameraFragment.setReadQrEnabled(true); + } + }); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + MakeToast("Invalid key"); + } + + break; + + case dataExchange: + // disable qr read + cameraFragment.setReadQrEnabled(false); + + String data = aesSecurity.decrypt(qrData); + SyncInformation info = new Gson().fromJson(data, SyncInformation.class); + + Log.i(TAG, info.organisation.toString()); + Log.i(TAG, info.user.toString()); + + break; + } + } + }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -82,66 +157,6 @@ public class SyncActivity extends AppCompatActivity { enableSyncOptionsFragment(); } - private View.OnClickListener onContinueClicked = new View.OnClickListener() { - @Override - public void onClick(View v) { - switch (currentSyncState) { - case publicKeyExchange: - currentSyncState = SyncState.dataExchange; - showInformationQr(); - continueButton.hide(); - cameraFragment.setReadQrEnabled(true); - break; - - case dataExchange: - // TODO upload - break; - } - } - }; - - /** - * Callback for the camera fragment. - * Delivers the content of a scanned QR code. - */ - private CameraFragment.QrScanCallback onQrCodeScanned = new CameraFragment.QrScanCallback() { - @Override - public void qrScanResult(String qrData) { - switch (currentSyncState) { - case publicKeyExchange: - try { - aesSecurity.setForeignPublicKey(AESSecurity.publicKeyFromString(qrData)); - cameraFragment.setReadQrEnabled(false); - - runOnUiThread(new Runnable() { - @Override - public void run() { - continueButton.show(); - } - }); - } catch (NoSuchAlgorithmException e) { - MakeToast("Something gone wrong while parsing the key (no such algorithm)"); - } catch (InvalidKeySpecException e) { - MakeToast("Something gone wrong while parsing the key (invalid key spec)"); - } - - break; - - case dataExchange: - // disable qr read - cameraFragment.setReadQrEnabled(false); - - String data = aesSecurity.decrypt(qrData); - SyncInformation info = new Gson().fromJson(data, SyncInformation.class); - - Log.i(TAG, info.organisation.toString()); - Log.i(TAG, info.user.toString()); - - break; - } - } - }; - /** * Creates the camera fragment used to scan the QR codes. */ @@ -197,7 +212,6 @@ public class SyncActivity extends AppCompatActivity { // my organisation Organisation org = preferenceManager.getUserOrganisation(); User user = preferenceManager.getUserInfo(); - MispUser mispUser = new MispUser(user); Server server = new Server( "SyncServer for " + org.name, @@ -278,6 +292,11 @@ public class SyncActivity extends AppCompatActivity { }); } + private enum SyncState { + publicKeyExchange, + dataExchange + } + // private View.OnClickListener onGetServers = new View.OnClickListener() { // @Override // public void onClick(View v) { diff --git a/app/src/main/java/lu/circl/mispbump/SyncInformation.java b/app/src/main/java/lu/circl/mispbump/SyncInformation.java new file mode 100644 index 0000000..f3e8d6e --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/SyncInformation.java @@ -0,0 +1,22 @@ +package lu.circl.mispbump; + +import lu.circl.mispbump.restful_client.Organisation; +import lu.circl.mispbump.restful_client.Server; +import lu.circl.mispbump.restful_client.User; + +/** + * A Class that holds the information needed synchronize two misp instances. + * This class can be serialized and passed via QR code. + */ +public class SyncInformation { + + public User user; + public Organisation organisation; + public Server server; + + SyncInformation(User user, Organisation organisation, Server server) { + this.user = user; + this.organisation = organisation; + this.server = server; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java b/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java new file mode 100644 index 0000000..08aa6ea --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java @@ -0,0 +1,88 @@ +package lu.circl.mispbump.auxiliary; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; + +/** + * Creates and show dialogs. + * Automatically takes care of using the UI Thread. + */ +public class DialogManager { + + /** + * Interface to give feedback about the user choice in dialogs. + */ + public interface IDialogFeedback { + void positive(); + void negative(); + } + + /** + * Dialog to display a received public key. + * @param publicKey the public key to display + * @param context needed to build and show the dialog + * @param callback {@link IDialogFeedback} + */ + public static void publicKeyDialog(String publicKey, Context context, final IDialogFeedback callback) { + final AlertDialog.Builder adb = new AlertDialog.Builder(context); + adb.setTitle("Public Key Received"); + adb.setMessage(publicKey); + adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + callback.positive(); + } + }); + + adb.setNegativeButton("Reject", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + callback.negative(); + } + }); + + Activity act = (Activity) context; + + act.runOnUiThread(new Runnable() { + @Override + public void run() { + adb.create().show(); + } + }); + } + + /** + * Dialog to ask the user if his sync partner already scanned the displayed qr code. + * @param context needed to build and show the dialog + * @param callback {@link IDialogFeedback} + */ + public static void confirmProceedDialog(Context context, final IDialogFeedback callback) { + final AlertDialog.Builder adb = new AlertDialog.Builder(context); + adb.setTitle("Really continue?"); + adb.setMessage("Was this QR Code already scanned by your partner?"); + adb.setPositiveButton("Yes, continue", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + callback.positive(); + } + }); + + adb.setNegativeButton("No, show QR Code", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + callback.negative(); + } + }); + + Activity act = (Activity) context; + + act.runOnUiThread(new Runnable() { + @Override + public void run() { + adb.create().show(); + } + }); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/RandomString.java b/app/src/main/java/lu/circl/mispbump/auxiliary/RandomString.java new file mode 100644 index 0000000..a0dc3d9 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/RandomString.java @@ -0,0 +1,46 @@ +package lu.circl.mispbump.auxiliary; +import java.security.SecureRandom; +import java.util.Locale; +import java.util.Objects; +import java.util.Random; + +public class RandomString { + @SuppressWarnings("SpellCheckingInspection") + private static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String lower = upper.toLowerCase(Locale.ROOT); + private static final String digits = "0123456789"; + private static final String alphaNum = upper + lower + digits; + + private final Random random; + private final char[] symbols; + private final char[] buf; + + public RandomString(int length) { + this(length, new SecureRandom()); + } + + RandomString(int length, Random random) { + this(length, random, alphaNum); + } + + RandomString(int length, Random random, String symbols) { + if (length < 1) { + throw new IllegalArgumentException(); + } + if (symbols.length() < 2) { + throw new IllegalArgumentException(); + } + + this.random = Objects.requireNonNull(random); + this.symbols = symbols.toCharArray(); + this.buf = new char[length]; + } + + public String nextString() { + for (int idx = 0; idx < buf.length; ++idx) { + buf[idx] = symbols[random.nextInt(symbols.length)]; + } + + return new String(buf); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java b/app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java index 5979a57..209d844 100644 --- a/app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java +++ b/app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java @@ -61,61 +61,49 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest } void addToQueue(Bitmap bitmap) { - - if (lastAccessedIndex == 10) { - lastAccessedIndex = 0; - } - processQueue[lastAccessedIndex] = bitmap; - lastAccessedIndex++; + // circular array access + lastAccessedIndex = (lastAccessedIndex + 1) % processQueue.length; } @Override public void run() { while (isRunning) { + // no need to process further images if (!readQrEnabled) { continue; } - int usedSlots = 0; - for (int i = 0; i < processQueue.length; i++) { + // queue position already processed or not in use if (processQueue[i] == null) { continue; } - usedSlots++; - - SparseArray barcodes = barcodeDetector.detect(new Frame.Builder().setBitmap(processQueue[i]).build()); + // analyze image for qr codes + SparseArray barcodes = barcodeDetector.detect( + new Frame.Builder().setBitmap(processQueue[i]).build() + ); + // does the frame contain any qr code? if (barcodes.size() > 0) { - if (qrResultCallback != null) { - + if (readQrEnabled) { qrResultCallback.qrScanResult(barcodes.valueAt(0).rawValue); - - try { - sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - } else { - Log.i(TAG, "QrScanCallback not attached"); } } + // set buffer entry as processed processQueue[i] = null; } + // sleep between analysis of buffer (-25% cpu usage) try { sleep(250); } catch (InterruptedException e) { e.printStackTrace(); } - - Log.i(TAG, "slots in use: " + usedSlots); } } } @@ -718,7 +706,6 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest } - /** * Shows an error message dialog. */