Merge branch '346-smaller-qr-codes' into 'master'

Encode transport properties more compactly in QR codes

The [original BQP spec](https://code.briarproject.org/akwizgran/briar/wikis/BQP) described a compact encoding for transport properties, with the goal of making the QR code as small as possible. At some point during the implementation, I asked @str4d to use TransportIds and TransportProperties instead, as described in the [current spec](https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BQP.md). That was a mistake.

Using the original format reduces the payload from 60 to 34 bytes (43% smaller) for Bluetooth only, and from 96 to 49 bytes (49% smaller) for Bluetooth and LAN. This makes it easier to scan codes from low-resolution screens using fixed-focus and/or low-resolution cameras. Using this branch I can exchange codes between the Sony Xperia Tipo (320x480 screen, fixed focus, 640x480 preview size) and the Huawei Ascend Y300 (480x800 screen, infinity focus, 1280x720 preview size).

This also removes an obstacle to implementing #558, as TransportIds are no longer included in QR codes.

Closes #346.

See merge request !394
This commit is contained in:
akwizgran
2016-11-08 17:32:26 +00:00
27 changed files with 441 additions and 202 deletions

View File

@@ -57,6 +57,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
public class ShowQrCodeFragment extends BaseEventFragment
@@ -244,8 +245,10 @@ public class ShowQrCodeFragment extends BaseEventFragment
@UiThread
private void qrCodeScanned(String content) {
try {
Payload remotePayload = payloadParser.parse(
Base64.decode(content, 0));
byte[] encoded = Base64.decode(content, 0);
if (LOG.isLoggable(INFO))
LOG.info("Remote payload is " + encoded.length + " bytes");
Payload remotePayload = payloadParser.parse(encoded);
cameraView.setVisibility(INVISIBLE);
statusView.setVisibility(VISIBLE);
status.setText(R.string.connecting_to_device);
@@ -293,9 +296,10 @@ public class ShowQrCodeFragment extends BaseEventFragment
@Override
protected Bitmap doInBackground(Void... params) {
String input =
Base64.encodeToString(payloadEncoder.encode(payload),
0);
byte[] encoded = payloadEncoder.encode(payload);
if (LOG.isLoggable(INFO))
LOG.info("Local payload is " + encoded.length + " bytes");
String input = Base64.encodeToString(encoded, 0);
return QrCodeUtils.createQrCode(dm, input);
}

View File

@@ -11,12 +11,15 @@ import android.content.IntentFilter;
import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.keyagreement.KeyAgreementConnection;
import org.briarproject.api.keyagreement.KeyAgreementListener;
import org.briarproject.api.keyagreement.TransportDescriptor;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.plugins.Backoff;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -24,6 +27,7 @@ import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.properties.TransportProperties;
import org.briarproject.util.StringUtils;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
@@ -44,6 +48,8 @@ import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
@@ -57,8 +63,11 @@ import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class DroidtoothPlugin implements DuplexPlugin {
// Share an ID with the J2SE Bluetooth plugin
@@ -217,7 +226,7 @@ class DroidtoothPlugin implements DuplexPlugin {
return UUID.fromString(uuid);
}
private void tryToClose(BluetoothServerSocket ss) {
private void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
@@ -340,9 +349,9 @@ class DroidtoothPlugin implements DuplexPlugin {
}
}
private void tryToClose(BluetoothSocket s) {
private void tryToClose(@Nullable Closeable c) {
try {
if (s != null) s.close();
if (c != null) c.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
@@ -420,7 +429,7 @@ class DroidtoothPlugin implements DuplexPlugin {
}
private void closeSockets(final List<Future<BluetoothSocket>> futures,
final BluetoothSocket chosen) {
@Nullable final BluetoothSocket chosen) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -454,6 +463,9 @@ class DroidtoothPlugin implements DuplexPlugin {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
// There's no point listening if we can't discover our own address
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
if (address.isEmpty()) return null;
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
@@ -466,23 +478,23 @@ class DroidtoothPlugin implements DuplexPlugin {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
TransportProperties p = new TransportProperties();
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
if (!StringUtils.isNullOrEmpty(address))
p.put(PROP_ADDRESS, address);
TransportDescriptor d = new TransportDescriptor(ID, p);
return new BluetoothKeyAgreementListener(d, ss);
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
descriptor.add(StringUtils.macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, TransportDescriptor d, long timeout) {
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
if (!ID.equals(d.getIdentifier())) return null;
TransportProperties p = d.getProperties();
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO))
@@ -492,6 +504,12 @@ class DroidtoothPlugin implements DuplexPlugin {
return new DroidtoothTransportConnection(this, s);
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
@@ -626,7 +644,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private final BluetoothServerSocket ss;
BluetoothKeyAgreementListener(TransportDescriptor descriptor,
BluetoothKeyAgreementListener(BdfList descriptor,
BluetoothServerSocket ss) {
super(descriptor);
this.ss = ss;

View File

@@ -7,16 +7,20 @@ import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.plugins.Backoff;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
@NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final Logger LOG =
@@ -24,6 +28,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
private final Context appContext;
@Nullable
private volatile BroadcastReceiver networkStateReceiver = null;
AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,

View File

@@ -19,11 +19,12 @@ import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.SettingsUpdatedEvent;
import org.briarproject.api.keyagreement.KeyAgreementListener;
import org.briarproject.api.keyagreement.TransportDescriptor;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.plugins.Backoff;
import org.briarproject.api.plugins.TorConstants;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
@@ -60,6 +61,7 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE;
@@ -76,6 +78,7 @@ import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.api.plugins.TorConstants.CONTROL_PORT;
import static org.briarproject.util.PrivacyUtils.scrubOnion;
@NotNullByDefault
class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final String PROP_ONION = "onion";
@@ -104,9 +107,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
@Nullable
private volatile ServerSocket socket = null;
@Nullable
private volatile Socket controlSocket = null;
@Nullable
private volatile TorControlConnection controlConnection = null;
@Nullable
private volatile BroadcastReceiver networkStateReceiver = null;
TorPlugin(Executor ioExecutor, Context appContext,
@@ -289,7 +296,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return appContext.getResources().getAssets().open("torrc");
}
private void tryToClose(Closeable c) {
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
@@ -297,7 +304,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void tryToClose(Socket s) {
private void tryToClose(@Nullable Socket s) {
try {
if (s != null) s.close();
} catch (IOException e) {
@@ -385,7 +392,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
}
private void tryToClose(ServerSocket ss) {
private void tryToClose(@Nullable ServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
@@ -574,7 +581,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, TransportDescriptor d, long timeout) {
byte[] commitment, BdfList descriptor, long timeout) {
throw new UnsupportedOperationException();
}