diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java index 2f5170b91..3e0e02054 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java @@ -1,18 +1,26 @@ package org.briarproject.bramble.api.keyagreement; -public interface KeyAgreementConstants { +import org.briarproject.bramble.api.mailbox.MailboxConstants; - /** - * The version of the BQP protocol used in beta releases. This version - * number is reserved. - */ - byte BETA_PROTOCOL_VERSION = 89; +public interface KeyAgreementConstants { /** * The current version of the BQP protocol. */ 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. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java index c6458443a..b73ce7b12 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java @@ -7,5 +7,5 @@ import java.io.IOException; @NotNullByDefault public interface PayloadParser { - Payload parse(byte[] raw) throws IOException; + Payload parse(String payload) throws IOException; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java index 02c7b8a5e..16896fc4b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.api.mailbox; +import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants; import org.briarproject.bramble.api.plugin.TransportId; import java.util.List; @@ -19,6 +20,18 @@ public interface MailboxConstants { */ 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 * contacts by {@link MailboxUpdateManager}. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java index 47c055aad..4b74013de 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java @@ -1,5 +1,7 @@ package org.briarproject.bramble.api.mailbox; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; + public abstract class MailboxPairingState { public abstract static class Pending extends MailboxPairingState { @@ -29,6 +31,14 @@ public abstract class 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 { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/QrCodeClassifier.java b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/QrCodeClassifier.java new file mode 100644 index 000000000..00600acf0 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/QrCodeClassifier.java @@ -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 classifyQrCode(String payload); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/WrongQrCodeTypeException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/WrongQrCodeTypeException.java new file mode 100644 index 000000000..070d1e3c0 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/WrongQrCodeTypeException.java @@ -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; + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java index 2797aaf72..d9a19e70a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java @@ -3,7 +3,6 @@ package org.briarproject.bramble.util; import org.briarproject.bramble.api.FormatException; import org.briarproject.nullsafety.NotNullByDefault; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; @@ -17,13 +16,18 @@ import javax.annotation.Nullable; import static java.nio.charset.CodingErrorAction.IGNORE; import static java.util.regex.Pattern.CASE_INSENSITIVE; +@SuppressWarnings("CharsetObjectCanBeUsed") @NotNullByDefault public class StringUtils { - private static final Charset UTF_8 = Charset.forName("UTF-8"); - private static 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); + public static final Charset UTF_8 = Charset.forName("UTF-8"); + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + 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[] { '0', '1', '2', '3', '4', '5', '6', '7', @@ -45,11 +49,7 @@ public class StringUtils { } public static byte[] toUtf8(String s) { - try { - return s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } + return s.getBytes(UTF_8); } public static String fromUtf8(byte[] bytes) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index 85ec0372a..b84ae1720 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -17,6 +17,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.mailbox.MailboxModule; import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.properties.PropertiesModule; +import org.briarproject.bramble.qrcode.QrCodeModule; import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.reliability.ReliabilityModule; import org.briarproject.bramble.rendezvous.RendezvousModule; @@ -47,6 +48,7 @@ import dagger.Module; MailboxModule.class, PluginModule.class, PropertiesModule.class, + QrCodeModule.class, RecordModule.class, ReliabilityModule.class, RendezvousModule.class, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java index 879c4505b..f0f8fe737 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java @@ -19,7 +19,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.charset.Charset; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -29,6 +28,7 @@ import javax.inject.Inject; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; 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.toHexString; @@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager { } try { BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(f), Charset.forName("UTF-8"))); + new FileInputStream(f), UTF_8)); String key = reader.readLine(); reader.close(); return key; @@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager { @GuardedBy("stateChangeLock") private void writeDbKeyToFile(String key, File f) throws IOException { FileOutputStream out = new FileOutputStream(f); - out.write(key.getBytes(Charset.forName("UTF-8"))); + out.write(key.getBytes(UTF_8)); out.flush(); out.close(); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index c81ef00d0..06ce15f29 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -29,7 +29,6 @@ import org.briarproject.nullsafety.NotNullByDefault; import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519KeyPair; -import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; 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.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.bramble.util.StringUtils.US_ASCII; @NotNullByDefault class CryptoComponentImpl implements CryptoComponent { @@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent { @Override public String encodeOnion(byte[] publicKey) { 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(publicKey, 0, publicKey.length); digest.update(ONION_HS_PROTOCOL_VERSION); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java index 3e00e2d1b..9a97b2f7a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java @@ -39,12 +39,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.nio.charset.Charset; import java.security.SecureRandom; import java.util.Scanner; import javax.annotation.concurrent.Immutable; +import static org.briarproject.bramble.util.StringUtils.UTF_8; + @Immutable @NotNullByDefault public class MessageEncrypter { @@ -228,7 +229,7 @@ public class MessageEncrypter { PublicKey publicKey = encrypter.getKeyParser().parsePublicKey(keyBytes); 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); System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH)); } @@ -242,7 +243,7 @@ public class MessageEncrypter { encrypter.getKeyParser().parsePrivateKey(keyBytes); byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in)); 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 { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java index 819832820..54d28d675 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java @@ -1,7 +1,5 @@ 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.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadParser; @@ -19,13 +17,13 @@ public class KeyAgreementModule { } @Provides - PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) { - return new PayloadEncoderImpl(bdfWriterFactory); + PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) { + return payloadEncoder; } @Provides - PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { - return new PayloadParserImpl(bdfReaderFactory); + PayloadParser providePayloadParser(PayloadParserImpl payloadParser) { + return payloadParser; } @Provides diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java index b0ee035e5..ca90f5f96 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java @@ -13,7 +13,8 @@ import java.io.IOException; import javax.annotation.concurrent.Immutable; 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 @NotNullByDefault @@ -29,7 +30,8 @@ class PayloadEncoderImpl implements PayloadEncoder { @Override public byte[] encode(Payload p) { 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); try { w.writeListStart(); // Payload start diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java index 585516c64..d41cffa99 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.keyagreement; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.data.BdfList; 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.LanTcpConstants; 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 java.io.ByteArrayInputStream; @@ -21,34 +25,42 @@ import java.util.List; import javax.annotation.concurrent.Immutable; 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.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_LAN; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; @Immutable @NotNullByDefault class PayloadParserImpl implements PayloadParser { private final BdfReaderFactory bdfReaderFactory; + private final QrCodeClassifier qrCodeClassifier; @Inject - PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { + PayloadParserImpl(BdfReaderFactory bdfReaderFactory, + QrCodeClassifier qrCodeClassifier) { this.bdfReaderFactory = bdfReaderFactory; + this.qrCodeClassifier = qrCodeClassifier; } @Override - public Payload parse(byte[] raw) throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream(raw); - // First byte: the protocol version - int protocolVersion = in.read(); - if (protocolVersion == -1) throw new FormatException(); - if (protocolVersion != PROTOCOL_VERSION) { - boolean tooOld = protocolVersion < PROTOCOL_VERSION || - protocolVersion == BETA_PROTOCOL_VERSION; + public Payload parse(String payloadString) throws IOException { + Pair typeAndVersion = + qrCodeClassifier.classifyQrCode(payloadString); + QrCodeType qrCodeType = typeAndVersion.getFirst(); + if (qrCodeType != BQP) throw new WrongQrCodeTypeException(qrCodeType); + int formatVersion = typeAndVersion.getSecond(); + if (formatVersion != QR_FORMAT_VERSION) { + boolean tooOld = formatVersion < QR_FORMAT_VERSION; 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 BdfReader r = bdfReaderFactory.createReader(in); BdfList payload = r.readList(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java index d90f05d90..a8002e27d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; import org.briarproject.bramble.api.system.Clock; import org.briarproject.nullsafety.NotNullByDefault; @@ -25,6 +26,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory { private final MailboxApi api; private final MailboxSettingsManager mailboxSettingsManager; private final MailboxUpdateManager mailboxUpdateManager; + private final QrCodeClassifier qrCodeClassifier; @Inject MailboxPairingTaskFactoryImpl( @@ -34,7 +36,8 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory { Clock clock, MailboxApi api, MailboxSettingsManager mailboxSettingsManager, - MailboxUpdateManager mailboxUpdateManager) { + MailboxUpdateManager mailboxUpdateManager, + QrCodeClassifier qrCodeClassifier) { this.eventExecutor = eventExecutor; this.db = db; this.crypto = crypto; @@ -42,12 +45,13 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory { this.api = api; this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxUpdateManager = mailboxUpdateManager; + this.qrCodeClassifier = qrCodeClassifier; } @Override public MailboxPairingTask createPairingTask(String qrCodePayload) { return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db, crypto, clock, api, mailboxSettingsManager, - mailboxUpdateManager); + mailboxUpdateManager, qrCodeClassifier); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java index 8ce25be9d..64083ea5f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.crypto.CryptoComponent; 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.mailbox.MailboxAuthToken; 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.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdate; 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.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException; import org.briarproject.nullsafety.NotNullByDefault; import java.io.IOException; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; 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.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.StringUtils.ISO_8859_1; @ThreadSafe @NotNullByDefault @@ -40,9 +52,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { private final static Logger LOG = 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 Executor eventExecutor; @@ -52,6 +61,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { private final MailboxApi api; private final MailboxSettingsManager mailboxSettingsManager; private final MailboxUpdateManager mailboxUpdateManager; + private final QrCodeClassifier qrCodeClassifier; private final long timeStarted; private final Object lock = new Object(); @@ -69,7 +79,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { Clock clock, MailboxApi api, MailboxSettingsManager mailboxSettingsManager, - MailboxUpdateManager mailboxUpdateManager) { + MailboxUpdateManager mailboxUpdateManager, + QrCodeClassifier qrCodeClassifier) { this.payload = payload; this.eventExecutor = eventExecutor; this.db = db; @@ -78,8 +89,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { this.api = api; this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxUpdateManager = mailboxUpdateManager; + this.qrCodeClassifier = qrCodeClassifier; timeStarted = clock.currentTimeMillis(); - state = new MailboxPairingState.QrCodeReceived(timeStarted); + state = new QrCodeReceived(timeStarted); } @Override @@ -101,22 +113,30 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { @Override public void run() { + Pair 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 { pairMailbox(); } catch (FormatException e) { - onMailboxError(e, new MailboxPairingState.InvalidQrCode()); + onMailboxError(e, new InvalidQrCode(qrCodeType, formatVersion)); } catch (MailboxAlreadyPairedException e) { - onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired()); + onMailboxError(e, new MailboxAlreadyPaired()); } catch (IOException e) { - onMailboxError(e, new MailboxPairingState.ConnectionError()); + onMailboxError(e, new ConnectionError()); } catch (ApiException | DbException e) { - onMailboxError(e, new MailboxPairingState.UnexpectedError()); + onMailboxError(e, new UnexpectedError()); } } private void pairMailbox() throws IOException, ApiException, DbException { MailboxProperties mailboxProperties = decodeQrCodePayload(payload); - setState(new MailboxPairingState.Pairing(timeStarted)); + setState(new Pairing(timeStarted)); MailboxProperties ownerProperties = api.setup(mailboxProperties); long time = clock.currentTimeMillis(); 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) { @@ -169,14 +189,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { } 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"); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); String onion = crypto.encodeOnion(onionPubKey); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index b3f08cee0..b3963f56b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -46,7 +46,6 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; 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.LogUtils.logException; 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; @MethodsNotNullByDefault @@ -419,9 +419,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path); String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath(); append(strb, "ClientTransportPlugin snowflake exec", snowflakePath); - //noinspection CharsetObjectCanBeUsed - return new ByteArrayInputStream( - strb.toString().getBytes(Charset.forName("UTF-8"))); + return new ByteArrayInputStream(strb.toString().getBytes(UTF_8)); } private void listFiles(File f) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java index d2b75322a..ee91fa9b4 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java @@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import org.bouncycastle.util.encoders.Base64; 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 { @@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto { EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); byte[] hash = spec.getH(); byte[] base64 = Base64.encode(hash); - return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII")); + return "ED25519-V3:" + new String(base64, US_ASCII); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeClassifierImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeClassifierImpl.java new file mode 100644 index 000000000..5dcd5f3ce --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeClassifierImpl.java @@ -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 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); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeModule.java b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeModule.java new file mode 100644 index 000000000..59fe09190 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeModule.java @@ -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; + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java index e5c345e53..c013158ce 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java @@ -20,7 +20,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.charset.Charset; 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.getSecretKey; 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.toHexString; import static org.junit.Assert.assertArrayEquals; @@ -342,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { private void storeDatabaseKey(File f, String hex) throws IOException { f.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(f); - out.write(hex.getBytes(Charset.forName("UTF-8"))); + out.write(hex.getBytes(UTF_8)); out.flush(); out.close(); } @@ -350,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { @Nullable private String loadDatabaseKey(File f) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(f), Charset.forName("UTF-8"))); + new FileInputStream(f), UTF_8)); String hex = reader.readLine(); reader.close(); return hex; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java index f9d74e081..a9be2c458 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java @@ -2,21 +2,27 @@ package org.briarproject.bramble.keyagreement; import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReaderFactory; 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.jmock.Expectations; import org.junit.Test; 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.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.util.StringUtils.getRandomString; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,32 +32,29 @@ public class PayloadParserImplTest extends BrambleMockTestCase { private final BdfReaderFactory bdfReaderFactory = context.mock(BdfReaderFactory.class); + private final QrCodeClassifier qrCodeClassifier = + context.mock(QrCodeClassifier.class); private final BdfReader bdfReader = context.mock(BdfReader.class); - private final PayloadParserImpl payloadParser = - new PayloadParserImpl(bdfReaderFactory); + private final String payload = getRandomString(123); - @Test(expected = FormatException.class) - public void testThrowsFormatExceptionIfPayloadIsEmpty() throws Exception { - payloadParser.parse(new byte[0]); + private final PayloadParserImpl payloadParser = + new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier); + + @Test(expected = WrongQrCodeTypeException.class) + public void testThrowsExceptionForWrongQrCodeType() throws Exception { + expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION); + + payloadParser.parse(payload); } @Test public void testThrowsUnsupportedVersionExceptionForOldVersion() throws Exception { - try { - payloadParser.parse(new byte[] {PROTOCOL_VERSION - 1}); - fail(); - } catch (UnsupportedVersionException e) { - assertTrue(e.isTooOld()); - } - } + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION - 1); - @Test - public void testThrowsUnsupportedVersionExceptionForBetaVersion() - throws Exception { try { - payloadParser.parse(new byte[] {BETA_PROTOCOL_VERSION}); + payloadParser.parse(payload); fail(); } catch (UnsupportedVersionException e) { assertTrue(e.isTooOld()); @@ -61,8 +64,10 @@ public class PayloadParserImplTest extends BrambleMockTestCase { @Test public void testThrowsUnsupportedVersionExceptionForNewVersion() throws Exception { + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION + 1); + try { - payloadParser.parse(new byte[] {PROTOCOL_VERSION + 1}); + payloadParser.parse(payload); fail(); } catch (UnsupportedVersionException e) { assertFalse(e.isTooOld()); @@ -71,6 +76,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase { @Test(expected = FormatException.class) public void testThrowsFormatExceptionForEmptyList() throws Exception { + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); + context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -79,7 +86,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(new BdfList())); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test(expected = FormatException.class) @@ -87,6 +94,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase { throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); + context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -97,7 +106,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(false)); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test(expected = FormatException.class) @@ -105,6 +114,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -115,7 +125,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test(expected = FormatException.class) @@ -123,6 +133,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -133,12 +144,14 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test public void testAcceptsPayloadWithNoDescriptors() throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH); + + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -149,8 +162,16 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - Payload p = payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + Payload p = payloadParser.parse(payload); assertArrayEquals(commitment, p.getCommitment()); 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))); + }}); + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java index 86763a525..01e4ba9c5 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.mailbox; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.crypto.CryptoComponent; 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.mailbox.MailboxAuthToken; 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.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; 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.mailbox.MailboxApi.ApiException; +import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.ImmediateExecutor; @@ -27,6 +39,9 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; 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.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; @@ -48,9 +63,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { context.mock(MailboxSettingsManager.class); private final MailboxUpdateManager mailboxUpdateManager = context.mock(MailboxUpdateManager.class); - private final MailboxPairingTaskFactory factory = - new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api, - mailboxSettingsManager, mailboxUpdateManager); + private final QrCodeClassifier qrCodeClassifier = + context.mock(QrCodeClassifier.class); private final String onion = getRandomString(56); private final byte[] onionBytes = getRandomBytes(32); @@ -70,28 +84,48 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { public void testInitialQrCodeReceivedState() { MailboxPairingTask task = createPairingTask(getRandomString(42)); task.addObserver(state -> - assertTrue(state instanceof MailboxPairingState.QrCodeReceived) - ); + assertTrue(state instanceof QrCodeReceived)); } @Test - public void testInvalidQrCode() { - MailboxPairingTask task1 = createPairingTask(getRandomString(42)); - task1.run(); - task1.addObserver(state -> - assertTrue(state instanceof MailboxPairingState.InvalidQrCode) - ); + public void testInvalidQrCodeType() { + String payload = getRandomString(65); + MailboxPairingTask task = createPairingTask(payload); - String goodLength = "00" + getRandomString(63); - MailboxPairingTask task2 = createPairingTask(goodLength); - task2.run(); - task2.addObserver(state -> - assertTrue(state instanceof MailboxPairingState.InvalidQrCode) - ); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); + + task.run(); + task.addObserver(state -> + 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 public void testSuccessfulPairing() throws Exception { + expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); @@ -122,14 +156,11 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { MailboxPairingTask task = createPairingTask(validPayload); task.addObserver(state -> { if (i.get() == 0) { - assertEquals(MailboxPairingState.QrCodeReceived.class, - state.getClass()); + assertEquals(QrCodeReceived.class, state.getClass()); } else if (i.get() == 1) { - assertEquals(MailboxPairingState.Pairing.class, - state.getClass()); + assertEquals(Pairing.class, state.getClass()); } else if (i.get() == 2) { - assertEquals(MailboxPairingState.Paired.class, - state.getClass()); + assertEquals(Paired.class, state.getClass()); } else fail("Unexpected change of state " + state.getClass()); i.getAndIncrement(); }); @@ -138,24 +169,23 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { @Test public void testAlreadyPaired() throws Exception { - testApiException(new MailboxApi.MailboxAlreadyPairedException(), - MailboxPairingState.MailboxAlreadyPaired.class); + testApiException(new MailboxAlreadyPairedException(), + MailboxAlreadyPaired.class); } @Test public void testMailboxApiException() throws Exception { - testApiException(new MailboxApi.ApiException(), - MailboxPairingState.UnexpectedError.class); + testApiException(new ApiException(), UnexpectedError.class); } @Test public void testApiIOException() throws Exception { - testApiException(new IOException(), - MailboxPairingState.ConnectionError.class); + testApiException(new IOException(), ConnectionError.class); } private void testApiException(Exception e, Class s) throws Exception { + expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); @@ -170,6 +200,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { @Test public void testDbException() throws Exception { + expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); @@ -188,8 +219,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { MailboxPairingTask task = createPairingTask(validPayload); task.run(); - task.addObserver(state -> assertEquals(state.getClass(), - MailboxPairingState.UnexpectedError.class)); + task.addObserver(state -> + assertEquals(state.getClass(), UnexpectedError.class)); } private PredicateMatcher matches(MailboxProperties p2) { @@ -206,6 +237,16 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { 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))); + }}); } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java index 4795fb739..5f90a6d5e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java @@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.WeakSingletonProvider; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import javax.annotation.Nonnull; import javax.net.SocketFactory; @@ -11,20 +10,24 @@ import javax.net.SocketFactory; import okhttp3.OkHttpClient; 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.util.StringUtils.ISO_8859_1; class MailboxTestUtils { static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) { + int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION; byte[] payloadBytes = ByteBuffer.allocate(65) - .put((byte) 32) // 1 + .put((byte) formatIdAndVersion) // 1 .put(onionBytes) // 32 .put(setupToken) // 32 .array(); - //noinspection CharsetObjectCanBeUsed - return new String(payloadBytes, Charset.forName("ISO-8859-1")); + return new String(payloadBytes, ISO_8859_1); } + // Used by mailbox integration tests static String getQrCodePayload(byte[] setupToken) { return getQrCodePayload(getRandomId(), setupToken); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/qrcode/QrCodeClassifierImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/qrcode/QrCodeClassifierImplTest.java new file mode 100644 index 000000000..201d72b7e --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/qrcode/QrCodeClassifierImplTest.java @@ -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 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 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 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 result = classifier.classifyQrCode(payload); + assertEquals(UNKNOWN, result.getFirst()); + assertEquals(0, result.getSecond().intValue()); + } +} diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java index 2a145cc31..b8cbc828f 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java @@ -12,7 +12,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.concurrent.Executor; 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_TXCLEAR; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.StringUtils.US_ASCII; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -41,7 +41,6 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { private static final Logger LOG = 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[] BAUD_RATES = { 256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200 diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java index df56be6bb..4a938d6c9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java @@ -3,12 +3,14 @@ package org.briarproject.briar.android.contact.add.nearby; import android.graphics.Bitmap; import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import androidx.annotation.Nullable; abstract class AddContactState { static class KeyAgreementListening extends AddContactState { + final Bitmap qrCode; KeyAgreementListening(Bitmap qrCode) { @@ -29,6 +31,7 @@ abstract class AddContactState { } static class ContactExchangeFinished extends AddContactState { + final ContactExchangeResult result; ContactExchangeFinished(ContactExchangeResult result) { @@ -37,25 +40,34 @@ abstract class 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) { - this.qrCodeTooOld = qrCodeTooOld; + static class WrongQrCodeType extends Failed { + + final QrCodeType qrCodeType; + + WrongQrCodeType(QrCodeType qrCodeType) { + this.qrCodeType = qrCodeType; + } } - Failed() { - this(null); + static class WrongQrCodeVersion extends Failed { + + /** + * 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 { + static class Success extends ContactExchangeResult { + final Author remoteAuthor; Success(Author remoteAuthor) { @@ -64,6 +76,7 @@ abstract class AddContactState { } static class Error extends ContactExchangeResult { + @Nullable final Author duplicateAuthor; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java index d58524e30..da7f71377 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java @@ -6,12 +6,15 @@ import android.view.MenuItem; import android.widget.Toast; import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; 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.ContactExchangeResult; 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.fragment.BaseFragment; 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 java.util.Objects.requireNonNull; 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.REFUSED; @@ -141,9 +145,15 @@ public class AddNearbyContactActivity extends BriarActivity ContactExchangeResult result = ((ContactExchangeFinished) state).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) { - Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; - onAddingContactFailed(qrCodeTooOld); + showErrorFragment(); } } @@ -170,15 +180,27 @@ public class AddNearbyContactActivity extends BriarActivity } else throw new AssertionError(); } - private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) { - if (qrCodeTooOld == null) { - showErrorFragment(); - } else { - String msg; - if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1); - else msg = getString(R.string.qr_code_too_new_1); - showNextFragment(AddNearbyContactErrorFragment.newInstance(msg)); - } + private void onMailboxQrCodeScanned() { + String title = getString(R.string.qr_code_invalid); + String msg = getString(R.string.mailbox_qr_code_for_contact); + showNextFragment( + AddNearbyContactErrorFragment.newInstance(title, msg, false)); + } + + 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() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java index 408fab4ec..129906695 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java @@ -22,6 +22,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider; 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.onSingleLinkClick; @@ -31,17 +32,22 @@ public class AddNearbyContactErrorFragment extends BaseFragment { public static final String TAG = 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 ViewModelProvider.Factory viewModelFactory; private AddNearbyContactViewModel viewModel; - public static AddNearbyContactErrorFragment newInstance(String errorMsg) { + public static AddNearbyContactErrorFragment newInstance(String title, + String errorMessage, boolean feedback) { AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment(); 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); return f; } @@ -66,19 +72,32 @@ public class AddNearbyContactErrorFragment extends BaseFragment { View v = inflater.inflate(R.layout.fragment_error_contact_exchange, container, false); - // set optional error message - TextView explanation = v.findViewById(R.id.errorMessage); + String title = null, errorMessage = null; + boolean feedback = true; Bundle args = getArguments(); - String errorMessage = args == null ? null : args.getString(ERROR_MSG); - if (errorMessage == null) { - explanation.setText(getString(R.string.add_contact_error_two_way)); - } else { - explanation.setText(args.getString(ERROR_MSG)); + if (args != null) { + title = args.getString(ARG_TITLE); + errorMessage = args.getString(ARG_ERROR_MSG); + feedback = args.getBoolean(ARG_FEEDBACK, true); + } + + 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); - onSingleLinkClick(sendFeedback, this::triggerFeedback); + if (feedback) { + // make feedback link clickable + onSingleLinkClick(sendFeedback, this::triggerFeedback); + } else { + sendFeedback.setVisibility(GONE); + } // buttons Button tryAgain = v.findViewById(R.id.tryAgainButton); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java index 18351c64c..de8148bd6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java @@ -43,6 +43,7 @@ import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.event.TransportStateEvent; +import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin; 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.Success; 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.KeyAgreementStarted; 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.QrCodeUtils; import org.briarproject.briar.android.viewmodel.LiveEvent; @@ -60,7 +65,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.nullsafety.NotNullByDefault; import java.io.IOException; -import java.nio.charset.Charset; import java.util.concurrent.Executor; 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.STARTING_STOPPING; 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.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; @@ -126,9 +131,6 @@ class AddNearbyContactViewModel extends AndroidViewModel REFUSED } - @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 - private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - private final EventBus eventBus; private final AndroidExecutor androidExecutor; private final Executor ioExecutor; @@ -376,11 +378,11 @@ class AddNearbyContactViewModel extends AndroidViewModel } else if (e instanceof KeyAgreementAbortedEvent) { LOG.info("KeyAgreementAbortedEvent received"); resetPayloadFlags(); - state.setValue(new AddContactState.Failed()); + state.setValue(new Failed()); } else if (e instanceof KeyAgreementFailedEvent) { LOG.info("KeyAgreementFailedEvent received"); 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 if (!gotLocalPayload || gotRemotePayload || currentTask == null) return; try { - byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); - if (LOG.isLoggable(INFO)) - LOG.info("Remote payload is " + payloadBytes.length + " bytes"); - Payload remotePayload = payloadParser.parse(payloadBytes); + Payload remotePayload = payloadParser.parse(result.getText()); gotRemotePayload = true; 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) { resetPayloadFlags(); - state.postValue(new AddContactState.Failed(e.isTooOld())); + state.postValue(new WrongQrCodeVersion(e.isTooOld())); } catch (IOException | IllegalArgumentException e) { LOG.log(WARNING, "QR Code Invalid", e); androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(), R.string.qr_code_invalid, LENGTH_LONG).show()); resetPayloadFlags(); - state.postValue(new AddContactState.Failed()); + state.postValue(new Failed()); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java index e69dc19de..745c59690 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java @@ -436,7 +436,7 @@ class HotspotManager { } 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 return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java index 64aaeacd7..65a69ab10 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java @@ -6,10 +6,24 @@ import android.widget.ProgressBar; import android.widget.Toast; 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.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; 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.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault; @@ -25,6 +39,9 @@ import androidx.lifecycle.ViewModelProvider; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; 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; @MethodsNotNullByDefault @@ -56,24 +73,23 @@ public class MailboxActivity extends BriarActivity { } viewModel.getPairingState().observeEvent(this, state -> { - if (state instanceof MailboxState.NotSetup) { + if (state instanceof NotSetup) { onNotSetup(); - } else if (state instanceof MailboxState.ShowDownload) { + } else if (state instanceof ShowDownload) { onShowDownload(); - } else if (state instanceof MailboxState.ScanningQrCode) { + } else if (state instanceof ScanningQrCode) { onScanningQrCode(); - } else if (state instanceof MailboxState.Pairing) { - MailboxPairingState s = - ((MailboxState.Pairing) state).pairingState; + } else if (state instanceof Pairing) { + MailboxPairingState s = ((Pairing) state).pairingState; onMailboxPairingStateChanged(s); - } else if (state instanceof MailboxState.OfflineWhenPairing) { + } else if (state instanceof OfflineWhenPairing) { onOffline(); - } else if (state instanceof MailboxState.CameraError) { + } else if (state instanceof CameraError) { onCameraError(); - } else if (state instanceof MailboxState.IsPaired) { - onIsPaired(((MailboxState.IsPaired) state).isOnline); - } else if (state instanceof MailboxState.WasUnpaired) { - MailboxState.WasUnpaired s = (MailboxState.WasUnpaired) state; + } else if (state instanceof IsPaired) { + onIsPaired(((IsPaired) state).isOnline); + } else if (state instanceof WasUnpaired) { + WasUnpaired s = (WasUnpaired) state; onUnPaired(s.tellUserToWipeMailbox); } else { throw new AssertionError("Unknown state: " + state); @@ -104,7 +120,7 @@ public class MailboxActivity extends BriarActivity { @Override public void onBackPressed() { 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 // with the mailbox. We provide a try-again button instead. supportFinishAfterTransition(); @@ -158,31 +174,44 @@ public class MailboxActivity extends BriarActivity { } Fragment f; String tag; - if (s instanceof MailboxPairingState.Pending) { - long timeStarted = ((MailboxPairingState.Pending) s).timeStarted; + if (s instanceof Pending) { + long timeStarted = ((Pending) s).timeStarted; f = MailboxConnectingFragment.newInstance(timeStarted); tag = MailboxConnectingFragment.TAG; - } else if (s instanceof MailboxPairingState.InvalidQrCode) { - f = ErrorFragment.newInstance( - R.string.mailbox_setup_qr_code_wrong_title, - R.string.mailbox_setup_qr_code_wrong_description); + } else if (s instanceof InvalidQrCode) { + InvalidQrCode i = (InvalidQrCode) s; + int errorRes; + 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; - } else if (s instanceof MailboxPairingState.MailboxAlreadyPaired) { + } else if (s instanceof MailboxAlreadyPaired) { f = ErrorFragment.newInstance( R.string.mailbox_setup_already_paired_title, R.string.mailbox_setup_already_paired_description); tag = ErrorFragment.TAG; - } else if (s instanceof MailboxPairingState.ConnectionError) { + } else if (s instanceof ConnectionError) { f = ErrorFragment.newInstance( R.string.mailbox_setup_io_error_title, R.string.mailbox_setup_io_error_description); tag = ErrorFragment.TAG; - } else if (s instanceof MailboxPairingState.UnexpectedError) { + } else if (s instanceof UnexpectedError) { f = ErrorFragment.newInstance( R.string.mailbox_setup_assertion_error_title, R.string.mailbox_setup_assertion_error_description); tag = ErrorFragment.TAG; - } else if (s instanceof MailboxPairingState.Paired) { + } else if (s instanceof Paired) { f = FinalFragment.newInstance(R.string.mailbox_setup_paired_title, R.drawable.ic_check_circle_outline, R.color.briar_brand_green, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java index a3f38c272..955436a40 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java @@ -15,6 +15,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.mailbox.MailboxManager; 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.MailboxStatus; 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.event.TransportInactiveEvent; 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.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.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.LiveEvent; @@ -113,7 +121,7 @@ class MailboxViewModel extends DbViewModel MailboxStatus mailboxStatus = mailboxManager.getMailboxStatus(txn); boolean isOnline = isTorActive(); - pairingState.postEvent(new MailboxState.IsPaired(isOnline)); + pairingState.postEvent(new IsPaired(isOnline)); status.postValue(mailboxStatus); } else { pairingState.postEvent(new NotSetup()); @@ -142,14 +150,14 @@ class MailboxViewModel extends DbViewModel @UiThread private void onTorInactive() { MailboxState lastState = pairingState.getLastValue(); - if (lastState instanceof MailboxState.IsPaired) { + if (lastState instanceof IsPaired) { // we are already paired, so use IsPaired state - pairingState.setEvent(new MailboxState.IsPaired(false)); - } else if (lastState instanceof MailboxState.Pairing) { - MailboxState.Pairing p = (MailboxState.Pairing) lastState; + pairingState.setEvent(new IsPaired(false)); + } else if (lastState instanceof Pairing) { + Pairing p = (Pairing) lastState; // check that we not just finished pairing (showing success screen) - if (!(p.pairingState instanceof MailboxPairingState.Paired)) { - pairingState.setEvent(new MailboxState.OfflineWhenPairing()); + if (!(p.pairingState instanceof Paired)) { + pairingState.setEvent(new OfflineWhenPairing()); } // else ignore offline event as user will be leaving UI flow anyway } @@ -158,15 +166,15 @@ class MailboxViewModel extends DbViewModel @UiThread void onScanButtonClicked() { if (isTorActive()) { - pairingState.setEvent(new MailboxState.ScanningQrCode()); + pairingState.setEvent(new ScanningQrCode()); } else { - pairingState.setEvent(new MailboxState.OfflineWhenPairing()); + pairingState.setEvent(new OfflineWhenPairing()); } } @UiThread void onCameraError() { - pairingState.setEvent(new MailboxState.CameraError()); + pairingState.setEvent(new CameraError()); } @Override @@ -182,7 +190,7 @@ class MailboxViewModel extends DbViewModel pairingTask = mailboxManager.startPairingTask(qrCodePayload); pairingTask.addObserver(this); } else { - pairingState.postEvent(new MailboxState.OfflineWhenPairing()); + pairingState.postEvent(new OfflineWhenPairing()); } } @@ -193,7 +201,7 @@ class MailboxViewModel extends DbViewModel LOG.info("New pairing state: " + mailboxPairingState.getClass().getSimpleName()); } - pairingState.setEvent(new MailboxState.Pairing(mailboxPairingState)); + pairingState.setEvent(new Pairing(mailboxPairingState)); } private boolean isTorActive() { @@ -203,7 +211,7 @@ class MailboxViewModel extends DbViewModel @UiThread void showDownloadFragment() { - pairingState.setEvent(new MailboxState.ShowDownload()); + pairingState.setEvent(new ShowDownload()); } @UiThread @@ -214,7 +222,7 @@ class MailboxViewModel extends DbViewModel @UiThread void checkIfOnlineWhenPaired() { boolean isOnline = isTorActive(); - pairingState.setEvent(new MailboxState.IsPaired(isOnline)); + pairingState.setEvent(new IsPaired(isOnline)); } LiveData checkConnection() { @@ -227,7 +235,7 @@ class MailboxViewModel extends DbViewModel checkConnection(success -> { boolean isOnline = isTorActive(); // 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(() -> { try { boolean wasWiped = mailboxManager.unPair(); - pairingState.postEvent(new MailboxState.WasUnpaired(!wasWiped)); + pairingState.postEvent(new WasUnpaired(!wasWiped)); } catch (DbException e) { handleException(e); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java index b5c96079a..a276d69b2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java @@ -41,7 +41,6 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private final ResultCallback callback; private Camera camera = null; - private int cameraIndex = 0; public QrCodeDecoder(AndroidExecutor androidExecutor, @IoExecutor Executor ioExecutor, ResultCallback callback) { @@ -53,14 +52,12 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback { @Override public void start(Camera camera, int cameraIndex) { this.camera = camera; - this.cameraIndex = cameraIndex; askForPreviewFrame(); } @Override public void stop() { camera = null; - cameraIndex = 0; } @UiThread diff --git a/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml b/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml index 40cc0983a..ce11de39c 100644 --- a/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml +++ b/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml @@ -51,12 +51,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_xlarge" + android:text="@string/add_contact_error_two_way" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" app:layout_constraintBottom_toTopOf="@+id/sendFeedback" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/errorTitle" - tools:text="error explanation" /> + app:layout_constraintTop_toBottomOf="@+id/errorTitle" /> The QR code is invalid 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. The QR code you have scanned comes from a newer version of Briar.\n\nPlease upgrade to the latest version and then try again. + 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. + The QR code you have scanned does not come from Briar.\n\nPlease scan the QR code shown on your contact\'s screen. Camera error Connecting to device\u2026 Authenticating with device\u2026 @@ -636,8 +638,10 @@ Connecting to Mailbox… This may take up to %1s - Wrong QR code - The scanned code is invalid. Please open the Briar Mailbox app on your Mailbox device and scan the QR code it presents. + 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. + 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. + 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. + 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. Mailbox already linked Unlink the Mailbox on your other device and try again. Could not connect diff --git a/mailbox-integration-tests/src/test/java/org/briarproject/bramble/mailbox/AbstractMailboxIntegrationTest.java b/mailbox-integration-tests/src/test/java/org/briarproject/bramble/mailbox/AbstractMailboxIntegrationTest.java index 2eb0eef4f..ee1136a52 100644 --- a/mailbox-integration-tests/src/test/java/org/briarproject/bramble/mailbox/AbstractMailboxIntegrationTest.java +++ b/mailbox-integration-tests/src/test/java/org/briarproject/bramble/mailbox/AbstractMailboxIntegrationTest.java @@ -11,7 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; 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.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; @@ -121,7 +121,7 @@ abstract class AbstractMailboxIntegrationTest CountDownLatch latch = new CountDownLatch(1); pairingTask.addObserver((state) -> { - if (state instanceof MailboxPairingState.Paired) { + if (state instanceof Paired) { latch.countDown(); } });