diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java index 6e67eb867ae034672ceea7fc4ba28f0bc245e60d..b8a9554caa6eaa3b99ab7e81febf1fbd6e366214 100644 --- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java @@ -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); } diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java index 686d15b615411ed05858061fcb46f9944c98ba68..d1ba7d858208ceea83441b838ca8093d30b6bca2 100644 --- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java +++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java @@ -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; diff --git a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java index 598e914b6d695d1373fba80145c392e2abe32e7f..b96aefa0893fa8c2dcf1e87d02649500d9fc7841 100644 --- a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java @@ -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, diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index e66e0a6583e9ca7acfff0a018e5411d621ea9422..2a8b58991bcafe11f4b2e4ed24d33286aca18519 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -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(); } diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java index f239111269124e8d938f15bbc5561a4670ed5d0a..9961a2b9949a74dde7ec7b6ff1fc3d645518347d 100644 --- a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java +++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java @@ -4,7 +4,7 @@ package org.briarproject.api.keyagreement; public interface KeyAgreementConstants { /** The current version of the BQP protocol. */ - byte PROTOCOL_VERSION = 1; + byte PROTOCOL_VERSION = 2; /** The length of the record header in bytes. */ int RECORD_HEADER_LENGTH = 4; @@ -16,4 +16,10 @@ public interface KeyAgreementConstants { int COMMIT_LENGTH = 16; long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds + + /** The transport identifier for Bluetooth. */ + int TRANSPORT_ID_BLUETOOTH = 0; + + /** The transport identifier for LAN. */ + int TRANSPORT_ID_LAN = 1; } diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java index 05163614cc13d2ab383c1041813fc48b63977ae0..41b18a7de3264de851655550a3ba2db41f00ed4b 100644 --- a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java +++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java @@ -1,5 +1,7 @@ package org.briarproject.api.keyagreement; +import org.briarproject.api.data.BdfList; + import java.util.concurrent.Callable; /** @@ -7,9 +9,9 @@ import java.util.concurrent.Callable; */ public abstract class KeyAgreementListener { - private final TransportDescriptor descriptor; + private final BdfList descriptor; - public KeyAgreementListener(TransportDescriptor descriptor) { + public KeyAgreementListener(BdfList descriptor) { this.descriptor = descriptor; } @@ -17,7 +19,7 @@ public abstract class KeyAgreementListener { * Returns the descriptor that a remote peer can use to connect to this * listener. */ - public TransportDescriptor getDescriptor() { + public BdfList getDescriptor() { return descriptor; } diff --git a/briar-api/src/org/briarproject/api/keyagreement/Payload.java b/briar-api/src/org/briarproject/api/keyagreement/Payload.java index 0c749da53ec9e2a70c652cc3dcdf70f5a9d079a6..60cbb45a363115f782556cdde20d00a15d28c7dd 100644 --- a/briar-api/src/org/briarproject/api/keyagreement/Payload.java +++ b/briar-api/src/org/briarproject/api/keyagreement/Payload.java @@ -1,29 +1,40 @@ package org.briarproject.api.keyagreement; import org.briarproject.api.Bytes; +import org.briarproject.api.TransportId; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.nullsafety.NotNullByDefault; -import java.util.List; +import java.util.Map; + +import javax.annotation.concurrent.Immutable; /** * A BQP payload. */ +@Immutable +@NotNullByDefault public class Payload implements Comparable<Payload> { private final Bytes commitment; - private final List<TransportDescriptor> descriptors; + private final Map<TransportId, BdfList> descriptors; - public Payload(byte[] commitment, List<TransportDescriptor> descriptors) { + public Payload(byte[] commitment, Map<TransportId, BdfList> descriptors) { this.commitment = new Bytes(commitment); this.descriptors = descriptors; } - /** Returns the commitment contained in this payload. */ + /** + * Returns the commitment contained in this payload. + */ public byte[] getCommitment() { return commitment.getBytes(); } - /** Returns the transport descriptors contained in this payload. */ - public List<TransportDescriptor> getTransportDescriptors() { + /** + * Returns the transport descriptors contained in this payload. + */ + public Map<TransportId, BdfList> getTransportDescriptors() { return descriptors; } diff --git a/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java b/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java index 0df9c653d418d464af843f74547981bc06f7a223..0ada113f360d046ed81100f4a3fe83a3b7bbb1eb 100644 --- a/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java +++ b/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java @@ -1,7 +1,10 @@ package org.briarproject.api.keyagreement; +import org.briarproject.api.nullsafety.NotNullByDefault; + import java.io.IOException; +@NotNullByDefault public interface PayloadParser { Payload parse(byte[] raw) throws IOException; diff --git a/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java b/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java deleted file mode 100644 index cdaa5a579464e7287f06ada392cb16d9b2ffcb5d..0000000000000000000000000000000000000000 --- a/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.briarproject.api.keyagreement; - -import org.briarproject.api.TransportId; -import org.briarproject.api.properties.TransportProperties; - -/** - * Describes how to connect to a device over a short-range transport. - */ -public class TransportDescriptor { - - private final TransportId id; - private final TransportProperties properties; - - public TransportDescriptor(TransportId id, TransportProperties properties) { - this.id = id; - this.properties = properties; - } - - /** Returns the transport identifier. */ - public TransportId getIdentifier() { - return id; - } - - /** Returns the transport properties. */ - public TransportProperties getProperties() { - return properties; - } -} diff --git a/briar-api/src/org/briarproject/api/plugins/Plugin.java b/briar-api/src/org/briarproject/api/plugins/Plugin.java index 393fa27ea1737e86a8c794eacc3d4b506a7a6eb7..e54822cceccd94854febc9be2cc247bb9fd17f0e 100644 --- a/briar-api/src/org/briarproject/api/plugins/Plugin.java +++ b/briar-api/src/org/briarproject/api/plugins/Plugin.java @@ -2,28 +2,42 @@ package org.briarproject.api.plugins; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.nullsafety.NotNullByDefault; import java.io.IOException; import java.util.Collection; +@NotNullByDefault public interface Plugin { - /** Returns the plugin's transport identifier. */ + /** + * Returns the plugin's transport identifier. + */ TransportId getId(); - /** Returns the transport's maximum latency in milliseconds. */ + /** + * Returns the transport's maximum latency in milliseconds. + */ int getMaxLatency(); - /** Returns the transport's maximum idle time in milliseconds. */ + /** + * Returns the transport's maximum idle time in milliseconds. + */ int getMaxIdleTime(); - /** Starts the plugin and returns true if it started successfully. */ + /** + * Starts the plugin and returns true if it started successfully. + */ boolean start() throws IOException; - /** Stops the plugin. */ + /** + * Stops the plugin. + */ void stop() throws IOException; - /** Returns true if the plugin is running. */ + /** + * Returns true if the plugin is running. + */ boolean isRunning(); /** diff --git a/briar-api/src/org/briarproject/api/plugins/PluginManager.java b/briar-api/src/org/briarproject/api/plugins/PluginManager.java index 18e71c2ec2e75ae4790c7e1580b7b95e75b1700c..3f1fcf31a22d3a00833b944c64f78622e73ed9f6 100644 --- a/briar-api/src/org/briarproject/api/plugins/PluginManager.java +++ b/briar-api/src/org/briarproject/api/plugins/PluginManager.java @@ -1,21 +1,26 @@ package org.briarproject.api.plugins; import org.briarproject.api.TransportId; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.simplex.SimplexPlugin; import java.util.Collection; +import javax.annotation.Nullable; + /** * Responsible for starting transport plugins at startup and stopping them at * shutdown. */ +@NotNullByDefault public interface PluginManager { /** * Returns the plugin for the given transport, or null if no such plugin * has been created. */ + @Nullable Plugin getPlugin(TransportId t); /** diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java index ff7db3b51d78526d10b2a54bcc342c3a691583c2..c45d5bab3b53ff69b5f712b7d0d8b71ddc3f2cd1 100644 --- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java +++ b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java @@ -2,21 +2,30 @@ package org.briarproject.api.plugins.duplex; import org.briarproject.api.contact.ContactId; import org.briarproject.api.crypto.PseudoRandom; +import org.briarproject.api.data.BdfList; import org.briarproject.api.keyagreement.KeyAgreementListener; -import org.briarproject.api.keyagreement.TransportDescriptor; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.plugins.Plugin; -/** An interface for transport plugins that support duplex communication. */ +import javax.annotation.Nullable; + +/** + * An interface for transport plugins that support duplex communication. + */ +@NotNullByDefault public interface DuplexPlugin extends Plugin { /** * Attempts to create and return a connection to the given contact using * the current transport and configuration properties. Returns null if a - * connection could not be created. + * connection cannot be created. */ + @Nullable DuplexTransportConnection createConnection(ContactId c); - /** Returns true if the plugin supports exchanging invitations. */ + /** + * Returns true if the plugin supports exchanging invitations. + */ boolean supportsInvitations(); /** @@ -24,21 +33,27 @@ public interface DuplexPlugin extends Plugin { * peer. Returns null if no connection can be established within the given * time. */ + @Nullable DuplexTransportConnection createInvitationConnection(PseudoRandom r, long timeout, boolean alice); - /** Returns true if the plugin supports short-range key agreement. */ + /** + * Returns true if the plugin supports short-range key agreement. + */ boolean supportsKeyAgreement(); /** - * Returns a listener that can be used to perform key agreement. + * Attempts to create and return a listener that can be used to perform key + * agreement. Returns null if a listener cannot be created. */ + @Nullable KeyAgreementListener createKeyAgreementListener(byte[] localCommitment); /** * Attempts to connect to the remote peer specified in the given descriptor. * Returns null if no connection can be established within the given time. */ + @Nullable DuplexTransportConnection createKeyAgreementConnection( - byte[] remoteCommitment, TransportDescriptor d, long timeout); + byte[] remoteCommitment, BdfList descriptor, long timeout); } diff --git a/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPlugin.java b/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPlugin.java index 36e2ed438393090fa71cea2dde44f4b744298326..cf9f5ebec3543cad0407d9c25d0c72d44bffc831 100644 --- a/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPlugin.java +++ b/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPlugin.java @@ -1,24 +1,32 @@ package org.briarproject.api.plugins.simplex; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.TransportConnectionReader; import org.briarproject.api.plugins.TransportConnectionWriter; -/** An interface for transport plugins that support simplex communication. */ +import javax.annotation.Nullable; + +/** + * An interface for transport plugins that support simplex communication. + */ +@NotNullByDefault public interface SimplexPlugin extends Plugin { /** * Attempts to create and return a reader for the given contact using the * current transport and configuration properties. Returns null if a reader - * could not be created. + * cannot be created. */ + @Nullable TransportConnectionReader createReader(ContactId c); /** * Attempts to create and return a writer for the given contact using the * current transport and configuration properties. Returns null if a writer - * could not be created. + * cannot be created. */ + @Nullable TransportConnectionWriter createWriter(ContactId c); } diff --git a/briar-core/src/org/briarproject/keyagreement/KeyAgreementConnector.java b/briar-core/src/org/briarproject/keyagreement/KeyAgreementConnector.java index 190a138502f9dce896ed93834fac2485b1f7cd4e..44636b31d65606fb46ba16a5be21b9940cd06754 100644 --- a/briar-core/src/org/briarproject/keyagreement/KeyAgreementConnector.java +++ b/briar-core/src/org/briarproject/keyagreement/KeyAgreementConnector.java @@ -1,11 +1,13 @@ package org.briarproject.keyagreement; +import org.briarproject.api.TransportId; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.KeyPair; +import org.briarproject.api.data.BdfList; import org.briarproject.api.keyagreement.KeyAgreementConnection; import org.briarproject.api.keyagreement.KeyAgreementListener; import org.briarproject.api.keyagreement.Payload; -import org.briarproject.api.keyagreement.TransportDescriptor; +import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.PluginManager; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; @@ -14,7 +16,10 @@ import org.briarproject.api.system.Clock; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; @@ -68,14 +73,13 @@ class KeyAgreementConnector { byte[] commitment = crypto.deriveKeyCommitment( localKeyPair.getPublic().getEncoded()); // Start all listeners and collect their descriptors - List<TransportDescriptor> descriptors = - new ArrayList<TransportDescriptor>(); + Map<TransportId, BdfList> descriptors = + new HashMap<TransportId, BdfList>(); for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) { - KeyAgreementListener l = plugin.createKeyAgreementListener( - commitment); + KeyAgreementListener l = + plugin.createKeyAgreementListener(commitment); if (l != null) { - TransportDescriptor d = l.getDescriptor(); - descriptors.add(d); + descriptors.put(plugin.getId(), l.getDescriptor()); pending.add(connect.submit(new ReadableTask(l.listen()))); listeners.add(l); } @@ -100,13 +104,16 @@ class KeyAgreementConnector { // Start connecting over supported transports LOG.info("Starting outgoing BQP connections"); - for (TransportDescriptor d : remotePayload.getTransportDescriptors()) { - DuplexPlugin plugin = (DuplexPlugin) pluginManager.getPlugin( - d.getIdentifier()); - if (plugin != null) + Map<TransportId, BdfList> descriptors = + remotePayload.getTransportDescriptors(); + for (Entry<TransportId, BdfList> e : descriptors.entrySet()) { + Plugin p = pluginManager.getPlugin(e.getKey()); + if (p instanceof DuplexPlugin) { + DuplexPlugin plugin = (DuplexPlugin) p; pending.add(connect.submit(new ReadableTask( new ConnectorTask(plugin, remotePayload.getCommitment(), - d, end)))); + e.getValue(), end)))); + } } // Get chosen connection @@ -170,12 +177,12 @@ class KeyAgreementConnector { private class ConnectorTask implements Callable<KeyAgreementConnection> { private final byte[] commitment; - private final TransportDescriptor descriptor; + private final BdfList descriptor; private final long end; private final DuplexPlugin plugin; private ConnectorTask(DuplexPlugin plugin, byte[] commitment, - TransportDescriptor descriptor, long end) { + BdfList descriptor, long end) { this.plugin = plugin; this.commitment = commitment; this.descriptor = descriptor; diff --git a/briar-core/src/org/briarproject/keyagreement/PayloadEncoderImpl.java b/briar-core/src/org/briarproject/keyagreement/PayloadEncoderImpl.java index 8a26f9405bd17128dc7cfde4ca89bb90859f13d0..32342993c1810479f8ca441615a7e9c611639884 100644 --- a/briar-core/src/org/briarproject/keyagreement/PayloadEncoderImpl.java +++ b/briar-core/src/org/briarproject/keyagreement/PayloadEncoderImpl.java @@ -1,24 +1,30 @@ package org.briarproject.keyagreement; +import org.briarproject.api.TransportId; +import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfWriter; import org.briarproject.api.data.BdfWriterFactory; import org.briarproject.api.keyagreement.Payload; import org.briarproject.api.keyagreement.PayloadEncoder; -import org.briarproject.api.keyagreement.TransportDescriptor; +import org.briarproject.api.nullsafety.NotNullByDefault; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Map; +import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; +@Immutable +@NotNullByDefault class PayloadEncoderImpl implements PayloadEncoder { private final BdfWriterFactory bdfWriterFactory; @Inject - public PayloadEncoderImpl(BdfWriterFactory bdfWriterFactory) { + PayloadEncoderImpl(BdfWriterFactory bdfWriterFactory) { this.bdfWriterFactory = bdfWriterFactory; } @@ -30,18 +36,13 @@ class PayloadEncoderImpl implements PayloadEncoder { w.writeListStart(); // Payload start w.writeLong(PROTOCOL_VERSION); w.writeRaw(p.getCommitment()); - w.writeListStart(); // Descriptors start - for (TransportDescriptor d : p.getTransportDescriptors()) { - w.writeListStart(); - w.writeString(d.getIdentifier().getString()); - w.writeDictionary(d.getProperties()); - w.writeListEnd(); - } - w.writeListEnd(); // Descriptors end + Map<TransportId, BdfList> descriptors = p.getTransportDescriptors(); + for (BdfList descriptor : descriptors.values()) + w.writeList(descriptor); w.writeListEnd(); // Payload end } catch (IOException e) { // Shouldn't happen with ByteArrayOutputStream - throw new RuntimeException(e); + throw new AssertionError(e); } return out.toByteArray(); } diff --git a/briar-core/src/org/briarproject/keyagreement/PayloadParserImpl.java b/briar-core/src/org/briarproject/keyagreement/PayloadParserImpl.java index d13f9ff73228af96b26a814a6d55e19a3e4d9a2b..105311811bd3268928083ec2bbb5f40d746dc0e4 100644 --- a/briar-core/src/org/briarproject/keyagreement/PayloadParserImpl.java +++ b/briar-core/src/org/briarproject/keyagreement/PayloadParserImpl.java @@ -2,30 +2,34 @@ package org.briarproject.keyagreement; import org.briarproject.api.FormatException; import org.briarproject.api.TransportId; +import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfReader; import org.briarproject.api.data.BdfReaderFactory; import org.briarproject.api.keyagreement.Payload; import org.briarproject.api.keyagreement.PayloadParser; -import org.briarproject.api.keyagreement.TransportDescriptor; -import org.briarproject.api.properties.TransportProperties; +import org.briarproject.api.nullsafety.NotNullByDefault; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; -import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; +import static org.briarproject.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; +import static org.briarproject.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; +@Immutable +@NotNullByDefault class PayloadParserImpl implements PayloadParser { private final BdfReaderFactory bdfReaderFactory; @Inject - public PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { + PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { this.bdfReaderFactory = bdfReaderFactory; } @@ -33,36 +37,28 @@ class PayloadParserImpl implements PayloadParser { public Payload parse(byte[] raw) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream(raw); BdfReader r = bdfReaderFactory.createReader(in); - r.readListStart(); // Payload start - int proto = (int) r.readLong(); - if (proto != PROTOCOL_VERSION) - throw new FormatException(); - byte[] commitment = r.readRaw(COMMIT_LENGTH); - if (commitment.length != COMMIT_LENGTH) - throw new FormatException(); - List<TransportDescriptor> descriptors = new ArrayList<TransportDescriptor>(); - r.readListStart(); // Descriptors start - while (r.hasList()) { - r.readListStart(); - while (!r.hasListEnd()) { - TransportId id = - new TransportId(r.readString(MAX_PROPERTY_LENGTH)); - TransportProperties p = new TransportProperties(); - r.readDictionaryStart(); - while (!r.hasDictionaryEnd()) { - String key = r.readString(MAX_PROPERTY_LENGTH); - String value = r.readString(MAX_PROPERTY_LENGTH); - p.put(key, value); - } - r.readDictionaryEnd(); - descriptors.add(new TransportDescriptor(id, p)); + // The payload is a BDF list with two or more elements + BdfList payload = r.readList(); + if (payload.size() < 2) throw new FormatException(); + if (!r.eof()) throw new FormatException(); + // First element: the protocol version + long protocolVersion = payload.getLong(0); + if (protocolVersion != PROTOCOL_VERSION) throw new FormatException(); + // Second element: the public key commitment + byte[] commitment = payload.getRaw(1); + if (commitment.length != COMMIT_LENGTH) throw new FormatException(); + // Remaining elements: transport descriptors + Map<TransportId, BdfList> recognised = + new HashMap<TransportId, BdfList>(); + for (int i = 2; i < payload.size(); i++) { + BdfList descriptor = payload.getList(i); + long transportId = descriptor.getLong(0); + if (transportId == TRANSPORT_ID_BLUETOOTH) { + recognised.put(new TransportId("bt"), descriptor); + } else if (transportId == TRANSPORT_ID_LAN) { + recognised.put(new TransportId("lan"), descriptor); } - r.readListEnd(); } - r.readListEnd(); // Descriptors end - r.readListEnd(); // Payload end - if (!r.eof()) - throw new FormatException(); - return new Payload(commitment, descriptors); + return new Payload(commitment, recognised); } } diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java index eaefc8e5f2f54ee71f509ea9e504bfc0a294b870..f9aa0ec1796bcb1fe041063d1558b2c881a77259 100644 --- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java +++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java @@ -9,6 +9,7 @@ import org.briarproject.api.event.TransportEnabledEvent; import org.briarproject.api.lifecycle.IoExecutor; import org.briarproject.api.lifecycle.Service; import org.briarproject.api.lifecycle.ServiceException; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.plugins.ConnectionManager; import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.PluginCallback; @@ -42,11 +43,14 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; +import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +@ThreadSafe +@NotNullByDefault class PluginManagerImpl implements PluginManager, Service { private static final Logger LOG = diff --git a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java index 9819235539b04318dcef32152617f4e32f3b7254..108db3c7b25ef17715d87ab4357701e275cc1c73 100644 --- a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java +++ b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java @@ -1,6 +1,7 @@ package org.briarproject.plugins.file; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.plugins.TransportConnectionReader; import org.briarproject.api.plugins.TransportConnectionWriter; import org.briarproject.api.plugins.simplex.SimplexPlugin; @@ -17,10 +18,13 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; +import javax.annotation.Nullable; + import static java.util.logging.Level.WARNING; import static org.briarproject.api.transport.TransportConstants.MIN_STREAM_LENGTH; -public abstract class FilePlugin implements SimplexPlugin { +@NotNullByDefault +abstract class FilePlugin implements SimplexPlugin { private static final Logger LOG = Logger.getLogger(FilePlugin.class.getName()); @@ -32,6 +36,7 @@ public abstract class FilePlugin implements SimplexPlugin { protected volatile boolean running = false; + @Nullable protected abstract File chooseOutputDirectory(); protected abstract Collection<File> findFilesByName(String filename); protected abstract void writerFinished(File f); @@ -82,6 +87,7 @@ public abstract class FilePlugin implements SimplexPlugin { return filename.toLowerCase(Locale.US).matches("[a-z]{8}\\.dat"); } + @Nullable private TransportConnectionWriter createWriter(String filename) { if (!running) return null; File dir = chooseOutputDirectory(); diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java index 87ba78fab88d5a8c40b11303b395ed83860bd763..e3527f74dd52129501dbc42b183677c5b91a53d5 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java @@ -1,10 +1,12 @@ package org.briarproject.plugins.tcp; +import org.briarproject.api.FormatException; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; +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.NotNullByDefault; import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; @@ -19,6 +21,7 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -29,8 +32,11 @@ import java.util.logging.Logger; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static org.briarproject.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; +import static org.briarproject.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.util.PrivacyUtils.scrubSocketAddress; +@NotNullByDefault class LanTcpPlugin extends TcpPlugin { static final TransportId ID = new TransportId("lan"); @@ -40,7 +46,6 @@ class LanTcpPlugin extends TcpPlugin { private static final int MAX_ADDRESSES = 5; private static final String PROP_IP_PORTS = "ipPorts"; - private static final String PROP_IP_PORT = "ipPort"; private static final String SEPARATOR = ","; LanTcpPlugin(Executor ioExecutor, Backoff backoff, @@ -186,23 +191,26 @@ class LanTcpPlugin extends TcpPlugin { LOG.info("Could not bind server socket for key agreement"); return null; } - TransportProperties p = new TransportProperties(); - SocketAddress local = ss.getLocalSocketAddress(); - p.put(PROP_IP_PORT, getIpPortString((InetSocketAddress) local)); - TransportDescriptor d = new TransportDescriptor(ID, p); - return new LanKeyAgreementListener(d, ss); + BdfList descriptor = new BdfList(); + descriptor.add(TRANSPORT_ID_LAN); + InetSocketAddress local = + (InetSocketAddress) ss.getLocalSocketAddress(); + descriptor.add(local.getAddress().getAddress()); + descriptor.add(local.getPort()); + return new LanKeyAgreementListener(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 ipPort = p.get(PROP_IP_PORT); - InetSocketAddress remote = parseSocketAddress(ipPort); - if (remote == null) return null; + InetSocketAddress remote; + try { + remote = parseSocketAddress(descriptor); + } catch (FormatException e) { + LOG.info("Invalid IP/port in key agreement descriptor"); + return null; + } if (!isConnectable(remote)) { if (LOG.isLoggable(INFO)) { SocketAddress local = socket.getLocalSocketAddress(); @@ -228,11 +236,25 @@ class LanTcpPlugin extends TcpPlugin { } } + private InetSocketAddress parseSocketAddress(BdfList descriptor) + throws FormatException { + byte[] address = descriptor.getRaw(1); + int port = descriptor.getLong(2).intValue(); + if (port < 1 || port > MAX_16_BIT_UNSIGNED) throw new FormatException(); + try { + InetAddress addr = InetAddress.getByAddress(address); + return new InetSocketAddress(addr, port); + } catch (UnknownHostException e) { + // Invalid address length + throw new FormatException(); + } + } + private class LanKeyAgreementListener extends KeyAgreementListener { private final ServerSocket ss; - public LanKeyAgreementListener(TransportDescriptor descriptor, + private LanKeyAgreementListener(BdfList descriptor, ServerSocket ss) { super(descriptor); this.ss = ss; diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java index 9ba84875bd40da38467b0899af4d566d6c26df2b..76da5bf7dd8899d0fdf0da01cf8ae35180c49939 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java @@ -2,8 +2,10 @@ package org.briarproject.plugins.tcp; import org.briarproject.api.contact.ContactId; import org.briarproject.api.crypto.PseudoRandom; +import org.briarproject.api.data.BdfList; 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; @@ -28,10 +30,14 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import java.util.regex.Pattern; +import javax.annotation.Nullable; + import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.util.PrivacyUtils.scrubSocketAddress; +@MethodsNotNullByDefault +@ParametersNotNullByDefault abstract class TcpPlugin implements DuplexPlugin { private static final Pattern DOTTED_QUAD = @@ -141,7 +147,7 @@ abstract class TcpPlugin implements DuplexPlugin { }); } - protected void tryToClose(ServerSocket ss) { + protected void tryToClose(@Nullable ServerSocket ss) { try { if (ss != null) ss.close(); } catch (IOException e) { @@ -252,6 +258,7 @@ abstract class TcpPlugin implements DuplexPlugin { return null; } + @Nullable protected InetSocketAddress parseSocketAddress(String ipPort) { if (StringUtils.isNullOrEmpty(ipPort)) return null; String[] split = ipPort.split(":"); @@ -298,7 +305,7 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, TransportDescriptor d, long timeout) { + byte[] commitment, BdfList descriptor, long timeout) { throw new UnsupportedOperationException(); } diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java index 82c3221a5b65cf99311a255321dbf87b16cd173d..3f297d14825d9f5120bd15461f1a310ee86494b0 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java @@ -2,6 +2,8 @@ package org.briarproject.plugins.tcp; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.properties.TransportProperties; @@ -14,6 +16,8 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; +@MethodsNotNullByDefault +@ParametersNotNullByDefault class WanTcpPlugin extends TcpPlugin { static final TransportId ID = new TransportId("wan"); diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java index 5acedc34e419be205764b27537e55812732ac613..af622cc270130458cf276941667b011ca55db5d6 100644 --- a/briar-core/src/org/briarproject/util/StringUtils.java +++ b/briar-core/src/org/briarproject/util/StringUtils.java @@ -1,24 +1,34 @@ package org.briarproject.util; +import org.briarproject.api.nullsafety.NotNullByDefault; + import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Collection; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; import static java.nio.charset.CodingErrorAction.IGNORE; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +@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); private static final char[] HEX = new char[] { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - public static boolean isNullOrEmpty(String s) { + public static boolean isNullOrEmpty(@Nullable String s) { return s == null || s.length() == 0; } @@ -61,7 +71,9 @@ public class StringUtils { return fromUtf8(utf8, 0, maxUtf8Length); } - /** Converts the given byte array to a hex character array. */ + /** + * Converts the given byte array to a hex character array. + */ private static char[] toHexChars(byte[] bytes) { char[] hex = new char[bytes.length * 2]; for (int i = 0, j = 0; i < bytes.length; i++) { @@ -71,12 +83,16 @@ public class StringUtils { return hex; } - /** Converts the given byte array to a hex string. */ + /** + * Converts the given byte array to a hex string. + */ public static String toHexString(byte[] bytes) { return new String(toHexChars(bytes)); } - /** Converts the given hex string to a byte array. */ + /** + * Converts the given hex string to a byte array. + */ public static byte[] fromHexString(String hex) { int len = hex.length(); if (len % 2 != 0) @@ -107,4 +123,20 @@ public class StringUtils { public static boolean utf8IsTooLong(String s, int maxLength) { return toUtf8(s).length > maxLength; } + + public static byte[] macToBytes(String mac) { + if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException(); + return fromHexString(mac.replaceAll(":", "")); + } + + public static String macToString(byte[] mac) { + if (mac.length != 6) throw new IllegalArgumentException(); + StringBuilder s = new StringBuilder(); + for (byte b : mac) { + if (s.length() > 0) s.append(':'); + s.append(HEX[(b >> 4) & 0xF]); + s.append(HEX[b & 0xF]); + } + return s.toString(); + } } diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java index f7b639bba93d5df223d729277ba8bdc39a7c9214..c945ca8154803711a0cddf181c3ca5dd66a80403 100644 --- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java @@ -1,11 +1,14 @@ package org.briarproject.plugins.bluetooth; +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; @@ -33,6 +36,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; +import javax.annotation.Nullable; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DiscoveryAgent; import javax.bluetooth.LocalDevice; @@ -44,7 +48,10 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static javax.bluetooth.DiscoveryAgent.GIAC; +import static org.briarproject.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; +@MethodsNotNullByDefault +@ParametersNotNullByDefault class BluetoothPlugin implements DuplexPlugin { // Share an ID with the Android Bluetooth plugin @@ -161,7 +168,7 @@ class BluetoothPlugin implements DuplexPlugin { return uuid; } - private void tryToClose(StreamConnectionNotifier ss) { + private void tryToClose(@Nullable StreamConnectionNotifier ss) { try { if (ss != null) ss.close(); } catch (IOException e) { @@ -330,7 +337,7 @@ class BluetoothPlugin implements DuplexPlugin { } private void closeSockets(final List<Future<StreamConnection>> futures, - final StreamConnection chosen) { + @Nullable final StreamConnection chosen) { ioExecutor.execute(new Runnable() { @Override public void run() { @@ -382,21 +389,24 @@ class BluetoothPlugin implements DuplexPlugin { tryToClose(ss); return null; } - TransportProperties p = new TransportProperties(); - p.put(PROP_ADDRESS, localDevice.getBluetoothAddress()); - TransportDescriptor d = new TransportDescriptor(ID, p); - return new BluetoothKeyAgreementListener(d, ss); + BdfList descriptor = new BdfList(); + descriptor.add(TRANSPORT_ID_BLUETOOTH); + String address = localDevice.getBluetoothAddress(); + 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 String uuid = UUID.nameUUIDFromBytes(commitment).toString(); if (LOG.isLoggable(INFO)) @@ -407,6 +417,12 @@ class BluetoothPlugin implements DuplexPlugin { return new BluetoothTransportConnection(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 void makeDeviceDiscoverable() { // Try to make the device discoverable (requires root on Linux) try { @@ -491,7 +507,7 @@ class BluetoothPlugin implements DuplexPlugin { private final StreamConnectionNotifier ss; - BluetoothKeyAgreementListener(TransportDescriptor descriptor, + BluetoothKeyAgreementListener(BdfList descriptor, StreamConnectionNotifier ss) { super(descriptor); this.ss = ss; diff --git a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java index 926f8d314800774f623b08688439bcc4dca36ca2..4a463eb3eb02c5a49b12f20fdc7a56e4e943e2e8 100644 --- a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java @@ -2,6 +2,7 @@ package org.briarproject.plugins.file; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.plugins.simplex.SimplexPluginCallback; import java.io.File; @@ -15,6 +16,7 @@ import java.util.logging.Logger; import static java.util.logging.Level.WARNING; +@NotNullByDefault class RemovableDrivePlugin extends FilePlugin implements RemovableDriveMonitor.Callback { diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java index ce2d2d57e7c0cf5ed7e4c19c5de94b8aa6344f43..fe3b89e2c32f99139694d4e7fe519366d796f342 100644 --- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java @@ -3,8 +3,10 @@ package org.briarproject.plugins.modem; 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.KeyAgreementListener; -import org.briarproject.api.keyagreement.TransportDescriptor; +import org.briarproject.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.api.plugins.duplex.AbstractDuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; @@ -22,6 +24,8 @@ import java.util.logging.Logger; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +@MethodsNotNullByDefault +@ParametersNotNullByDefault class ModemPlugin implements DuplexPlugin, Modem.Callback { static final TransportId ID = new TransportId("modem"); @@ -114,7 +118,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { throw new UnsupportedOperationException(); } - boolean resetModem() { + private boolean resetModem() { if (!running) return false; for (String portName : serialPortList.getPortNames()) { if (LOG.isLoggable(INFO)) @@ -184,7 +188,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, TransportDescriptor d, long timeout) { + byte[] commitment, BdfList descriptor, long timeout) { throw new UnsupportedOperationException(); } diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java index 622bb59aa0d7e6dec97374ad96eae4912219414f..2f8e349c4caeae61580035583e058b6d761bae0a 100644 --- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java +++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java @@ -2,9 +2,9 @@ package org.briarproject.plugins.tcp; import org.briarproject.BriarTestCase; import org.briarproject.api.contact.ContactId; +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.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; @@ -27,11 +27,12 @@ import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.briarproject.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; +import static org.briarproject.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -188,23 +189,22 @@ public class LanTcpPluginTest extends BriarTestCase { 0, 0); plugin.start(); assertTrue(callback.propertiesLatch.await(5, SECONDS)); - KeyAgreementListener kal = plugin.createKeyAgreementListener(null); + KeyAgreementListener kal = + plugin.createKeyAgreementListener(new byte[COMMIT_LENGTH]); + assertNotNull(kal); Callable<KeyAgreementConnection> c = kal.listen(); FutureTask<KeyAgreementConnection> f = new FutureTask<>(c); new Thread(f).start(); // The plugin should have bound a socket and stored the port number - TransportDescriptor d = kal.getDescriptor(); - TransportProperties p = d.getProperties(); - String ipPort = p.get("ipPort"); - assertNotNull(ipPort); - String[] split = ipPort.split(":"); - assertEquals(2, split.length); - String addrString = split[0], portString = split[1]; - InetAddress addr = InetAddress.getByName(addrString); + BdfList descriptor = kal.getDescriptor(); + assertEquals(3, descriptor.size()); + assertEquals(TRANSPORT_ID_LAN, descriptor.getLong(0).longValue()); + byte[] address = descriptor.getRaw(1); + InetAddress addr = InetAddress.getByAddress(address); assertTrue(addr instanceof Inet4Address); assertFalse(addr.isLoopbackAddress()); assertTrue(addr.isLinkLocalAddress() || addr.isSiteLocalAddress()); - int port = Integer.parseInt(portString); + int port = descriptor.getLong(2).intValue(); assertTrue(port > 0 && port < 65536); // The plugin should be listening on the port InetSocketAddress socketAddr = new InetSocketAddress(addr, port); @@ -240,7 +240,6 @@ public class LanTcpPluginTest extends BriarTestCase { // Listen on the same interface as the plugin final ServerSocket ss = new ServerSocket(); ss.bind(new InetSocketAddress(addrString, 0), 10); - int port = ss.getLocalPort(); final CountDownLatch latch = new CountDownLatch(1); final AtomicBoolean error = new AtomicBoolean(false); new Thread() { @@ -255,12 +254,15 @@ public class LanTcpPluginTest extends BriarTestCase { } }.start(); // Tell the plugin about the port - TransportProperties p = new TransportProperties(); - p.put("ipPort", addrString + ":" + port); - TransportDescriptor desc = new TransportDescriptor(plugin.getId(), p); + BdfList descriptor = new BdfList(); + descriptor.add(TRANSPORT_ID_LAN); + InetSocketAddress local = + (InetSocketAddress) ss.getLocalSocketAddress(); + descriptor.add(local.getAddress().getAddress()); + descriptor.add(local.getPort()); // Connect to the port - DuplexTransportConnection d = - plugin.createKeyAgreementConnection(null, desc, 5000); + DuplexTransportConnection d = plugin.createKeyAgreementConnection( + new byte[COMMIT_LENGTH], descriptor, 5000); assertNotNull(d); // Check that the connection was accepted assertTrue(latch.await(5, SECONDS)); @@ -291,61 +293,76 @@ public class LanTcpPluginTest extends BriarTestCase { private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final TransportProperties local = new TransportProperties(); + @Override public Settings getSettings() { return new Settings(); } + @Override public TransportProperties getLocalProperties() { return local; } + @Override public Map<ContactId, TransportProperties> getRemoteProperties() { return remote; } + @Override public void mergeSettings(Settings s) { } + @Override public void mergeLocalProperties(TransportProperties p) { local.putAll(p); propertiesLatch.countDown(); } + @Override public int showChoice(String[] options, String... message) { return -1; } + @Override public boolean showConfirmationMessage(String... message) { return false; } + @Override public void showMessage(String... message) { } + @Override public void incomingConnectionCreated(DuplexTransportConnection d) { connectionsLatch.countDown(); } + @Override public void outgoingConnectionCreated(ContactId c, DuplexTransportConnection d) { } + @Override public void transportEnabled() { } + @Override public void transportDisabled() { } } private static class TestBackoff implements Backoff { + @Override public int getPollingInterval() { return 60 * 1000; } + @Override public void increment() { } + @Override public void reset() { } } diff --git a/briar-tests/src/org/briarproject/util/StringUtilsTest.java b/briar-tests/src/org/briarproject/util/StringUtilsTest.java index 76f830df3df83c2e4ba22fbb016a173118bb7056..92aeaf54c7331046128a7e26f784d4a610defd21 100644 --- a/briar-tests/src/org/briarproject/util/StringUtilsTest.java +++ b/briar-tests/src/org/briarproject/util/StringUtilsTest.java @@ -173,4 +173,55 @@ public class StringUtilsTest extends BriarTestCase { public void testTruncateUtf8EmptyInput() { assertEquals("", StringUtils.truncateUtf8("", 123)); } + + @Test(expected = IllegalArgumentException.class) + public void testMacToBytesRejectsShortMac() { + StringUtils.macToBytes("00:00:00:00:00"); + } + + @Test(expected = IllegalArgumentException.class) + public void testMacToBytesRejectsLongMac() { + StringUtils.macToBytes("00:00:00:00:00:00:00"); + } + + @Test(expected = IllegalArgumentException.class) + public void testMacToBytesRejectsInvalidCharacter() { + StringUtils.macToBytes("00:00:00:00:00:0g"); + } + + @Test(expected = IllegalArgumentException.class) + public void testMacToBytesRejectsInvalidFormat() { + StringUtils.macToBytes("0:000:00:00:00:00"); + } + + @Test + public void testMacToBytesUpperCase() { + byte[] expected = new byte[] {0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F}; + String mac = "0A:1B:2C:3D:4E:5F"; + assertArrayEquals(expected, StringUtils.macToBytes(mac)); + } + + @Test + public void testMacToBytesLowerCase() { + byte[] expected = new byte[] {0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F}; + String mac = "0a:1b:2c:3d:4e:5f"; + assertArrayEquals(expected, StringUtils.macToBytes(mac)); + } + + @Test(expected = IllegalArgumentException.class) + public void testMacToStringRejectsShortMac() { + StringUtils.macToString(new byte[5]); + } + + @Test(expected = IllegalArgumentException.class) + public void testMacToStringRejectsLongMac() { + StringUtils.macToString(new byte[7]); + } + + @Test + public void testMacToString() { + byte[] mac = new byte[] {0x0a, 0x1b, 0x2c, 0x3d, 0x4e, 0x5f}; + String expected = "0A:1B:2C:3D:4E:5F"; + assertEquals(expected, StringUtils.macToString(mac)); + } }