Merge branch '2397-wrong-type-of-qr-code' into 'master'

Show appropriate error message if user scans wrong kind of QR code

Closes #2397

See merge request briar/briar!1748
This commit is contained in:
akwizgran
2022-12-19 15:43:16 +00:00
37 changed files with 620 additions and 233 deletions

View File

@@ -1,18 +1,26 @@
package org.briarproject.bramble.api.keyagreement; package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants { import org.briarproject.bramble.api.mailbox.MailboxConstants;
/** public interface KeyAgreementConstants {
* The version of the BQP protocol used in beta releases. This version
* number is reserved.
*/
byte BETA_PROTOCOL_VERSION = 89;
/** /**
* The current version of the BQP protocol. * The current version of the BQP protocol.
*/ */
byte PROTOCOL_VERSION = 4; byte PROTOCOL_VERSION = 4;
/**
* The QR code format identifier, used to distinguish BQP QR codes from
* QR codes used for other purposes. See
* {@link MailboxConstants#QR_FORMAT_ID}.
*/
byte QR_FORMAT_ID = 0;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = PROTOCOL_VERSION;
/** /**
* The length of the BQP key commitment in bytes. * The length of the BQP key commitment in bytes.
*/ */

View File

@@ -7,5 +7,5 @@ import java.io.IOException;
@NotNullByDefault @NotNullByDefault
public interface PayloadParser { public interface PayloadParser {
Payload parse(byte[] raw) throws IOException; Payload parse(String payload) throws IOException;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List; import java.util.List;
@@ -19,6 +20,18 @@ public interface MailboxConstants {
*/ */
TransportId ID = new TransportId("org.briarproject.bramble.mailbox"); TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
/**
* The QR code format identifier, used to distinguish mailbox QR codes
* from QR codes used for other purposes. See
* {@link KeyAgreementConstants#QR_FORMAT_ID};
*/
byte QR_FORMAT_ID = 1;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = 0;
/** /**
* Mailbox API versions that we support as a client. This is reported to our * Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}. * contacts by {@link MailboxUpdateManager}.

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
public abstract class MailboxPairingState { public abstract class MailboxPairingState {
public abstract static class Pending extends MailboxPairingState { public abstract static class Pending extends MailboxPairingState {
@@ -29,6 +31,14 @@ public abstract class MailboxPairingState {
} }
public static class InvalidQrCode extends MailboxPairingState { public static class InvalidQrCode extends MailboxPairingState {
public final QrCodeType qrCodeType;
public final int formatVersion;
public InvalidQrCode(QrCodeType qrCodeType, int formatVersion) {
this.qrCodeType = qrCodeType;
this.formatVersion = formatVersion;
}
} }
public static class MailboxAlreadyPaired extends MailboxPairingState { public static class MailboxAlreadyPaired extends MailboxPairingState {

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface QrCodeClassifier {
enum QrCodeType {
BQP,
MAILBOX,
UNKNOWN
}
Pair<QrCodeType, Integer> classifyQrCode(String payload);
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Thrown when a QR code that has been scanned does not have the expected type.
*/
@Immutable
@NotNullByDefault
public class WrongQrCodeTypeException extends FormatException {
private final QrCodeType qrCodeType;
public WrongQrCodeTypeException(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
public QrCodeType getQrCodeType() {
return qrCodeType;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -17,13 +16,18 @@ import javax.annotation.Nullable;
import static java.nio.charset.CodingErrorAction.IGNORE; import static java.nio.charset.CodingErrorAction.IGNORE;
import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.CASE_INSENSITIVE;
@SuppressWarnings("CharsetObjectCanBeUsed")
@NotNullByDefault @NotNullByDefault
public class StringUtils { public class StringUtils {
private static final Charset UTF_8 = Charset.forName("UTF-8"); public static final Charset UTF_8 = Charset.forName("UTF-8");
private static Pattern MAC = Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" + public static final Charset US_ASCII = Charset.forName("US-ASCII");
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
CASE_INSENSITIVE);
private static final Pattern MAC =
Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" +
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}",
CASE_INSENSITIVE);
private static final char[] HEX = new char[] { private static final char[] HEX = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',
@@ -45,11 +49,7 @@ public class StringUtils {
} }
public static byte[] toUtf8(String s) { public static byte[] toUtf8(String s) {
try { return s.getBytes(UTF_8);
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
} }
public static String fromUtf8(byte[] bytes) { public static String fromUtf8(byte[] bytes) {

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule; import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.qrcode.QrCodeModule;
import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.reliability.ReliabilityModule; import org.briarproject.bramble.reliability.ReliabilityModule;
import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -47,6 +48,7 @@ import dagger.Module;
MailboxModule.class, MailboxModule.class,
PluginModule.class, PluginModule.class,
PropertiesModule.class, PropertiesModule.class,
QrCodeModule.class,
RecordModule.class, RecordModule.class,
ReliabilityModule.class, ReliabilityModule.class,
RendezvousModule.class, RendezvousModule.class,

View File

@@ -19,7 +19,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -29,6 +28,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager {
} }
try { try {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), UTF_8));
String key = reader.readLine(); String key = reader.readLine();
reader.close(); reader.close();
return key; return key;
@@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock") @GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException { private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes(Charset.forName("UTF-8"))); out.write(key.getBytes(UTF_8));
out.flush(); out.flush();
out.close(); out.close();
} }

View File

@@ -29,7 +29,6 @@ import org.briarproject.nullsafety.NotNullByDefault;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair; import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
@@ -51,6 +50,7 @@ import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHE
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@NotNullByDefault @NotNullByDefault
class CryptoComponentImpl implements CryptoComponent { class CryptoComponentImpl implements CryptoComponent {
@@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public String encodeOnion(byte[] publicKey) { public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256); Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII")); byte[] label = ".onion checksum".getBytes(US_ASCII);
digest.update(label, 0, label.length); digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length); digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION); digest.update(ONION_HS_PROTOCOL_VERSION);

View File

@@ -39,12 +39,13 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.Charset;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Scanner; import java.util.Scanner;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class MessageEncrypter { public class MessageEncrypter {
@@ -228,7 +229,7 @@ public class MessageEncrypter {
PublicKey publicKey = PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes); encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in); String message = readFully(System.in);
byte[] plaintext = message.getBytes(Charset.forName("UTF-8")); byte[] plaintext = message.getBytes(UTF_8);
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext); byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH)); System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
} }
@@ -242,7 +243,7 @@ public class MessageEncrypter {
encrypter.getKeyParser().parsePrivateKey(keyBytes); encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in)); byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext); byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
System.out.println(new String(plaintext, Charset.forName("UTF-8"))); System.out.println(new String(plaintext, UTF_8));
} }
private static String readFully(InputStream in) throws IOException { private static String readFully(InputStream in) throws IOException {

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
@@ -19,13 +17,13 @@ public class KeyAgreementModule {
} }
@Provides @Provides
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) { PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) {
return new PayloadEncoderImpl(bdfWriterFactory); return payloadEncoder;
} }
@Provides @Provides
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { PayloadParser providePayloadParser(PayloadParserImpl payloadParser) {
return new PayloadParserImpl(bdfReaderFactory); return payloadParser;
} }
@Provides @Provides

View File

@@ -13,7 +13,8 @@ import java.io.IOException;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -29,7 +30,8 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override @Override
public byte[] encode(Payload p) { public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(PROTOCOL_VERSION); int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
out.write(formatIdAndVersion);
BdfWriter w = bdfWriterFactory.createWriter(out); BdfWriter w = bdfWriterFactory.createWriter(out);
try { try {
w.writeListStart(); // Payload start w.writeListStart(); // Payload start

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
@@ -11,6 +12,9 @@ import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -21,34 +25,42 @@ import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class PayloadParserImpl implements PayloadParser { class PayloadParserImpl implements PayloadParser {
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final QrCodeClassifier qrCodeClassifier;
@Inject @Inject
PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { PayloadParserImpl(BdfReaderFactory bdfReaderFactory,
QrCodeClassifier qrCodeClassifier) {
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.qrCodeClassifier = qrCodeClassifier;
} }
@Override @Override
public Payload parse(byte[] raw) throws IOException { public Payload parse(String payloadString) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw); Pair<QrCodeType, Integer> typeAndVersion =
// First byte: the protocol version qrCodeClassifier.classifyQrCode(payloadString);
int protocolVersion = in.read(); QrCodeType qrCodeType = typeAndVersion.getFirst();
if (protocolVersion == -1) throw new FormatException(); if (qrCodeType != BQP) throw new WrongQrCodeTypeException(qrCodeType);
if (protocolVersion != PROTOCOL_VERSION) { int formatVersion = typeAndVersion.getSecond();
boolean tooOld = protocolVersion < PROTOCOL_VERSION || if (formatVersion != QR_FORMAT_VERSION) {
protocolVersion == BETA_PROTOCOL_VERSION; boolean tooOld = formatVersion < QR_FORMAT_VERSION;
throw new UnsupportedVersionException(tooOld); throw new UnsupportedVersionException(tooOld);
} }
byte[] raw = payloadString.getBytes(ISO_8859_1);
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the format identifier and version (already parsed)
if (in.read() == -1) throw new AssertionError();
// The rest of the payload is a BDF list with one or more elements // The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
BdfList payload = r.readList(); BdfList payload = r.readList();

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
@@ -25,6 +26,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
@Inject @Inject
MailboxPairingTaskFactoryImpl( MailboxPairingTaskFactoryImpl(
@@ -34,7 +36,8 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
Clock clock, Clock clock,
MailboxApi api, MailboxApi api,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) { MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.db = db; this.db = db;
this.crypto = crypto; this.crypto = crypto;
@@ -42,12 +45,13 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
} }
@Override @Override
public MailboxPairingTask createPairingTask(String qrCodePayload) { public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db, return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager, crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager); mailboxUpdateManager, qrCodeClassifier);
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -9,18 +10,26 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException; import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -32,7 +41,10 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -40,9 +52,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG = private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName()); getLogger(MailboxPairingTaskImpl.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final String payload; private final String payload;
private final Executor eventExecutor; private final Executor eventExecutor;
@@ -52,6 +61,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
private final long timeStarted; private final long timeStarted;
private final Object lock = new Object(); private final Object lock = new Object();
@@ -69,7 +79,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
Clock clock, Clock clock,
MailboxApi api, MailboxApi api,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) { MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.payload = payload; this.payload = payload;
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.db = db; this.db = db;
@@ -78,8 +89,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
timeStarted = clock.currentTimeMillis(); timeStarted = clock.currentTimeMillis();
state = new MailboxPairingState.QrCodeReceived(timeStarted); state = new QrCodeReceived(timeStarted);
} }
@Override @Override
@@ -101,22 +113,30 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
@Override @Override
public void run() { public void run() {
Pair<QrCodeType, Integer> typeAndVersion =
qrCodeClassifier.classifyQrCode(payload);
QrCodeType qrCodeType = typeAndVersion.getFirst();
int formatVersion = typeAndVersion.getSecond();
if (qrCodeType != MAILBOX || formatVersion != QR_FORMAT_VERSION) {
setState(new InvalidQrCode(qrCodeType, formatVersion));
return;
}
try { try {
pairMailbox(); pairMailbox();
} catch (FormatException e) { } catch (FormatException e) {
onMailboxError(e, new MailboxPairingState.InvalidQrCode()); onMailboxError(e, new InvalidQrCode(qrCodeType, formatVersion));
} catch (MailboxAlreadyPairedException e) { } catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired()); onMailboxError(e, new MailboxAlreadyPaired());
} catch (IOException e) { } catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError()); onMailboxError(e, new ConnectionError());
} catch (ApiException | DbException e) { } catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError()); onMailboxError(e, new UnexpectedError());
} }
} }
private void pairMailbox() throws IOException, ApiException, DbException { private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload); MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing(timeStarted)); setState(new Pairing(timeStarted));
MailboxProperties ownerProperties = api.setup(mailboxProperties); MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis(); long time = clock.currentTimeMillis();
db.transaction(false, txn -> { db.transaction(false, txn -> {
@@ -135,7 +155,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} }
} }
}); });
setState(new MailboxPairingState.Paired()); setState(new Paired());
} }
private void onMailboxError(Exception e, MailboxPairingState state) { private void onMailboxError(Exception e, MailboxPairingState state) {
@@ -169,14 +189,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} }
throw new FormatException(); throw new FormatException();
} }
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid"); LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey); String onion = crypto.encodeOnion(onionPubKey);

View File

@@ -46,7 +46,6 @@ import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -100,6 +99,7 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -419,9 +419,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path); append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath(); String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath); append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
//noinspection CharsetObjectCanBeUsed return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
} }
private void listFiles(File f) { private void listFiles(File f) {

View File

@@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import java.nio.charset.Charset; import static org.briarproject.bramble.util.StringUtils.US_ASCII;
class TorRendezvousCryptoImpl implements TorRendezvousCrypto { class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
@@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
byte[] hash = spec.getH(); byte[] hash = spec.getH();
byte[] base64 = Base64.encode(hash); byte[] base64 = Base64.encode(hash);
return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII")); return "ED25519-V3:" + new String(base64, US_ASCII);
} }
} }

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
class QrCodeClassifierImpl implements QrCodeClassifier {
@Inject
QrCodeClassifierImpl() {
}
@Override
public Pair<QrCodeType, Integer> classifyQrCode(String payload) {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length == 0) return new Pair<>(UNKNOWN, 0);
// If this is a Bramble QR code then the first byte encodes the
// format ID (3 bits) and version (5 bits)
int formatIdAndVersion = bytes[0] & 0xFF;
int formatId = formatIdAndVersion >> 5;
int formatVersion = formatIdAndVersion & 0x1F;
if (formatId == KeyAgreementConstants.QR_FORMAT_ID) {
return new Pair<>(BQP, formatVersion);
}
if (formatId == MailboxConstants.QR_FORMAT_ID) {
return new Pair<>(MAILBOX, formatVersion);
}
return new Pair<>(UNKNOWN, 0);
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import dagger.Module;
import dagger.Provides;
@Module
public class QrCodeModule {
@Provides
QrCodeClassifier provideQrCodeClassifier(
QrCodeClassifierImpl qrCodeClassifier) {
return qrCodeClassifier;
}
}

View File

@@ -20,7 +20,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -34,6 +33,7 @@ import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
@@ -342,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private void storeDatabaseKey(File f, String hex) throws IOException { private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs(); f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes(Charset.forName("UTF-8"))); out.write(hex.getBytes(UTF_8));
out.flush(); out.flush();
out.close(); out.close();
} }
@@ -350,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Nullable @Nullable
private String loadDatabaseKey(File f) throws IOException { private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), UTF_8));
String hex = reader.readLine(); String hex = reader.readLine();
reader.close(); reader.close();
return hex; return hex;

View File

@@ -2,21 +2,27 @@ package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -26,32 +32,29 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
private final BdfReaderFactory bdfReaderFactory = private final BdfReaderFactory bdfReaderFactory =
context.mock(BdfReaderFactory.class); context.mock(BdfReaderFactory.class);
private final QrCodeClassifier qrCodeClassifier =
context.mock(QrCodeClassifier.class);
private final BdfReader bdfReader = context.mock(BdfReader.class); private final BdfReader bdfReader = context.mock(BdfReader.class);
private final PayloadParserImpl payloadParser = private final String payload = getRandomString(123);
new PayloadParserImpl(bdfReaderFactory);
@Test(expected = FormatException.class) private final PayloadParserImpl payloadParser =
public void testThrowsFormatExceptionIfPayloadIsEmpty() throws Exception { new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier);
payloadParser.parse(new byte[0]);
@Test(expected = WrongQrCodeTypeException.class)
public void testThrowsExceptionForWrongQrCodeType() throws Exception {
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
payloadParser.parse(payload);
} }
@Test @Test
public void testThrowsUnsupportedVersionExceptionForOldVersion() public void testThrowsUnsupportedVersionExceptionForOldVersion()
throws Exception { throws Exception {
try { expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION - 1);
payloadParser.parse(new byte[] {PROTOCOL_VERSION - 1});
fail();
} catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld());
}
}
@Test
public void testThrowsUnsupportedVersionExceptionForBetaVersion()
throws Exception {
try { try {
payloadParser.parse(new byte[] {BETA_PROTOCOL_VERSION}); payloadParser.parse(payload);
fail(); fail();
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld()); assertTrue(e.isTooOld());
@@ -61,8 +64,10 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test @Test
public void testThrowsUnsupportedVersionExceptionForNewVersion() public void testThrowsUnsupportedVersionExceptionForNewVersion()
throws Exception { throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION + 1);
try { try {
payloadParser.parse(new byte[] {PROTOCOL_VERSION + 1}); payloadParser.parse(payload);
fail(); fail();
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
assertFalse(e.isTooOld()); assertFalse(e.isTooOld());
@@ -71,6 +76,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testThrowsFormatExceptionForEmptyList() throws Exception { public void testThrowsFormatExceptionForEmptyList() throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -79,7 +86,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(new BdfList())); will(returnValue(new BdfList()));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -87,6 +94,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH); byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -97,7 +106,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -105,6 +114,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1); byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -115,7 +125,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -123,6 +133,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1); byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -133,12 +144,14 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test @Test
public void testAcceptsPayloadWithNoDescriptors() throws Exception { public void testAcceptsPayloadWithNoDescriptors() throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH); byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -149,8 +162,16 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
Payload p = payloadParser.parse(new byte[] {PROTOCOL_VERSION}); Payload p = payloadParser.parse(payload);
assertArrayEquals(commitment, p.getCommitment()); assertArrayEquals(commitment, p.getCommitment());
assertTrue(p.getTransportDescriptors().isEmpty()); assertTrue(p.getTransportDescriptors().isEmpty());
} }
private void expectClassifyQrCode(String payload, QrCodeType qrCodeType,
int formatVersion) {
context.checking(new Expectations() {{
oneOf(qrCodeClassifier).classifyQrCode(payload);
will(returnValue(new Pair<>(qrCodeType, formatVersion)));
}});
}
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -7,13 +8,24 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
@@ -27,6 +39,9 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.mailbox.MailboxTestUtils.getQrCodePayload; import static org.briarproject.bramble.mailbox.MailboxTestUtils.getQrCodePayload;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -48,9 +63,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
context.mock(MailboxSettingsManager.class); context.mock(MailboxSettingsManager.class);
private final MailboxUpdateManager mailboxUpdateManager = private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class); context.mock(MailboxUpdateManager.class);
private final MailboxPairingTaskFactory factory = private final QrCodeClassifier qrCodeClassifier =
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api, context.mock(QrCodeClassifier.class);
mailboxSettingsManager, mailboxUpdateManager);
private final String onion = getRandomString(56); private final String onion = getRandomString(56);
private final byte[] onionBytes = getRandomBytes(32); private final byte[] onionBytes = getRandomBytes(32);
@@ -70,28 +84,48 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
public void testInitialQrCodeReceivedState() { public void testInitialQrCodeReceivedState() {
MailboxPairingTask task = createPairingTask(getRandomString(42)); MailboxPairingTask task = createPairingTask(getRandomString(42));
task.addObserver(state -> task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.QrCodeReceived) assertTrue(state instanceof QrCodeReceived));
);
} }
@Test @Test
public void testInvalidQrCode() { public void testInvalidQrCodeType() {
MailboxPairingTask task1 = createPairingTask(getRandomString(42)); String payload = getRandomString(65);
task1.run(); MailboxPairingTask task = createPairingTask(payload);
task1.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
);
String goodLength = "00" + getRandomString(63); expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
MailboxPairingTask task2 = createPairingTask(goodLength);
task2.run(); task.run();
task2.addObserver(state -> task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode) assertTrue(state instanceof InvalidQrCode));
); }
@Test
public void testInvalidQrCodeVersion() {
String payload = getRandomString(65);
MailboxPairingTask task = createPairingTask(payload);
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION + 1);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
}
@Test
public void testInvalidQrCodeLength() {
String payload = getRandomString(42);
MailboxPairingTask task = createPairingTask(payload);
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
} }
@Test @Test
public void testSuccessfulPairing() throws Exception { public void testSuccessfulPairing() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -122,14 +156,11 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
MailboxPairingTask task = createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.addObserver(state -> { task.addObserver(state -> {
if (i.get() == 0) { if (i.get() == 0) {
assertEquals(MailboxPairingState.QrCodeReceived.class, assertEquals(QrCodeReceived.class, state.getClass());
state.getClass());
} else if (i.get() == 1) { } else if (i.get() == 1) {
assertEquals(MailboxPairingState.Pairing.class, assertEquals(Pairing.class, state.getClass());
state.getClass());
} else if (i.get() == 2) { } else if (i.get() == 2) {
assertEquals(MailboxPairingState.Paired.class, assertEquals(Paired.class, state.getClass());
state.getClass());
} else fail("Unexpected change of state " + state.getClass()); } else fail("Unexpected change of state " + state.getClass());
i.getAndIncrement(); i.getAndIncrement();
}); });
@@ -138,24 +169,23 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testAlreadyPaired() throws Exception { public void testAlreadyPaired() throws Exception {
testApiException(new MailboxApi.MailboxAlreadyPairedException(), testApiException(new MailboxAlreadyPairedException(),
MailboxPairingState.MailboxAlreadyPaired.class); MailboxAlreadyPaired.class);
} }
@Test @Test
public void testMailboxApiException() throws Exception { public void testMailboxApiException() throws Exception {
testApiException(new MailboxApi.ApiException(), testApiException(new ApiException(), UnexpectedError.class);
MailboxPairingState.UnexpectedError.class);
} }
@Test @Test
public void testApiIOException() throws Exception { public void testApiIOException() throws Exception {
testApiException(new IOException(), testApiException(new IOException(), ConnectionError.class);
MailboxPairingState.ConnectionError.class);
} }
private void testApiException(Exception e, private void testApiException(Exception e,
Class<? extends MailboxPairingState> s) throws Exception { Class<? extends MailboxPairingState> s) throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -170,6 +200,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testDbException() throws Exception { public void testDbException() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -188,8 +219,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
MailboxPairingTask task = createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.run(); task.run();
task.addObserver(state -> assertEquals(state.getClass(), task.addObserver(state ->
MailboxPairingState.UnexpectedError.class)); assertEquals(state.getClass(), UnexpectedError.class));
} }
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) { private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
@@ -206,6 +237,16 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(returnValue(time)); will(returnValue(time));
}}); }});
return factory.createPairingTask(qrCodePayload); return new MailboxPairingTaskImpl(qrCodePayload, executor, db,
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager, qrCodeClassifier);
}
private void expectClassifyQrCode(String payload, QrCodeType qrCodeType,
int formatVersion) {
context.checking(new Expectations() {{
oneOf(qrCodeClassifier).classifyQrCode(payload);
will(returnValue(new Pair<>(qrCodeType, formatVersion)));
}});
} }
} }

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider; import org.briarproject.bramble.api.WeakSingletonProvider;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@@ -11,20 +10,24 @@ import javax.net.SocketFactory;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
class MailboxTestUtils { class MailboxTestUtils {
static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) { static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) {
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
byte[] payloadBytes = ByteBuffer.allocate(65) byte[] payloadBytes = ByteBuffer.allocate(65)
.put((byte) 32) // 1 .put((byte) formatIdAndVersion) // 1
.put(onionBytes) // 32 .put(onionBytes) // 32
.put(setupToken) // 32 .put(setupToken) // 32
.array(); .array();
//noinspection CharsetObjectCanBeUsed return new String(payloadBytes, ISO_8859_1);
return new String(payloadBytes, Charset.forName("ISO-8859-1"));
} }
// Used by mailbox integration tests
static String getQrCodePayload(byte[] setupToken) { static String getQrCodePayload(byte[] setupToken) {
return getQrCodePayload(getRandomId(), setupToken); return getQrCodePayload(getRandomId(), setupToken);
} }

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.junit.Assert.assertEquals;
public class QrCodeClassifierImplTest extends BrambleTestCase {
private final QrCodeClassifier classifier = new QrCodeClassifierImpl();
@Test
public void testClassifiesEmptyStringAsUnknown() {
Pair<QrCodeType, Integer> result = classifier.classifyQrCode("");
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
@Test
public void testClassifiesKeyAgreement() {
byte[] payloadBytes = getRandomBytes(123);
for (int version = 0; version < 32; version++) {
int typeAndVersion =
(KeyAgreementConstants.QR_FORMAT_ID << 5) | version;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result =
classifier.classifyQrCode(payload);
assertEquals(BQP, result.getFirst());
assertEquals(version, result.getSecond().intValue());
}
}
@Test
public void testClassifiesMailbox() {
byte[] payloadBytes = getRandomBytes(123);
for (int version = 0; version < 32; version++) {
int typeAndVersion =
(MailboxConstants.QR_FORMAT_ID << 5) | version;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result =
classifier.classifyQrCode(payload);
assertEquals(MAILBOX, result.getFirst());
assertEquals(version, result.getSecond().intValue());
}
}
@Test
public void testClassifiesUnknownFormatIdAsUnknown() {
byte[] payloadBytes = getRandomBytes(123);
int unknownFormatId = MailboxConstants.QR_FORMAT_ID + 1;
int typeAndVersion = unknownFormatId << 5;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result = classifier.classifyQrCode(payload);
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
}

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@@ -33,6 +32,7 @@ import static java.util.logging.Level.WARNING;
import static jssc.SerialPort.PURGE_RXCLEAR; import static jssc.SerialPort.PURGE_RXCLEAR;
import static jssc.SerialPort.PURGE_TXCLEAR; import static jssc.SerialPort.PURGE_TXCLEAR;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -41,7 +41,6 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ModemImpl.class.getName()); Logger.getLogger(ModemImpl.class.getName());
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private static final int MAX_LINE_LENGTH = 256; private static final int MAX_LINE_LENGTH = 256;
private static final int[] BAUD_RATES = { private static final int[] BAUD_RATES = {
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200 256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200

View File

@@ -3,12 +3,14 @@ package org.briarproject.briar.android.contact.add.nearby;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
abstract class AddContactState { abstract class AddContactState {
static class KeyAgreementListening extends AddContactState { static class KeyAgreementListening extends AddContactState {
final Bitmap qrCode; final Bitmap qrCode;
KeyAgreementListening(Bitmap qrCode) { KeyAgreementListening(Bitmap qrCode) {
@@ -29,6 +31,7 @@ abstract class AddContactState {
} }
static class ContactExchangeFinished extends AddContactState { static class ContactExchangeFinished extends AddContactState {
final ContactExchangeResult result; final ContactExchangeResult result;
ContactExchangeFinished(ContactExchangeResult result) { ContactExchangeFinished(ContactExchangeResult result) {
@@ -37,25 +40,34 @@ abstract class AddContactState {
} }
static class Failed extends AddContactState { static class Failed extends AddContactState {
/**
* 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) { static class WrongQrCodeType extends Failed {
this.qrCodeTooOld = qrCodeTooOld;
final QrCodeType qrCodeType;
WrongQrCodeType(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
} }
Failed() { static class WrongQrCodeVersion extends Failed {
this(null);
/**
* True if the app producing the code is too old.
* False if the scanning app is too old.
*/
final boolean qrCodeTooOld;
WrongQrCodeVersion(boolean qrCodeTooOld) {
this.qrCodeTooOld = qrCodeTooOld;
}
} }
} }
abstract static class ContactExchangeResult { abstract static class ContactExchangeResult {
static class Success extends ContactExchangeResult { static class Success extends ContactExchangeResult {
final Author remoteAuthor; final Author remoteAuthor;
Success(Author remoteAuthor) { Success(Author remoteAuthor) {
@@ -64,6 +76,7 @@ abstract class AddContactState {
} }
static class Error extends ContactExchangeResult { static class Error extends ContactExchangeResult {
@Nullable @Nullable
final Author duplicateAuthor; final Author duplicateAuthor;

View File

@@ -6,12 +6,15 @@ import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.briar.R; 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.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed; import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision; import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
@@ -34,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -141,9 +145,15 @@ public class AddNearbyContactActivity extends BriarActivity
ContactExchangeResult result = ContactExchangeResult result =
((ContactExchangeFinished) state).result; ((ContactExchangeFinished) state).result;
onContactExchangeResult(result); onContactExchangeResult(result);
} else if (state instanceof WrongQrCodeType) {
QrCodeType qrCodeType = ((WrongQrCodeType) state).qrCodeType;
if (qrCodeType == MAILBOX) onMailboxQrCodeScanned();
else onWrongQrCodeType();
} else if (state instanceof WrongQrCodeVersion) {
boolean qrCodeTooOld = ((WrongQrCodeVersion) state).qrCodeTooOld;
onWrongQrCodeVersion(qrCodeTooOld);
} else if (state instanceof Failed) { } else if (state instanceof Failed) {
Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; showErrorFragment();
onAddingContactFailed(qrCodeTooOld);
} }
} }
@@ -170,15 +180,27 @@ public class AddNearbyContactActivity extends BriarActivity
} else throw new AssertionError(); } else throw new AssertionError();
} }
private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) { private void onMailboxQrCodeScanned() {
if (qrCodeTooOld == null) { String title = getString(R.string.qr_code_invalid);
showErrorFragment(); String msg = getString(R.string.mailbox_qr_code_for_contact);
} else { showNextFragment(
String msg; AddNearbyContactErrorFragment.newInstance(title, msg, false));
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1); }
else msg = getString(R.string.qr_code_too_new_1);
showNextFragment(AddNearbyContactErrorFragment.newInstance(msg)); private void onWrongQrCodeType() {
} String title = getString(R.string.qr_code_invalid);
String msg = getString(R.string.qr_code_format_unknown);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
}
private void onWrongQrCodeVersion(boolean qrCodeTooOld) {
String title = getString(R.string.qr_code_invalid);
String msg;
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1);
else msg = getString(R.string.qr_code_too_new_1);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
} }
private void showErrorFragment() { private void showErrorFragment() {

View File

@@ -22,6 +22,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.GONE;
import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen; import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
@@ -31,17 +32,22 @@ public class AddNearbyContactErrorFragment extends BaseFragment {
public static final String TAG = public static final String TAG =
AddNearbyContactErrorFragment.class.getName(); AddNearbyContactErrorFragment.class.getName();
private static final String ERROR_MSG = "errorMessage"; private static final String ARG_TITLE = "title";
private static final String ARG_ERROR_MSG = "message";
private static final String ARG_FEEDBACK = "feedback";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private AddNearbyContactViewModel viewModel; private AddNearbyContactViewModel viewModel;
public static AddNearbyContactErrorFragment newInstance(String errorMsg) { public static AddNearbyContactErrorFragment newInstance(String title,
String errorMessage, boolean feedback) {
AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment(); AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(ERROR_MSG, errorMsg); args.putString(ARG_TITLE, title);
args.putString(ARG_ERROR_MSG, errorMessage);
args.putBoolean(ARG_FEEDBACK, feedback);
f.setArguments(args); f.setArguments(args);
return f; return f;
} }
@@ -66,19 +72,32 @@ public class AddNearbyContactErrorFragment extends BaseFragment {
View v = inflater.inflate(R.layout.fragment_error_contact_exchange, View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
container, false); container, false);
// set optional error message String title = null, errorMessage = null;
TextView explanation = v.findViewById(R.id.errorMessage); boolean feedback = true;
Bundle args = getArguments(); Bundle args = getArguments();
String errorMessage = args == null ? null : args.getString(ERROR_MSG); if (args != null) {
if (errorMessage == null) { title = args.getString(ARG_TITLE);
explanation.setText(getString(R.string.add_contact_error_two_way)); errorMessage = args.getString(ARG_ERROR_MSG);
} else { feedback = args.getBoolean(ARG_FEEDBACK, true);
explanation.setText(args.getString(ERROR_MSG)); }
if (title != null) {
TextView titleView = v.findViewById(R.id.errorTitle);
titleView.setText(title);
}
if (errorMessage != null) {
TextView messageView = v.findViewById(R.id.errorMessage);
messageView.setText(errorMessage);
} }
// make feedback link clickable
TextView sendFeedback = v.findViewById(R.id.sendFeedback); TextView sendFeedback = v.findViewById(R.id.sendFeedback);
onSingleLinkClick(sendFeedback, this::triggerFeedback); if (feedback) {
// make feedback link clickable
onSingleLinkClick(sendFeedback, this::triggerFeedback);
} else {
sendFeedback.setVisibility(GONE);
}
// buttons // buttons
Button tryAgain = v.findViewById(R.id.tryAgainButton); Button tryAgain = v.findViewById(R.id.tryAgainButton);

View File

@@ -43,6 +43,7 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin; import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -50,9 +51,13 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Contact
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Success; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Success;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeStarted; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeStarted;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.QrCodeScanned;
import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeDecoder;
import org.briarproject.briar.android.qrcode.QrCodeUtils; import org.briarproject.briar.android.qrcode.QrCodeUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
@@ -60,7 +65,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -85,6 +89,7 @@ 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.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -126,9 +131,6 @@ class AddNearbyContactViewModel extends AndroidViewModel
REFUSED REFUSED
} }
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private final EventBus eventBus; private final EventBus eventBus;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -376,11 +378,11 @@ class AddNearbyContactViewModel extends AndroidViewModel
} else if (e instanceof KeyAgreementAbortedEvent) { } else if (e instanceof KeyAgreementAbortedEvent) {
LOG.info("KeyAgreementAbortedEvent received"); LOG.info("KeyAgreementAbortedEvent received");
resetPayloadFlags(); resetPayloadFlags();
state.setValue(new AddContactState.Failed()); state.setValue(new Failed());
} else if (e instanceof KeyAgreementFailedEvent) { } else if (e instanceof KeyAgreementFailedEvent) {
LOG.info("KeyAgreementFailedEvent received"); LOG.info("KeyAgreementFailedEvent received");
resetPayloadFlags(); resetPayloadFlags();
state.setValue(new AddContactState.Failed()); state.setValue(new Failed());
} }
} }
@@ -446,22 +448,22 @@ class AddNearbyContactViewModel extends AndroidViewModel
// Ignore results until the KeyAgreementTask is ready // Ignore results until the KeyAgreementTask is ready
if (!gotLocalPayload || gotRemotePayload || currentTask == null) return; if (!gotLocalPayload || gotRemotePayload || currentTask == null) return;
try { try {
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); Payload remotePayload = payloadParser.parse(result.getText());
if (LOG.isLoggable(INFO))
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
Payload remotePayload = payloadParser.parse(payloadBytes);
gotRemotePayload = true; gotRemotePayload = true;
currentTask.connectAndRunProtocol(remotePayload); currentTask.connectAndRunProtocol(remotePayload);
state.postValue(new AddContactState.QrCodeScanned()); state.postValue(new QrCodeScanned());
} catch (WrongQrCodeTypeException e) {
resetPayloadFlags();
state.postValue(new WrongQrCodeType(e.getQrCodeType()));
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
resetPayloadFlags(); resetPayloadFlags();
state.postValue(new AddContactState.Failed(e.isTooOld())); state.postValue(new WrongQrCodeVersion(e.isTooOld()));
} catch (IOException | IllegalArgumentException e) { } catch (IOException | IllegalArgumentException e) {
LOG.log(WARNING, "QR Code Invalid", e); LOG.log(WARNING, "QR Code Invalid", e);
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(), androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
R.string.qr_code_invalid, LENGTH_LONG).show()); R.string.qr_code_invalid, LENGTH_LONG).show());
resetPayloadFlags(); resetPayloadFlags();
state.postValue(new AddContactState.Failed()); state.postValue(new Failed());
} }
} }

View File

@@ -436,7 +436,7 @@ class HotspotManager {
} }
private static String createWifiLoginString(String ssid, String password) { private static String createWifiLoginString(String ssid, String password) {
// https://en.wikipedia.org/wiki/QR_code#WiFi_network_login // https://en.wikipedia.org/wiki/QR_code#Joining_a_Wi%E2%80%91Fi_network
// do not remove the dangling ';', it can cause problems to omit it // do not remove the dangling ';', it can cause problems to omit it
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
} }

View File

@@ -6,10 +6,24 @@ import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pending;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.briar.R; 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.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.FinalFragment; import org.briarproject.briar.android.fragment.FinalFragment;
import org.briarproject.briar.android.mailbox.MailboxState.CameraError;
import org.briarproject.briar.android.mailbox.MailboxState.IsPaired;
import org.briarproject.briar.android.mailbox.MailboxState.NotSetup;
import org.briarproject.briar.android.mailbox.MailboxState.OfflineWhenPairing;
import org.briarproject.briar.android.mailbox.MailboxState.Pairing;
import org.briarproject.briar.android.mailbox.MailboxState.ScanningQrCode;
import org.briarproject.briar.android.mailbox.MailboxState.ShowDownload;
import org.briarproject.briar.android.mailbox.MailboxState.WasUnpaired;
import org.briarproject.briar.android.view.BlankFragment; import org.briarproject.briar.android.view.BlankFragment;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
@@ -25,6 +39,9 @@ import androidx.lifecycle.ViewModelProvider;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.briar.android.util.UiUtils.showFragment; import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -56,24 +73,23 @@ public class MailboxActivity extends BriarActivity {
} }
viewModel.getPairingState().observeEvent(this, state -> { viewModel.getPairingState().observeEvent(this, state -> {
if (state instanceof MailboxState.NotSetup) { if (state instanceof NotSetup) {
onNotSetup(); onNotSetup();
} else if (state instanceof MailboxState.ShowDownload) { } else if (state instanceof ShowDownload) {
onShowDownload(); onShowDownload();
} else if (state instanceof MailboxState.ScanningQrCode) { } else if (state instanceof ScanningQrCode) {
onScanningQrCode(); onScanningQrCode();
} else if (state instanceof MailboxState.Pairing) { } else if (state instanceof Pairing) {
MailboxPairingState s = MailboxPairingState s = ((Pairing) state).pairingState;
((MailboxState.Pairing) state).pairingState;
onMailboxPairingStateChanged(s); onMailboxPairingStateChanged(s);
} else if (state instanceof MailboxState.OfflineWhenPairing) { } else if (state instanceof OfflineWhenPairing) {
onOffline(); onOffline();
} else if (state instanceof MailboxState.CameraError) { } else if (state instanceof CameraError) {
onCameraError(); onCameraError();
} else if (state instanceof MailboxState.IsPaired) { } else if (state instanceof IsPaired) {
onIsPaired(((MailboxState.IsPaired) state).isOnline); onIsPaired(((IsPaired) state).isOnline);
} else if (state instanceof MailboxState.WasUnpaired) { } else if (state instanceof WasUnpaired) {
MailboxState.WasUnpaired s = (MailboxState.WasUnpaired) state; WasUnpaired s = (WasUnpaired) state;
onUnPaired(s.tellUserToWipeMailbox); onUnPaired(s.tellUserToWipeMailbox);
} else { } else {
throw new AssertionError("Unknown state: " + state); throw new AssertionError("Unknown state: " + state);
@@ -104,7 +120,7 @@ public class MailboxActivity extends BriarActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
MailboxState s = viewModel.getPairingState().getLastValue(); MailboxState s = viewModel.getPairingState().getLastValue();
if (s instanceof MailboxState.Pairing) { if (s instanceof Pairing) {
// don't go back in the flow if we are already pairing // don't go back in the flow if we are already pairing
// with the mailbox. We provide a try-again button instead. // with the mailbox. We provide a try-again button instead.
supportFinishAfterTransition(); supportFinishAfterTransition();
@@ -158,31 +174,44 @@ public class MailboxActivity extends BriarActivity {
} }
Fragment f; Fragment f;
String tag; String tag;
if (s instanceof MailboxPairingState.Pending) { if (s instanceof Pending) {
long timeStarted = ((MailboxPairingState.Pending) s).timeStarted; long timeStarted = ((Pending) s).timeStarted;
f = MailboxConnectingFragment.newInstance(timeStarted); f = MailboxConnectingFragment.newInstance(timeStarted);
tag = MailboxConnectingFragment.TAG; tag = MailboxConnectingFragment.TAG;
} else if (s instanceof MailboxPairingState.InvalidQrCode) { } else if (s instanceof InvalidQrCode) {
f = ErrorFragment.newInstance( InvalidQrCode i = (InvalidQrCode) s;
R.string.mailbox_setup_qr_code_wrong_title, int errorRes;
R.string.mailbox_setup_qr_code_wrong_description); if (i.qrCodeType == MAILBOX) {
if (i.formatVersion < QR_FORMAT_VERSION) {
errorRes = R.string.mailbox_qr_code_too_old;
} else if (i.formatVersion > QR_FORMAT_VERSION) {
errorRes = R.string.mailbox_qr_code_too_new;
} else {
errorRes = R.string.mailbox_setup_qr_code_wrong_description;
}
} else if (i.qrCodeType == BQP) {
errorRes = R.string.contact_qr_code_for_mailbox;
} else {
errorRes = R.string.mailbox_setup_qr_code_wrong_description;
}
f = ErrorFragment.newInstance(R.string.qr_code_invalid, errorRes);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.MailboxAlreadyPaired) { } else if (s instanceof MailboxAlreadyPaired) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(
R.string.mailbox_setup_already_paired_title, R.string.mailbox_setup_already_paired_title,
R.string.mailbox_setup_already_paired_description); R.string.mailbox_setup_already_paired_description);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.ConnectionError) { } else if (s instanceof ConnectionError) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(
R.string.mailbox_setup_io_error_title, R.string.mailbox_setup_io_error_title,
R.string.mailbox_setup_io_error_description); R.string.mailbox_setup_io_error_description);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.UnexpectedError) { } else if (s instanceof UnexpectedError) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(
R.string.mailbox_setup_assertion_error_title, R.string.mailbox_setup_assertion_error_title,
R.string.mailbox_setup_assertion_error_description); R.string.mailbox_setup_assertion_error_description);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.Paired) { } else if (s instanceof Paired) {
f = FinalFragment.newInstance(R.string.mailbox_setup_paired_title, f = FinalFragment.newInstance(R.string.mailbox_setup_paired_title,
R.drawable.ic_check_circle_outline, R.drawable.ic_check_circle_outline,
R.color.briar_brand_green, R.color.briar_brand_green,

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent; import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
@@ -24,7 +25,14 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.mailbox.MailboxState.CameraError;
import org.briarproject.briar.android.mailbox.MailboxState.IsPaired;
import org.briarproject.briar.android.mailbox.MailboxState.NotSetup; import org.briarproject.briar.android.mailbox.MailboxState.NotSetup;
import org.briarproject.briar.android.mailbox.MailboxState.OfflineWhenPairing;
import org.briarproject.briar.android.mailbox.MailboxState.Pairing;
import org.briarproject.briar.android.mailbox.MailboxState.ScanningQrCode;
import org.briarproject.briar.android.mailbox.MailboxState.ShowDownload;
import org.briarproject.briar.android.mailbox.MailboxState.WasUnpaired;
import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeDecoder;
import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
@@ -113,7 +121,7 @@ class MailboxViewModel extends DbViewModel
MailboxStatus mailboxStatus = MailboxStatus mailboxStatus =
mailboxManager.getMailboxStatus(txn); mailboxManager.getMailboxStatus(txn);
boolean isOnline = isTorActive(); boolean isOnline = isTorActive();
pairingState.postEvent(new MailboxState.IsPaired(isOnline)); pairingState.postEvent(new IsPaired(isOnline));
status.postValue(mailboxStatus); status.postValue(mailboxStatus);
} else { } else {
pairingState.postEvent(new NotSetup()); pairingState.postEvent(new NotSetup());
@@ -142,14 +150,14 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
private void onTorInactive() { private void onTorInactive() {
MailboxState lastState = pairingState.getLastValue(); MailboxState lastState = pairingState.getLastValue();
if (lastState instanceof MailboxState.IsPaired) { if (lastState instanceof IsPaired) {
// we are already paired, so use IsPaired state // we are already paired, so use IsPaired state
pairingState.setEvent(new MailboxState.IsPaired(false)); pairingState.setEvent(new IsPaired(false));
} else if (lastState instanceof MailboxState.Pairing) { } else if (lastState instanceof Pairing) {
MailboxState.Pairing p = (MailboxState.Pairing) lastState; Pairing p = (Pairing) lastState;
// check that we not just finished pairing (showing success screen) // check that we not just finished pairing (showing success screen)
if (!(p.pairingState instanceof MailboxPairingState.Paired)) { if (!(p.pairingState instanceof Paired)) {
pairingState.setEvent(new MailboxState.OfflineWhenPairing()); pairingState.setEvent(new OfflineWhenPairing());
} }
// else ignore offline event as user will be leaving UI flow anyway // else ignore offline event as user will be leaving UI flow anyway
} }
@@ -158,15 +166,15 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void onScanButtonClicked() { void onScanButtonClicked() {
if (isTorActive()) { if (isTorActive()) {
pairingState.setEvent(new MailboxState.ScanningQrCode()); pairingState.setEvent(new ScanningQrCode());
} else { } else {
pairingState.setEvent(new MailboxState.OfflineWhenPairing()); pairingState.setEvent(new OfflineWhenPairing());
} }
} }
@UiThread @UiThread
void onCameraError() { void onCameraError() {
pairingState.setEvent(new MailboxState.CameraError()); pairingState.setEvent(new CameraError());
} }
@Override @Override
@@ -182,7 +190,7 @@ class MailboxViewModel extends DbViewModel
pairingTask = mailboxManager.startPairingTask(qrCodePayload); pairingTask = mailboxManager.startPairingTask(qrCodePayload);
pairingTask.addObserver(this); pairingTask.addObserver(this);
} else { } else {
pairingState.postEvent(new MailboxState.OfflineWhenPairing()); pairingState.postEvent(new OfflineWhenPairing());
} }
} }
@@ -193,7 +201,7 @@ class MailboxViewModel extends DbViewModel
LOG.info("New pairing state: " + LOG.info("New pairing state: " +
mailboxPairingState.getClass().getSimpleName()); mailboxPairingState.getClass().getSimpleName());
} }
pairingState.setEvent(new MailboxState.Pairing(mailboxPairingState)); pairingState.setEvent(new Pairing(mailboxPairingState));
} }
private boolean isTorActive() { private boolean isTorActive() {
@@ -203,7 +211,7 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void showDownloadFragment() { void showDownloadFragment() {
pairingState.setEvent(new MailboxState.ShowDownload()); pairingState.setEvent(new ShowDownload());
} }
@UiThread @UiThread
@@ -214,7 +222,7 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void checkIfOnlineWhenPaired() { void checkIfOnlineWhenPaired() {
boolean isOnline = isTorActive(); boolean isOnline = isTorActive();
pairingState.setEvent(new MailboxState.IsPaired(isOnline)); pairingState.setEvent(new IsPaired(isOnline));
} }
LiveData<Boolean> checkConnection() { LiveData<Boolean> checkConnection() {
@@ -227,7 +235,7 @@ class MailboxViewModel extends DbViewModel
checkConnection(success -> { checkConnection(success -> {
boolean isOnline = isTorActive(); boolean isOnline = isTorActive();
// make UI move back to status fragment by changing pairingState // make UI move back to status fragment by changing pairingState
pairingState.postEvent(new MailboxState.IsPaired(isOnline)); pairingState.postEvent(new IsPaired(isOnline));
}); });
} }
@@ -246,7 +254,7 @@ class MailboxViewModel extends DbViewModel
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
try { try {
boolean wasWiped = mailboxManager.unPair(); boolean wasWiped = mailboxManager.unPair();
pairingState.postEvent(new MailboxState.WasUnpaired(!wasWiped)); pairingState.postEvent(new WasUnpaired(!wasWiped));
} catch (DbException e) { } catch (DbException e) {
handleException(e); handleException(e);
} }

View File

@@ -41,7 +41,6 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private final ResultCallback callback; private final ResultCallback callback;
private Camera camera = null; private Camera camera = null;
private int cameraIndex = 0;
public QrCodeDecoder(AndroidExecutor androidExecutor, public QrCodeDecoder(AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor, ResultCallback callback) { @IoExecutor Executor ioExecutor, ResultCallback callback) {
@@ -53,14 +52,12 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
public void start(Camera camera, int cameraIndex) { public void start(Camera camera, int cameraIndex) {
this.camera = camera; this.camera = camera;
this.cameraIndex = cameraIndex;
askForPreviewFrame(); askForPreviewFrame();
} }
@Override @Override
public void stop() { public void stop() {
camera = null; camera = null;
cameraIndex = 0;
} }
@UiThread @UiThread

View File

@@ -51,12 +51,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_xlarge" android:layout_marginTop="@dimen/margin_xlarge"
android:text="@string/add_contact_error_two_way"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/sendFeedback" app:layout_constraintBottom_toTopOf="@+id/sendFeedback"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorTitle" app:layout_constraintTop_toBottomOf="@+id/errorTitle" />
tools:text="error explanation" />
<TextView <TextView
android:id="@+id/sendFeedback" android:id="@+id/sendFeedback"

View File

@@ -252,6 +252,8 @@
<string name="qr_code_invalid">The QR code is invalid</string> <string name="qr_code_invalid">The QR code is invalid</string>
<string name="qr_code_too_old_1">The QR code you have scanned comes from an older version of Briar.\n\nPlease ask your contact to upgrade to the latest version and then try again.</string> <string name="qr_code_too_old_1">The QR code you have scanned comes from an older version of Briar.\n\nPlease ask your contact to upgrade to the latest version and then try again.</string>
<string name="qr_code_too_new_1">The QR code you have scanned comes from a newer version of Briar.\n\nPlease upgrade to the latest version and then try again.</string> <string name="qr_code_too_new_1">The QR code you have scanned comes from a newer version of Briar.\n\nPlease upgrade to the latest version and then try again.</string>
<string name="mailbox_qr_code_for_contact">The QR code you have scanned comes from Briar Mailbox.\n\nIf you want to link a Mailbox, please choose Settings > Mailbox from the Briar menu.</string>
<string name="qr_code_format_unknown">The QR code you have scanned does not come from Briar.\n\nPlease scan the QR code shown on your contact\'s screen.</string>
<string name="camera_error">Camera error</string> <string name="camera_error">Camera error</string>
<string name="connecting_to_device">Connecting to device\u2026</string> <string name="connecting_to_device">Connecting to device\u2026</string>
<string name="authenticating_with_device">Authenticating with device\u2026</string> <string name="authenticating_with_device">Authenticating with device\u2026</string>
@@ -636,8 +638,10 @@
<string name="mailbox_setup_connecting">Connecting to Mailbox…</string> <string name="mailbox_setup_connecting">Connecting to Mailbox…</string>
<!-- This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes". --> <!-- This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes". -->
<string name="mailbox_setup_connecting_info">This may take up to %1s</string> <string name="mailbox_setup_connecting_info">This may take up to %1s</string>
<string name="mailbox_setup_qr_code_wrong_title">Wrong QR code</string> <string name="mailbox_qr_code_too_old">The QR code you have scanned comes from an older version of Briar Mailbox.\n\nPlease upgrade Briar Mailbox to the latest version and then try again.</string>
<string name="mailbox_setup_qr_code_wrong_description">The scanned code is invalid. Please open the Briar Mailbox app on your Mailbox device and scan the QR code it presents.</string> <string name="mailbox_qr_code_too_new">The QR code you have scanned comes from a newer version of Briar Mailbox.\n\nPlease upgrade Briar to the latest version and then try again.</string>
<string name="contact_qr_code_for_mailbox">The QR code you have scanned is for adding a Briar contact.\n\nIf you want to add a contact, please go to the contact list and tap the + icon.</string>
<string name="mailbox_setup_qr_code_wrong_description">The QR code you have scanned does not come from Briar Mailbox.\n\nPlease open the Briar Mailbox app on your Mailbox device and scan the QR code it presents.</string>
<string name="mailbox_setup_already_paired_title">Mailbox already linked</string> <string name="mailbox_setup_already_paired_title">Mailbox already linked</string>
<string name="mailbox_setup_already_paired_description">Unlink the Mailbox on your other device and try again.</string> <string name="mailbox_setup_already_paired_description">Unlink the Mailbox on your other device and try again.</string>
<string name="mailbox_setup_io_error_title">Could not connect</string> <string name="mailbox_setup_io_error_title">Could not connect</string>

View File

@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
@@ -121,7 +121,7 @@ abstract class AbstractMailboxIntegrationTest
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
pairingTask.addObserver((state) -> { pairingTask.addObserver((state) -> {
if (state instanceof MailboxPairingState.Paired) { if (state instanceof Paired) {
latch.countDown(); latch.countDown();
} }
}); });