Compare commits

..

1 Commits

Author SHA1 Message Date
Ernir Erlingsson
d5734f90f7 new event handler structure 2016-12-07 23:15:17 +01:00
564 changed files with 9407 additions and 13696 deletions

9
.gitignore vendored
View File

@@ -9,18 +9,17 @@ Thumbs.db
.DS_Store
# Eclipse project files
.classpath
.project
.settings
#.classpath
#.project
# Local configuration file (sdk path, etc)
local.properties
# Android Studio
.idea/*
!.idea/runConfigurations/
!.idea/codeStyleSettings.xml
.gradle
build/
*.iml
projectFilesBackup/
.gitignore
src/test/

View File

@@ -1,28 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method>
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
</method>
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-api" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-j2se" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-j2se" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-j2se" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -18,8 +18,8 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
@@ -66,7 +66,6 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
@@ -74,8 +73,7 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class DroidtoothPlugin<C, S>
extends AbstractBluetoothPlugin<C, S>{
class DroidtoothPlugin implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger(DroidtoothPlugin.class.getName());
@@ -84,10 +82,16 @@ class DroidtoothPlugin<C, S>
private static final String DISCOVERY_FINISHED =
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
private final Executor ioExecutor;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
private volatile BluetoothServerSocket socket = null;
@@ -98,19 +102,33 @@ class DroidtoothPlugin<C, S>
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
protected void close(S ss) throws IOException {
((BluetoothServerSocket)ss).close();
public TransportId getId() {
return ID;
}
@Override
public void start() throws PluginException {
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
@@ -124,14 +142,13 @@ class DroidtoothPlugin<C, S>
}).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.warning("Interrupted while getting BluetoothAdapter");
throw new PluginException(e);
throw new IOException("Interrupted while getting BluetoothAdapter");
} catch (ExecutionException e) {
throw new PluginException(e);
throw new IOException(e);
}
if (adapter == null) {
LOG.info("Bluetooth is not supported");
throw new PluginException();
return false;
}
running = true;
// Listen for changes to the Bluetooth state
@@ -145,7 +162,7 @@ class DroidtoothPlugin<C, S>
bind();
} else {
// Enable Bluetooth if settings allow
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
if (callback.getSettings().getBoolean("enable", false)) {
wasEnabledByUs = true;
if (adapter.enable()) LOG.info("Enabling Bluetooth");
else LOG.info("Could not enable Bluetooth");
@@ -153,6 +170,7 @@ class DroidtoothPlugin<C, S>
LOG.info("Not enabling Bluetooth");
}
}
return true;
}
private void bind() {
@@ -174,14 +192,14 @@ class DroidtoothPlugin<C, S>
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", UUID.fromString(getUuid()));
"RFCOMM", getUuid());
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return;
}
if (!isRunning()) {
tryToClose((S)ss);
tryToClose(ss);
return;
}
LOG.info("Socket bound");
@@ -193,6 +211,29 @@ class DroidtoothPlugin<C, S>
});
}
private UUID getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return UUID.fromString(uuid);
}
private void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections() {
while (isRunning()) {
BluetoothSocket s;
@@ -218,9 +259,9 @@ class DroidtoothPlugin<C, S>
@Override
public void stop() {
this.running = false;
running = false;
if (receiver != null) appContext.unregisterReceiver(receiver);
tryToClose((S)socket);
tryToClose(socket);
// Disable Bluetooth if we enabled it and it's still enabled
if (wasEnabledByUs && adapter.isEnabled()) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
@@ -233,20 +274,42 @@ class DroidtoothPlugin<C, S>
return running && adapter != null && adapter.isEnabled();
}
protected Runnable returnPollRunnable(final String address, final String uuid,
final ContactId c) {
return new Runnable() {
@Override
public void run() {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
@Override
public boolean shouldPoll() {
return true;
}
};
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<ContactId> connected) {
if (!isRunning()) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
});
}
}
@Nullable
@@ -282,17 +345,37 @@ class DroidtoothPlugin<C, S>
LOG.info("Failed to connect to " + scrubMacAddress(address)
+ ": " + e);
}
tryToClose((S)s);
tryToClose(s);
return null;
}
}
public DuplexTransportConnection connectToAddress(String address, String uuid) {
BluetoothSocket s = connect(address, uuid);
return s == null ? null : wrapSocket(s);
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
BluetoothSocket s = connect(address, uuid);
if (s == null) return null;
return new DroidtoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
@@ -341,7 +424,7 @@ class DroidtoothPlugin<C, S>
return null;
} finally {
// Closing the socket will terminate the listener task
tryToClose((S)ss);
tryToClose(ss);
closeSockets(futures, chosen);
}
}
@@ -373,6 +456,11 @@ class DroidtoothPlugin<C, S>
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
@@ -397,6 +485,31 @@ class DroidtoothPlugin<C, S>
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) 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))
LOG.info("Connecting to key agreement UUID " + uuid);
BluetoothSocket s = connect(address, uuid.toString());
if (s == null) return null;
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 {
@@ -408,7 +521,7 @@ class DroidtoothPlugin<C, S>
bind();
} else if (state == STATE_OFF) {
LOG.info("Bluetooth disabled");
tryToClose((S)socket);
tryToClose(socket);
}
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_NONE) {

View File

@@ -39,13 +39,14 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
}
@Override
public void start() {
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
return true;
}
@Override

View File

@@ -25,7 +25,6 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -79,12 +78,6 @@ import static java.util.logging.Level.WARNING;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WIFI;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
@MethodsNotNullByDefault
@@ -169,18 +162,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public void start() throws PluginException {
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets();
LOG.info("Starting Tor");
// Watch for the auth cookie file being updated
try {
cookieFile.getParentFile().mkdirs();
cookieFile.createNewFile();
} catch (IOException e) {
throw new PluginException(e);
}
cookieFile.getParentFile().mkdirs();
cookieFile.createNewFile();
CountDownLatch latch = new CountDownLatch(1);
FileObserver obs = new WriteObserver(cookieFile, latch);
obs.startWatching();
@@ -188,31 +177,19 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(android.os.Process.myPid());
String[] cmd = {torPath, "-f", configPath, OWNER, pid};
String[] env = {"HOME=" + torDirectory.getAbsolutePath()};
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
try {
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
torProcess = Runtime.getRuntime().exec(cmd, env, torDirectory);
} catch (SecurityException e) {
throw new IOException(e);
}
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()){
if(stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if(stderr.hasNextLine()){
LOG.info(stderr.nextLine());
}
}
while (stdout.hasNextLine()) LOG.info(stdout.nextLine());
stdout.close();
stderr.close();
}
try {
// Wait for the process to detach or exit
@@ -220,39 +197,35 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
return false;
}
// Wait for the auth cookie file to be created/updated
if (!latch.await(COOKIE_TIMEOUT, MILLISECONDS)) {
LOG.warning("Auth cookie not created");
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
throw new PluginException();
return false;
}
} catch (InterruptedException e) {
LOG.warning("Interrupted while starting Tor");
Thread.currentThread().interrupt();
throw new PluginException();
return false;
}
try {
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(Collections.singletonList(OWNER));
running = true;
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(Arrays.asList(EVENTS));
// Check whether Tor has already bootstrapped
String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
connectionStatus.setBootstrapped();
}
} catch (IOException e) {
throw new PluginException(e);
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(Collections.singletonList(OWNER));
running = true;
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(Arrays.asList(EVENTS));
// Check whether Tor has already bootstrapped
String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
connectionStatus.setBootstrapped();
}
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
@@ -260,6 +233,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
appContext.registerReceiver(networkStateReceiver, filter);
// Bind a server socket to receive incoming hidden service connections
bind();
return true;
}
private boolean assetsAreUpToDate() {
@@ -272,7 +246,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void installAssets() throws PluginException {
private void installAssets() throws IOException {
InputStream in = null;
OutputStream out = null;
try {
@@ -295,7 +269,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} catch (IOException e) {
tryToClose(in);
tryToClose(out);
throw new PluginException(e);
throw e;
}
}
@@ -384,7 +358,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void run() {
// If there's already a port number stored in config, reuse it
String portString = callback.getSettings().get(PREF_TOR_PORT);
String portString = callback.getSettings().get("port");
int port;
if (StringUtils.isNullOrEmpty(portString)) port = 0;
else port = Integer.parseInt(portString);
@@ -407,7 +381,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Store the port number
final String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
s.put(PREF_TOR_PORT, localPort);
s.put("port", localPort);
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(new Runnable() {
@@ -502,7 +476,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public void stop() throws PluginException {
public void stop() throws IOException {
running = false;
tryToClose(socket);
if (networkStateReceiver != null)
@@ -684,8 +658,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) {
if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) {
LOG.info("Tor settings updated");
updateConnectionStatus();
}
@@ -707,8 +680,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_ALWAYS);
boolean useMobileData = s.getBoolean("torOverMobile", true);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
@@ -723,8 +695,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
} else if (!wifi && !useMobileData) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {

View File

@@ -1,93 +0,0 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Parcel;
import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable
@NotNullByDefault
class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
private static final int SEED_LENGTH = 32;
private final Context appContext;
@Inject
AndroidSecureRandomProvider(Application app) {
appContext = app.getApplicationContext();
}
@Override
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
super.writeToEntropyPool(out);
out.writeInt(android.os.Process.myPid());
out.writeInt(android.os.Process.myTid());
out.writeInt(android.os.Process.myUid());
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id);
Parcel parcel = Parcel.obtain();
WifiManager wm =
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs)
parcel.writeParcelable(config, 0);
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices())
parcel.writeParcelable(device, 0);
}
out.write(parcel.marshall());
parcel.recycle();
}
@Override
protected void writeSeed() {
super.writeSeed();
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
applyOpenSslFix();
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void applyOpenSslFix() {
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
SEED_LENGTH);
try {
// Seed the OpenSSL PRNG
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, seed);
// Mix the output of the Linux PRNG into the OpenSSL PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) throw new IOException();
} catch (Exception e) {
throw new SecurityException(e);
}
}
}

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable
@NotNullByDefault
class AndroidSeedProvider extends LinuxSeedProvider {
private final Context appContext;
@Inject
AndroidSeedProvider(Application app) {
appContext = app.getApplicationContext();
}
@Override
void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeInt(android.os.Process.myPid());
out.writeInt(android.os.Process.myTid());
out.writeInt(android.os.Process.myUid());
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id);
super.writeToEntropyPool(out);
}
}

View File

@@ -4,7 +4,7 @@ import android.app.Application;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.system.SeedProvider;
import javax.inject.Singleton;
@@ -16,8 +16,8 @@ public class AndroidSystemModule {
@Provides
@Singleton
SecureRandomProvider provideSecureRandomProvider(Application app) {
return new AndroidSecureRandomProvider(app);
SeedProvider provideSeedProvider(Application app) {
return new AndroidSeedProvider(app);
}
@Provides

View File

@@ -8,34 +8,15 @@ dependencies {
compile "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2'
compile 'com.google.code.findbugs:jsr305:3.0.1'
testCompile 'junit:junit:4.12'
testCompile "org.jmock:jmock:2.8.1"
testCompile "org.jmock:jmock-junit4:2.8.1"
testCompile "org.jmock:jmock-legacy:2.8.1"
testCompile "org.hamcrest:hamcrest-library:1.3"
testCompile "org.hamcrest:hamcrest-core:1.3"
}
dependencyVerification {
verify = [
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.code.findbugs:jsr305:c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'com.google.code.findbugs:jsr305:c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd'
]
}
// needed to make test output available to bramble-core and briar-core
configurations {
testOutput.extendsFrom(testCompile)
}
task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output
classifier = 'test'
}
artifacts {
testOutput jarTest
}

View File

@@ -43,24 +43,6 @@ public interface ContactManager {
*/
Contact getContact(ContactId c) throws DbException;
/**
* Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId
*
* @throws org.briarproject.bramble.api.db.NoSuchContactException
*/
Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/**
* Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId
*
* @throws org.briarproject.bramble.api.db.NoSuchContactException
*/
Contact getContact(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException;
/**
* Returns all active contacts.
*/

View File

@@ -6,9 +6,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
public interface PasswordStrengthEstimator {
float NONE = 0;
float WEAK = 0.25f;
float QUITE_WEAK = 0.5f;
float QUITE_STRONG = 0.75f;
float WEAK = 0.4f;
float QUITE_WEAK = 0.6f;
float QUITE_STRONG = 0.8f;
float STRONG = 1;
/**

View File

@@ -13,9 +13,7 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class Author {
public enum Status {
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
}
public enum Status {ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES}
private final AuthorId id;
private final String name;

View File

@@ -9,5 +9,4 @@ public interface BluetoothConstants {
String PROP_ADDRESS = "address";
String PROP_UUID = "uuid";
String PREF_BT_ENABLE = "enable";
}

View File

@@ -4,5 +4,4 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan");
String PREF_LAN_IP_PORTS = "ipPorts";
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.util.Collection;
@NotNullByDefault
@@ -24,14 +25,14 @@ public interface Plugin {
int getMaxIdleTime();
/**
* Starts the plugin.
* Starts the plugin and returns true if it started successfully.
*/
void start() throws PluginException;
boolean start() throws IOException;
/**
* Stops the plugin.
*/
void stop() throws PluginException;
void stop() throws IOException;
/**
* Returns true if the plugin is running.

View File

@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.plugin;
/**
* An exception that indicates an error starting or stopping a {@link Plugin}.
*/
public class PluginException extends Exception {
public PluginException() {
super();
}
public PluginException(Throwable cause) {
super(cause);
}
}

View File

@@ -8,12 +8,4 @@ public interface TorConstants {
int CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
String PREF_TOR_NETWORK = "network";
String PREF_TOR_PORT = "port";
int PREF_TOR_NETWORK_NEVER = 0;
int PREF_TOR_NETWORK_WIFI = 1;
int PREF_TOR_NETWORK_ALWAYS = 2;
}

View File

@@ -1,190 +0,0 @@
package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.StringUtils;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Created by Santiago Torres-Arias on 1/10/17.
*/
public abstract class AbstractBluetoothPlugin<C, S> implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger("Halp");
protected final Executor ioExecutor;
protected final SecureRandom secureRandom;
protected final Backoff backoff;
protected final int maxLatency;
protected final DuplexPluginCallback callback;
protected final S ss = null;
protected volatile boolean running = false;
protected Runnable pollRunnable = null;
public AbstractBluetoothPlugin(Executor ioExecutor,
SecureRandom secureRandom,
Backoff backoff, int maxLatency,
DuplexPluginCallback callback) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.maxLatency = maxLatency;
this.callback = callback;
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
protected String getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return uuid;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
protected String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
protected void tryToClose(@Nullable S ss) {
try {
if (ss != null) close(ss);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
protected abstract void close(S ss) throws IOException;
public void stop() {
running = false;
tryToClose(ss);
}
@Override
public void poll(final Collection<ContactId> connected) {
if (!running) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Map.Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(returnPollRunnable(address,uuid, c));
}
}
protected abstract Runnable returnPollRunnable(String address, String uuid,
ContactId c);
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
return connectToAddress(address, uuid);
}
protected abstract DuplexTransportConnection connectToAddress(String address, String uuid);
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) 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))
LOG.info("Connecting to key agreement UUID " + uuid);
return connectToAddress(address, uuid);
}
}

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* A record acknowledging receipt of one or more {@link Message Messages}.
* A packet acknowledging receipt of one or more {@link Message Messages}.
*/
public class Ack {

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.api.sync;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
public class Group {
public enum Visibility {
@@ -15,8 +13,6 @@ public class Group {
private final byte[] descriptor;
public Group(GroupId id, ClientId clientId, byte[] descriptor) {
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
throw new IllegalArgumentException();
this.id = id;
this.clientId = clientId;
this.descriptor = descriptor;

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* A record offering the recipient one or more {@link Message Messages}.
* A packet offering the recipient one or more {@link Message Messages}.
*/
public class Offer {

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
@NotNullByDefault
public interface RecordReader {
public interface PacketReader {
boolean eof() throws IOException;
@@ -24,5 +24,4 @@ public interface RecordReader {
boolean hasRequest() throws IOException;
Request readRequest() throws IOException;
}

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
@NotNullByDefault
public interface RecordReaderFactory {
public interface PacketReaderFactory {
RecordReader createRecordReader(InputStream in);
PacketReader createPacketReader(InputStream in);
}

View File

@@ -1,13 +1,12 @@
package org.briarproject.bramble.api.sync;
/**
* Record types for the sync protocol.
* Packet types for the sync protocol.
*/
public interface RecordTypes {
public interface PacketTypes {
byte ACK = 0;
byte MESSAGE = 1;
byte OFFER = 2;
byte REQUEST = 3;
}

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
@NotNullByDefault
public interface RecordWriter {
public interface PacketWriter {
void writeAck(Ack a) throws IOException;

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.OutputStream;
@NotNullByDefault
public interface RecordWriterFactory {
public interface PacketWriterFactory {
RecordWriter createRecordWriter(OutputStream out);
PacketWriter createPacketWriter(OutputStream out);
}

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* A record requesting one or more {@link Message Messages} from the recipient.
* A packet requesting one or more {@link Message Messages} from the recipient.
*/
public class Request {

View File

@@ -10,17 +10,19 @@ public interface SyncConstants {
byte PROTOCOL_VERSION = 0;
/**
* The length of the record header in bytes.
* The length of the packet header in bytes.
*/
int RECORD_HEADER_LENGTH = 4;
int PACKET_HEADER_LENGTH = 4;
/**
* The maximum length of the record payload in bytes.
* The maximum length of the packet payload in bytes.
*/
int MAX_RECORD_PAYLOAD_LENGTH = 48 * 1024; // 48 KiB
int MAX_PACKET_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
/** The maximum length of a group descriptor in bytes. */
int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB
/**
* The maximum length of a message in bytes.
*/
int MAX_MESSAGE_LENGTH = MAX_PACKET_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH;
/**
* The length of the message header in bytes.
@@ -30,15 +32,10 @@ public interface SyncConstants {
/**
* The maximum length of a message body in bytes.
*/
int MAX_MESSAGE_BODY_LENGTH = 32 * 1024; // 32 KiB
int MAX_MESSAGE_BODY_LENGTH = MAX_MESSAGE_LENGTH - MESSAGE_HEADER_LENGTH;
/**
* The maximum length of a message in bytes.
* The maximum number of message IDs in an ack, offer or request packet.
*/
int MAX_MESSAGE_LENGTH = MESSAGE_HEADER_LENGTH + MAX_MESSAGE_BODY_LENGTH;
/**
* The maximum number of message IDs in an ack, offer or request record.
*/
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH;
int MAX_MESSAGE_IDS = MAX_PACKET_PAYLOAD_LENGTH / UniqueId.LENGTH;
}

View File

@@ -5,7 +5,7 @@ import java.io.IOException;
public interface SyncSession {
/**
* Runs the session. This method returns when there are no more records to
* Runs the session. This method returns when there are no more packets to
* send or receive, or when the {@link #interrupt()} method has been called.
*/
void run() throws IOException;

View File

@@ -1,23 +0,0 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.Provider;
import java.security.SecureRandom;
import javax.annotation.Nullable;
/**
* Wrapper for a platform-specific secure random number generator.
*/
@NotNullByDefault
public interface SecureRandomProvider {
/**
* Returns a {@link Provider} that provides a strong {@link SecureRandom}
* implementation, or null if the platform's default implementation should
* be used.
*/
@Nullable
Provider getProvider();
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Uses a platform-specific source to provide a seed for a pseudo-random
* number generator.
*/
@NotNullByDefault
public interface SeedProvider {
/**
* The length of the seed in bytes.
*/
int SEED_BYTES = 32;
byte[] getSeed();
}

View File

@@ -1,4 +1,3 @@
bin
build
test.tmp
.settings

View File

@@ -1,8 +1,4 @@
plugins {
id "java"
id "net.ltgt.apt" version "0.9"
id "idea"
}
apply plugin: 'java'
sourceCompatibility = 1.6
targetCompatibility = 1.6
@@ -13,8 +9,6 @@ dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.madgag.spongycastle:core:1.54.0.0'
compile 'com.h2database:h2:1.4.190'
testCompile project(path: ':bramble-api', configuration: 'testOutput')
}
dependencyVerification {
@@ -23,15 +17,3 @@ dependencyVerification {
'com.h2database:h2:23ba495a07bbbb3bd6c3084d10a96dad7a23741b8b6d64b213459a784195a98c'
]
}
// needed to make test output available to bramble-j2se
configurations {
testOutput.extendsFrom(testCompile)
}
task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output
classifier = 'test'
}
artifacts {
testOutput jarTest
}

View File

@@ -1,84 +0,0 @@
package org.briarproject.bramble;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import static java.util.logging.Level.FINE;
/**
* An {@link Executor} that delegates its tasks to another {@link Executor}
* while limiting the number of tasks that are delegated concurrently. Tasks
* are delegated in the order they are submitted to this executor.
*/
@NotNullByDefault
public class PoliteExecutor implements Executor {
private static final Level LOG_LEVEL = FINE;
private final Object lock = new Object();
@GuardedBy("lock")
private final Queue<Runnable> queue = new LinkedList<Runnable>();
private final Executor delegate;
private final int maxConcurrentTasks;
private final Logger log;
@GuardedBy("lock")
private int concurrentTasks = 0;
/**
* @param tag the tag to be used for logging
* @param delegate the executor to which tasks will be delegated
* @param maxConcurrentTasks the maximum number of tasks that will be
* delegated concurrently. If this is set to 1, tasks submitted to this
* executor will run in the order they are submitted and will not run
* concurrently
*/
public PoliteExecutor(String tag, Executor delegate,
int maxConcurrentTasks) {
this.delegate = delegate;
this.maxConcurrentTasks = maxConcurrentTasks;
log = Logger.getLogger(tag);
}
@Override
public void execute(final Runnable r) {
final long submitted = System.currentTimeMillis();
Runnable wrapped = new Runnable() {
@Override
public void run() {
if (log.isLoggable(LOG_LEVEL)) {
long queued = System.currentTimeMillis() - submitted;
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
}
try {
r.run();
} finally {
scheduleNext();
}
}
};
synchronized (lock) {
if (concurrentTasks < maxConcurrentTasks) {
concurrentTasks++;
delegate.execute(wrapped);
} else {
queue.add(wrapped);
}
}
}
private void scheduleNext() {
synchronized (lock) {
Runnable next = queue.poll();
if (next == null) concurrentTasks--;
else delegate.execute(next);
}
}
}

View File

@@ -1,49 +0,0 @@
package org.briarproject.bramble;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.FINE;
@NotNullByDefault
public class TimeLoggingExecutor extends ThreadPoolExecutor {
private static final Level LOG_LEVEL = FINE;
private final Logger log;
public TimeLoggingExecutor(String tag, int corePoolSize, int maxPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue,
handler);
log = Logger.getLogger(tag);
}
@Override
public void execute(final Runnable r) {
if (log.isLoggable(LOG_LEVEL)) {
final long submitted = System.currentTimeMillis();
super.execute(new Runnable() {
@Override
public void run() {
long started = System.currentTimeMillis();
long queued = started - submitted;
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
r.run();
long executing = System.currentTimeMillis() - started;
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
}
});
} else {
super.execute(r);
}
}
}

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
@@ -89,32 +88,6 @@ class ContactManagerImpl implements ContactManager {
return contact;
}
@Override
public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException {
Transaction txn = db.startTransaction(true);
try {
Contact c = getContact(txn, remoteAuthorId, localAuthorId);
db.commitTransaction(txn);
return c;
} finally {
db.endTransaction(txn);
}
}
@Override
public Contact getContact(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException {
Collection<Contact> contacts =
db.getContactsByAuthorId(txn, remoteAuthorId);
for (Contact c : contacts) {
if (c.getLocalAuthorId().equals(localAuthorId)) {
return c;
}
}
throw new NoSuchContactException();
}
@Override
public Collection<Contact> getActiveContacts() throws DbException {
Collection<Contact> contacts;

View File

@@ -0,0 +1,62 @@
package org.briarproject.bramble.crypto;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
/**
* A {@link SecureRandom} implementation that combines the outputs of two or
* more other implementations using XOR.
*/
class CombinedSecureRandom extends SecureRandom {
private static final Provider PROVIDER = new CombinedProvider();
CombinedSecureRandom(SecureRandom... randoms) {
super(new CombinedSecureRandomSpi(randoms), PROVIDER);
}
private static class CombinedSecureRandomSpi extends SecureRandomSpi {
private final SecureRandom[] randoms;
private CombinedSecureRandomSpi(SecureRandom... randoms) {
if (randoms.length < 2) throw new IllegalArgumentException();
this.randoms = randoms;
}
@Override
protected byte[] engineGenerateSeed(int numBytes) {
byte[] combined = new byte[numBytes];
for (SecureRandom random : randoms) {
byte[] b = random.generateSeed(numBytes);
int length = Math.min(numBytes, b.length);
for (int i = 0; i < length; i++)
combined[i] = (byte) (combined[i] ^ b[i]);
}
return combined;
}
@Override
protected void engineNextBytes(byte[] b) {
byte[] temp = new byte[b.length];
for (SecureRandom random : randoms) {
random.nextBytes(temp);
for (int i = 0; i < b.length; i++)
b[i] = (byte) (b[i] ^ temp[i]);
}
}
@Override
protected void engineSetSeed(byte[] seed) {
for (SecureRandom random : randoms) random.setSeed(seed);
}
}
private static class CombinedProvider extends Provider {
private CombinedProvider() {
super("Combined", 1.0, "");
}
}
}

View File

@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
@@ -29,10 +29,7 @@ import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -104,26 +101,16 @@ class CryptoComponentImpl implements CryptoComponent {
private final MessageEncrypter messageEncrypter;
@Inject
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
CryptoComponentImpl(SeedProvider seedProvider) {
if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
SecureRandom platformSecureRandom = new SecureRandom();
if (LOG.isLoggable(INFO)) {
SecureRandom defaultSecureRandom = new SecureRandom();
String name = defaultSecureRandom.getProvider().getName();
String algorithm = defaultSecureRandom.getAlgorithm();
LOG.info("Default SecureRandom: " + name + " " + algorithm);
String provider = platformSecureRandom.getProvider().getName();
String algorithm = platformSecureRandom.getAlgorithm();
LOG.info("Default SecureRandom: " + provider + " " + algorithm);
}
Provider provider = secureRandomProvider.getProvider();
if (provider == null) {
LOG.info("Using default");
} else {
installSecureRandomProvider(provider);
if (LOG.isLoggable(INFO)) {
SecureRandom installedSecureRandom = new SecureRandom();
String name = installedSecureRandom.getProvider().getName();
String algorithm = installedSecureRandom.getAlgorithm();
LOG.info("Installed SecureRandom: " + name + " " + algorithm);
}
}
secureRandom = new SecureRandom();
SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed());
secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna);
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
@@ -137,31 +124,6 @@ class CryptoComponentImpl implements CryptoComponent {
messageEncrypter = new MessageEncrypter(secureRandom);
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void installSecureRandomProvider(Provider provider) {
Provider[] providers = Security.getProviders("SecureRandom.SHA1PRNG");
if (providers == null || providers.length == 0
|| !provider.getClass().equals(providers[0].getClass())) {
Security.insertProviderAt(provider, 1);
}
// Check the new provider is the default when no algorithm is specified
SecureRandom random = new SecureRandom();
if (!provider.getClass().equals(random.getProvider().getClass())) {
throw new SecurityException("Wrong SecureRandom provider: "
+ random.getProvider().getClass());
}
// Check the new provider is the default when SHA1PRNG is specified
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException(e);
}
if (!provider.getClass().equals(random.getProvider().getClass())) {
throw new SecurityException("Wrong SHA1PRNG provider: "
+ random.getProvider().getClass());
}
}
@Override
public SecretKey generateSecretKey() {
byte[] b = new byte[SecretKey.LENGTH];
@@ -171,10 +133,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
byte[] seed = new byte[INT_32_BYTES * 2];
ByteUtils.writeUint32(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
return new PseudoRandomImpl(seed);
return new PseudoRandomImpl(seed1, seed2);
}
@Override
@@ -337,7 +296,7 @@ class CryptoComponentImpl implements CryptoComponent {
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
return deriveMasterSecret(deriveSharedSecret(
theirPublicKey, ourKeyPair, alice));
theirPublicKey,ourKeyPair, alice));
}
@Override
@@ -648,7 +607,7 @@ class CryptoComponentImpl implements CryptoComponent {
}
private long sampleRunningTime(int iterations) {
byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
byte[] salt = new byte[PBKDF_SALT_BYTES];
int keyLengthInBits = SecretKey.LENGTH * 8;
long start = System.nanoTime();

View File

@@ -1,13 +1,12 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.system.SeedProvider;
import java.security.SecureRandom;
import java.util.concurrent.BlockingQueue;
@@ -32,17 +31,14 @@ public class CryptoModule {
public static class EagerSingletons {
@Inject
@CryptoExecutor
ExecutorService cryptoExecutor;
Executor cryptoExecutor;
}
/**
* The maximum number of executor threads.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_EXECUTOR_THREADS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
Runtime.getRuntime().availableProcessors();
private final ExecutorService cryptoExecutor;
@@ -53,8 +49,8 @@ public class CryptoModule {
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create a limited # of threads and keep them in the pool for 60 secs
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
cryptoExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
60, SECONDS, queue, policy);
}
@Provides
@@ -64,9 +60,8 @@ public class CryptoModule {
@Provides
@Singleton
CryptoComponent provideCryptoComponent(
SecureRandomProvider secureRandomProvider) {
return new CryptoComponentImpl(secureRandomProvider);
CryptoComponent provideCryptoComponent(SeedProvider seedProvider) {
return new CryptoComponentImpl(seedProvider);
}
@Provides
@@ -89,18 +84,11 @@ public class CryptoModule {
@Provides
@Singleton
@CryptoExecutor
ExecutorService getCryptoExecutorService(
LifecycleManager lifecycleManager) {
Executor getCryptoExecutor(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(cryptoExecutor);
return cryptoExecutor;
}
@Provides
@CryptoExecutor
Executor getCryptoExecutor() {
return cryptoExecutor;
}
@Provides
SecureRandom getSecureRandom(CryptoComponent crypto) {
return crypto.getSecureRandom();

View File

@@ -0,0 +1,76 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import javax.annotation.concurrent.NotThreadSafe;
/**
* A message digest that prevents length extension attacks - see Ferguson and
* Schneier, <i>Practical Cryptography</i>, chapter 6.
* <p>
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
* min(k, n/2) where k is the security level of h and n is the size of the hash
* result."
*/
@NotThreadSafe
@NotNullByDefault
class DoubleDigest implements Digest {
private final Digest delegate;
DoubleDigest(Digest delegate) {
this.delegate = delegate;
}
private byte[] digest() {
byte[] digest = new byte[delegate.getDigestSize()];
delegate.doFinal(digest, 0); // h(m)
delegate.update(digest, 0, digest.length);
delegate.doFinal(digest, 0); // h(h(m))
return digest;
}
public int digest(byte[] buf, int offset, int len) {
byte[] digest = digest();
len = Math.min(len, digest.length);
System.arraycopy(digest, 0, buf, offset, len);
return len;
}
@Override
public int getDigestSize() {
return delegate.getDigestSize();
}
@Override
public String getAlgorithmName() {
return "Double " + delegate.getAlgorithmName();
}
@Override
public void reset() {
delegate.reset();
}
@Override
public void update(byte input) {
delegate.update(input);
}
public void update(byte[] input) {
delegate.update(input, 0, input.length);
}
@Override
public void update(byte[] input, int offset, int len) {
delegate.update(input, offset, len);
}
@Override
public int doFinal(byte[] out, int outOff) {
return digest(out, outOff, delegate.getDigestSize());
}
}

View File

@@ -0,0 +1,114 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.ThreadSafe;
/**
* Implements the Fortuna pseudo-random number generator, as described in
* Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
*/
@ThreadSafe
@NotNullByDefault
class FortunaGenerator {
private static final int MAX_BYTES_PER_REQUEST = 1024 * 1024;
private static final int KEY_BYTES = 32;
private static final int BLOCK_BYTES = 16;
private final Lock lock = new ReentrantLock();
// The following are locking: lock
private final DoubleDigest digest = new DoubleDigest(new SHA256Digest());
private final BlockCipher cipher = new AESLightEngine();
private final byte[] key = new byte[KEY_BYTES];
private final byte[] counter = new byte[BLOCK_BYTES];
private final byte[] buffer = new byte[BLOCK_BYTES];
private final byte[] newKey = new byte[KEY_BYTES];
FortunaGenerator(byte[] seed) {
reseed(seed);
}
void reseed(byte[] seed) {
lock.lock();
try {
digest.update(key);
digest.update(seed);
digest.digest(key, 0, KEY_BYTES);
incrementCounter();
} finally {
lock.unlock();
}
}
// Package access for testing
void incrementCounter() {
lock.lock();
try {
counter[0]++;
for (int i = 0; counter[i] == 0; i++) {
if (i + 1 == BLOCK_BYTES)
throw new RuntimeException("Counter exhausted");
counter[i + 1]++;
}
} finally {
lock.unlock();
}
}
// Package access for testing
byte[] getCounter() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
int nextBytes(byte[] dest, int off, int len) {
lock.lock();
try {
// Don't write more than the maximum number of bytes in one request
if (len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
cipher.init(true, new KeyParameter(key));
// Generate full blocks directly into the output buffer
int fullBlocks = len / BLOCK_BYTES;
for (int i = 0; i < fullBlocks; i++) {
cipher.processBlock(counter, 0, dest, off + i * BLOCK_BYTES);
incrementCounter();
}
// Generate a partial block if needed
int done = fullBlocks * BLOCK_BYTES, remaining = len - done;
if (remaining >= BLOCK_BYTES) throw new AssertionError();
if (remaining > 0) {
cipher.processBlock(counter, 0, buffer, 0);
incrementCounter();
// Copy the partial block to the output buffer and erase our copy
System.arraycopy(buffer, 0, dest, off + done, remaining);
for (int i = 0; i < BLOCK_BYTES; i++) buffer[i] = 0;
}
// Generate a new key
for (int i = 0; i < KEY_BYTES / BLOCK_BYTES; i++) {
cipher.processBlock(counter, 0, newKey, i * BLOCK_BYTES);
incrementCounter();
}
System.arraycopy(newKey, 0, key, 0, KEY_BYTES);
for (int i = 0; i < KEY_BYTES; i++) newKey[i] = 0;
// Return the number of bytes written
return len;
} finally {
lock.unlock();
}
}
}

View File

@@ -0,0 +1,81 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.util.StringUtils;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.Arrays;
/**
* A {@link java.security.SecureRandom SecureRandom} implementation based on a
* {@link FortunaGenerator}.
*/
class FortunaSecureRandom extends SecureRandom {
// Package access for testing
static final byte[] SELF_TEST_VECTOR_1 =
StringUtils.fromHexString("4BD6EA599D47E3EE9DD911833C29CA22");
static final byte[] SELF_TEST_VECTOR_2 =
StringUtils.fromHexString("10984D576E6850E505CA9F42A9BFD88A");
static final byte[] SELF_TEST_VECTOR_3 =
StringUtils.fromHexString("1E12DA166BD86DCECDE50A8296018DE2");
private static final Provider PROVIDER = new FortunaProvider();
FortunaSecureRandom(byte[] seed) {
super(new FortunaSecureRandomSpi(seed), PROVIDER);
}
/**
* Tests that the {@link #nextBytes(byte[])} and {@link #setSeed(byte[])}
* methods are passed through to the generator in the expected way.
*/
static boolean selfTest() {
byte[] seed = new byte[32];
SecureRandom r = new FortunaSecureRandom(seed);
byte[] output = new byte[16];
r.nextBytes(output);
if (!Arrays.equals(SELF_TEST_VECTOR_1, output)) return false;
r.nextBytes(output);
if (!Arrays.equals(SELF_TEST_VECTOR_2, output)) return false;
r.setSeed(seed);
r.nextBytes(output);
return Arrays.equals(SELF_TEST_VECTOR_3, output);
}
private static class FortunaSecureRandomSpi extends SecureRandomSpi {
private final FortunaGenerator generator;
private FortunaSecureRandomSpi(byte[] seed) {
generator = new FortunaGenerator(seed);
}
@Override
protected byte[] engineGenerateSeed(int numBytes) {
byte[] b = new byte[numBytes];
engineNextBytes(b);
return b;
}
@Override
protected void engineNextBytes(byte[] b) {
int offset = 0;
while (offset < b.length)
offset += generator.nextBytes(b, offset, b.length - offset);
}
@Override
protected void engineSetSeed(byte[] seed) {
generator.reseed(seed);
}
}
private static class FortunaProvider extends Provider {
private FortunaProvider() {
super("Fortuna", 1.0, "");
}
}
}

View File

@@ -11,14 +11,31 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
// The minimum number of unique characters in a strong password
private static final int STRONG_UNIQUE_CHARS = 12;
private static final int LOWER = 26;
private static final int UPPER = 26;
private static final int DIGIT = 10;
private static final int OTHER = 10;
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
DIGIT + OTHER, 10));
@Override
public float estimateStrength(String password) {
HashSet<Character> unique = new HashSet<Character>();
int length = password.length();
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
boolean lower = false, upper = false, digit = false, other = false;
for (char c : unique) {
if (Character.isLowerCase(c)) lower = true;
else if (Character.isUpperCase(c)) upper = true;
else if (Character.isDigit(c)) digit = true;
else other = true;
}
int alphabetSize = 0;
if (lower) alphabetSize += LOWER;
if (upper) alphabetSize += UPPER;
if (digit) alphabetSize += DIGIT;
if (other) alphabetSize += OTHER;
double score = Math.log(Math.pow(alphabetSize, unique.size()));
return Math.min(1, (float) (score / STRONG));
}
}

View File

@@ -2,34 +2,30 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import org.briarproject.bramble.util.ByteUtils;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@NotThreadSafe
@NotNullByDefault
class PseudoRandomImpl implements PseudoRandom {
private final Salsa20Engine cipher = new Salsa20Engine();
private final FortunaGenerator generator;
PseudoRandomImpl(byte[] seed) {
// Hash the seed to produce a 32-byte key
byte[] key = new byte[32];
Digest digest = new Blake2sDigest();
digest.update(seed, 0, seed.length);
digest.doFinal(key, 0);
// Initialise the stream cipher with an all-zero nonce
byte[] nonce = new byte[8];
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
PseudoRandomImpl(int seed1, int seed2) {
byte[] seed = new byte[INT_32_BYTES * 2];
ByteUtils.writeUint32(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
generator = new FortunaGenerator(seed);
}
@Override
public byte[] nextBytes(int length) {
byte[] in = new byte[length], out = new byte[length];
cipher.processBytes(in, 0, length, out, 0);
return out;
byte[] b = new byte[length];
int offset = 0;
while (offset < length) offset += generator.nextBytes(b, offset, length);
return b;
}
}

View File

@@ -62,8 +62,6 @@ class StreamEncrypterImpl implements StreamEncrypter {
@Override
public void writeFrame(byte[] payload, int payloadLength,
int paddingLength, boolean finalFrame) throws IOException {
if (payloadLength < 0 || paddingLength < 0)
throw new IllegalArgumentException();
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException();
// Don't allow the frame counter to wrap

View File

@@ -67,7 +67,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -131,14 +130,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
// Don't allow reentrant locking
if (lock.getReadHoldCount() > 0) throw new IllegalStateException();
if (lock.getWriteHoldCount() > 0) throw new IllegalStateException();
long start = System.currentTimeMillis();
if (readOnly) lock.readLock().lock();
else lock.writeLock().lock();
if (LOG.isLoggable(FINE)) {
long duration = System.currentTimeMillis() - start;
if (readOnly) LOG.fine("Waited " + duration + " ms for read lock");
else LOG.fine("Waited " + duration + " ms for write lock");
}
try {
return new Transaction(db.startTransaction(), readOnly);
} catch (DbException e) {
@@ -668,9 +661,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
acked.add(m);
}
}
if (acked.size() > 0) {
transaction.attach(new MessagesAckedEvent(c, acked));
}
transaction.attach(new MessagesAckedEvent(c, acked));
}
@Override

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -37,8 +36,8 @@ public class DatabaseExecutorModule {
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Use a single thread and keep it in the pool for 60 secs
databaseExecutor = new TimeLoggingExecutor("DatabaseExecutor", 0, 1,
60, SECONDS, queue, policy);
databaseExecutor = new ThreadPoolExecutor(0, 1, 60, SECONDS, queue,
policy);
}
@Provides

View File

@@ -68,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
@NotNullByDefault
abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 30;
private static final int MIN_SCHEMA_VERSION = 30;
private static final int SCHEMA_VERSION = 29;
private static final int MIN_SCHEMA_VERSION = 29;
private static final String CREATE_SETTINGS =
"CREATE TABLE settings"

View File

@@ -52,7 +52,7 @@ class KeyAgreementProtocol {
void connectionWaiting();
void initialRecordReceived();
void initialPacketReceived();
}
private final Callbacks callbacks;
@@ -117,7 +117,7 @@ class KeyAgreementProtocol {
private byte[] receiveKey() throws AbortException {
byte[] publicKey = transport.receiveKey();
callbacks.initialRecordReceived();
callbacks.initialPacketReceived();
byte[] expected = crypto.deriveKeyCommitment(publicKey);
if (!Arrays.equals(expected, theirPayload.getCommitment()))
throw new AbortException();

View File

@@ -129,7 +129,7 @@ class KeyAgreementTaskImpl extends Thread implements
}
@Override
public void initialRecordReceived() {
public void initialPacketReceived() {
// We send this here instead of when we create the protocol, so that
// if device A makes a connection after getting device B's payload and
// starts its protocol, device A's UI doesn't change to prevent device B

View File

@@ -95,30 +95,20 @@ class KeyAgreementTransport {
out.flush();
}
private byte[] readRecord(byte expectedType) throws AbortException {
while (true) {
byte[] header = readHeader();
byte version = header[0], type = header[1];
int len = ByteUtils.readUint16(header,
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
// Reject unrecognised protocol version
if (version != PROTOCOL_VERSION) throw new AbortException(false);
if (type == ABORT) throw new AbortException(true);
if (type == expectedType) {
try {
return readData(len);
} catch (IOException e) {
throw new AbortException(e);
}
}
// Reject recognised but unexpected record type
if (type == KEY || type == CONFIRM) throw new AbortException(false);
// Skip unrecognised record type
try {
readData(len);
} catch (IOException e) {
throw new AbortException(e);
}
private byte[] readRecord(byte type) throws AbortException {
byte[] header = readHeader();
if (header[0] != PROTOCOL_VERSION)
throw new AbortException(); // TODO handle?
if (header[1] != type) {
// Unexpected packet
throw new AbortException(header[1] == ABORT);
}
int len = ByteUtils.readUint16(header,
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
try {
return readData(len);
} catch (IOException e) {
throw new AbortException(e);
}
}

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -31,6 +30,7 @@ import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.ui.UiCallback;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -193,17 +193,24 @@ class PluginManagerImpl implements PluginManager, Service {
@Override
public void run() {
try {
long start = System.currentTimeMillis();
plugin.start();
long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) {
LOG.info("Starting plugin " + plugin.getId() + " took " +
duration + " ms");
}
} catch (PluginException e) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Plugin " + plugin.getId() + " did not start");
LOG.log(WARNING, e.toString(), e);
try {
long start = System.currentTimeMillis();
boolean started = plugin.start();
long duration = System.currentTimeMillis() - start;
if (started) {
if (LOG.isLoggable(INFO)) {
LOG.info("Starting plugin " + plugin.getId()
+ " took " + duration + " ms");
}
} else {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Plugin" + plugin.getId()
+ " did not start");
}
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} finally {
startLatch.countDown();
@@ -239,13 +246,10 @@ class PluginManagerImpl implements PluginManager, Service {
+ " took " + duration + " ms");
}
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for plugin to stop");
LOG.warning("Interrupted while waiting for plugin to start");
// This task runs on an executor, so don't reset the interrupt
} catch (PluginException e) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Plugin " + plugin.getId() + " did not stop");
LOG.log(WARNING, e.toString(), e);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
stopLatch.countDown();
}

View File

@@ -34,7 +34,6 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
@@ -44,7 +43,7 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG =
Logger.getLogger(LanTcpPlugin.class.getName());
private static final int MAX_ADDRESSES = 4;
private static final int MAX_ADDRESSES = 5;
private static final String PROP_IP_PORTS = "ipPorts";
private static final String SEPARATOR = ",";
@@ -83,19 +82,19 @@ class LanTcpPlugin extends TcpPlugin {
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
List<InetSocketAddress> remotes = new ArrayList<InetSocketAddress>();
for (String ipPort : split) {
InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) addresses.add(a);
if (a != null) remotes.add(a);
}
return addresses;
return remotes;
}
@Override
protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a);
// Get the list of recently used addresses
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS);
String setting = callback.getSettings().get(PROP_IP_PORTS);
List<String> recent = new ArrayList<String>();
if (!StringUtils.isNullOrEmpty(setting))
Collections.addAll(recent, setting.split(SEPARATOR));
@@ -121,7 +120,7 @@ class LanTcpPlugin extends TcpPlugin {
}
// Save the setting
Settings settings = new Settings();
settings.put(PREF_LAN_IP_PORTS, setting);
settings.put(PROP_IP_PORTS, setting);
callback.mergeSettings(settings);
}

View File

@@ -101,10 +101,11 @@ abstract class TcpPlugin implements DuplexPlugin {
}
@Override
public void start() {
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
bind();
return true;
}
protected void bind() {

View File

@@ -8,7 +8,6 @@ import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.SOCKS_PORT;
@Module
@@ -18,7 +17,6 @@ public class SocksModule {
SocketFactory provideTorSocketFactory() {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
SOCKS_PORT);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
EXTRA_SOCKET_TIMEOUT);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT);
}
}

View File

@@ -6,36 +6,18 @@ import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Arrays;
class SocksSocket extends Socket {
private static final String[] ERRORS = {
"Succeeded",
"General SOCKS server failure",
"Connection not allowed by ruleset",
"Network unreachable",
"Host unreachable",
"Connection refused",
"TTL expired",
"Command not supported",
"Address type not supported"
};
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
private final SocketAddress proxy;
private final int connectToProxyTimeout, extraSocketTimeout;
private final int connectToProxyTimeout;
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
int extraSocketTimeout) {
SocksSocket(SocketAddress proxy, int connectToProxyTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@Override
@@ -46,11 +28,6 @@ class SocksSocket extends Socket {
if (!(endpoint instanceof InetSocketAddress))
throw new IllegalArgumentException();
InetSocketAddress inet = (InetSocketAddress) endpoint;
InetAddress address = inet.getAddress();
if (address != null
&& !Arrays.equals(address.getAddress(), UNSPECIFIED_ADDRESS)) {
throw new IllegalArgumentException();
}
String host = inet.getHostName();
if (host.length() > 255) throw new IllegalArgumentException();
int port = inet.getPort();
@@ -64,16 +41,16 @@ class SocksSocket extends Socket {
sendMethodRequest(out);
receiveMethodResponse(in);
// Use the supplied timeout temporarily, plus any configured extra
// Use the supplied timeout temporarily
int oldTimeout = getSoTimeout();
setSoTimeout(timeout + extraSocketTimeout);
setSoTimeout(timeout);
// Connect to the endpoint via the proxy
sendConnectRequest(out, host, port);
receiveConnectResponse(in);
// Restore the old timeout, plus any configured extra
setSoTimeout(oldTimeout + extraSocketTimeout);
// Restore the old timeout
setSoTimeout(oldTimeout);
}
private void sendMethodRequest(OutputStream out) throws IOException {
@@ -116,16 +93,13 @@ class SocksSocket extends Socket {
private void receiveConnectResponse(InputStream in) throws IOException {
byte[] connectResponse = new byte[4];
IoUtils.read(in, connectResponse);
int version = connectResponse[0] & 0xFF;
int reply = connectResponse[1] & 0xFF;
int addressType = connectResponse[3] & 0xFF;
byte version = connectResponse[0];
byte reply = connectResponse[1];
byte addressType = connectResponse[3];
if (version != 5)
throw new IOException("Unsupported SOCKS version: " + version);
if (reply != 0) {
if (reply < ERRORS.length)
throw new IOException("Connection failed: " + ERRORS[reply]);
else throw new IOException("Connection failed: " + reply);
}
if (reply != 0)
throw new IOException("Connection failed: " + reply);
if (addressType == 1) IoUtils.read(in, new byte[4]); // IPv4
else if (addressType == 4) IoUtils.read(in, new byte[16]); // IPv6
else throw new IOException("Unsupported address type: " + addressType);

View File

@@ -11,18 +11,16 @@ import javax.net.SocketFactory;
class SocksSocketFactory extends SocketFactory {
private final SocketAddress proxy;
private final int connectToProxyTimeout, extraSocketTimeout;
private final int connectToProxyTimeout;
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
int extraSocketTimeout) {
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@Override
public Socket createSocket() {
return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
return new SocksSocket(proxy, connectToProxyTimeout);
}
@Override

View File

@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.PacketWriter;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
@@ -37,19 +37,19 @@ 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.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
/**
* An outgoing {@link SyncSession} suitable for duplex transports. The session
* offers messages before sending them, keeps its output stream open when there
* are no records to send, and reacts to events that make records available to
* are no packets to send, and reacts to events that make packets available to
* send.
*/
@ThreadSafe
@NotNullByDefault
class DuplexOutgoingSession implements SyncSession, EventListener {
// Check for retransmittable records once every 60 seconds
// Check for retransmittable packets once every 60 seconds
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
private static final Logger LOG =
Logger.getLogger(DuplexOutgoingSession.class.getName());
@@ -67,14 +67,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final Clock clock;
private final ContactId contactId;
private final int maxLatency, maxIdleTime;
private final RecordWriter recordWriter;
private final PacketWriter packetWriter;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
private volatile boolean interrupted = false;
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
int maxIdleTime, RecordWriter recordWriter) {
int maxIdleTime, PacketWriter packetWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
@@ -82,7 +82,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
this.contactId = contactId;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.recordWriter = recordWriter;
this.packetWriter = packetWriter;
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
}
@@ -91,7 +91,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() throws IOException {
eventBus.addListener(this);
try {
// Start a query for each type of record
// Start a query for each type of packet
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
@@ -100,33 +100,33 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
long nextKeepalive = now + maxIdleTime;
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
boolean dataToFlush = true;
// Write records until interrupted
// Write packets until interrupted
try {
while (!interrupted) {
// Work out how long we should wait for a record
// Work out how long we should wait for a packet
now = clock.currentTimeMillis();
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
if (wait < 0) wait = 0;
// Flush any unflushed data if we're going to wait
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
recordWriter.flush();
packetWriter.flush();
dataToFlush = false;
nextKeepalive = now + maxIdleTime;
}
// Wait for a record
// Wait for a packet
ThrowingRunnable<IOException> task = writerTasks.poll(wait,
MILLISECONDS);
if (task == null) {
now = clock.currentTimeMillis();
if (now >= nextRetxQuery) {
// Check for retransmittable records
// Check for retransmittable packets
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
nextRetxQuery = now + RETX_QUERY_INTERVAL;
}
if (now >= nextKeepalive) {
// Flush the stream to keep it alive
recordWriter.flush();
packetWriter.flush();
dataToFlush = false;
nextKeepalive = now + maxIdleTime;
}
@@ -137,9 +137,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
dataToFlush = true;
}
}
if (dataToFlush) recordWriter.flush();
if (dataToFlush) packetWriter.flush();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for a record to write");
LOG.info("Interrupted while waiting for a packet to write");
Thread.currentThread().interrupt();
}
} finally {
@@ -215,7 +215,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() throws IOException {
if (interrupted) return;
recordWriter.writeAck(ack);
packetWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(new GenerateAck());
}
@@ -232,7 +232,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Transaction txn = db.startTransaction(false);
try {
b = db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -259,7 +259,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() throws IOException {
if (interrupted) return;
for (byte[] raw : batch) recordWriter.writeMessage(raw);
for (byte[] raw : batch) packetWriter.writeMessage(raw);
LOG.info("Sent batch");
dbExecutor.execute(new GenerateBatch());
}
@@ -303,7 +303,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() throws IOException {
if (interrupted) return;
recordWriter.writeOffer(offer);
packetWriter.writeOffer(offer);
LOG.info("Sent offer");
dbExecutor.execute(new GenerateOffer());
}
@@ -346,7 +346,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() throws IOException {
if (interrupted) return;
recordWriter.writeRequest(request);
packetWriter.writeRequest(request);
LOG.info("Sent request");
dbExecutor.execute(new GenerateRequest());
}

View File

@@ -16,7 +16,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.PacketReader;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncSession;
@@ -42,18 +42,18 @@ class IncomingSession implements SyncSession, EventListener {
private final Executor dbExecutor;
private final EventBus eventBus;
private final ContactId contactId;
private final RecordReader recordReader;
private final PacketReader packetReader;
private volatile boolean interrupted = false;
IncomingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId,
RecordReader recordReader) {
PacketReader packetReader) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.contactId = contactId;
this.recordReader = recordReader;
this.packetReader = packetReader;
}
@IoExecutor
@@ -61,22 +61,21 @@ class IncomingSession implements SyncSession, EventListener {
public void run() throws IOException {
eventBus.addListener(this);
try {
// Read records until interrupted or EOF
while (!interrupted && !recordReader.eof()) {
if (recordReader.hasAck()) {
Ack a = recordReader.readAck();
// Read packets until interrupted or EOF
while (!interrupted && !packetReader.eof()) {
if (packetReader.hasAck()) {
Ack a = packetReader.readAck();
dbExecutor.execute(new ReceiveAck(a));
} else if (recordReader.hasMessage()) {
Message m = recordReader.readMessage();
} else if (packetReader.hasMessage()) {
Message m = packetReader.readMessage();
dbExecutor.execute(new ReceiveMessage(m));
} else if (recordReader.hasOffer()) {
Offer o = recordReader.readOffer();
} else if (packetReader.hasOffer()) {
Offer o = packetReader.readOffer();
dbExecutor.execute(new ReceiveOffer(o));
} else if (recordReader.hasRequest()) {
Request r = recordReader.readRequest();
} else if (packetReader.hasRequest()) {
Request r = packetReader.readRequest();
dbExecutor.execute(new ReceiveRequest(r));
} else {
// unknown records are ignored in RecordReader#eof()
throw new FormatException();
}
}

View File

@@ -30,15 +30,11 @@ class MessageFactoryImpl implements MessageFactory {
public Message createMessage(GroupId g, long timestamp, byte[] body) {
if (body.length > MAX_MESSAGE_BODY_LENGTH)
throw new IllegalArgumentException();
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] idHash =
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
MessageId id = new MessageId(idHash);
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
MessageId id = new MessageId(crypto.hash(MessageId.LABEL, raw));
return new Message(id, g, timestamp, raw);
}

View File

@@ -0,0 +1,28 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.PacketReader;
import org.briarproject.bramble.api.sync.PacketReaderFactory;
import java.io.InputStream;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class PacketReaderFactoryImpl implements PacketReaderFactory {
private final CryptoComponent crypto;
@Inject
PacketReaderFactoryImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
@Override
public PacketReader createPacketReader(InputStream in) {
return new PacketReaderImpl(crypto, in);
}
}

View File

@@ -2,14 +2,14 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.PacketReader;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.util.ByteUtils;
@@ -20,84 +20,66 @@ import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
import static org.briarproject.bramble.api.sync.PacketTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
@NotThreadSafe
@NotNullByDefault
class RecordReaderImpl implements RecordReader {
class PacketReaderImpl implements PacketReader {
private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF}
private enum State { BUFFER_EMPTY, BUFFER_FULL, EOF }
private final MessageFactory messageFactory;
private final CryptoComponent crypto;
private final InputStream in;
private final byte[] header, payload;
private State state = State.BUFFER_EMPTY;
private int payloadLength = 0;
RecordReaderImpl(MessageFactory messageFactory, InputStream in) {
this.messageFactory = messageFactory;
PacketReaderImpl(CryptoComponent crypto, InputStream in) {
this.crypto = crypto;
this.in = in;
header = new byte[RECORD_HEADER_LENGTH];
payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
header = new byte[PACKET_HEADER_LENGTH];
payload = new byte[MAX_PACKET_PAYLOAD_LENGTH];
}
private void readRecord() throws IOException {
private void readPacket() throws IOException {
if (state != State.BUFFER_EMPTY) throw new IllegalStateException();
while (true) {
// Read the header
int offset = 0;
while (offset < RECORD_HEADER_LENGTH) {
int read =
in.read(header, offset, RECORD_HEADER_LENGTH - offset);
if (read == -1) {
if (offset > 0) throw new FormatException();
state = State.EOF;
return;
}
offset += read;
}
byte version = header[0], type = header[1];
payloadLength = ByteUtils.readUint16(header, 2);
// Check the protocol version
if (version != PROTOCOL_VERSION) throw new FormatException();
// Check the payload length
if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH)
throw new FormatException();
// Read the payload
offset = 0;
while (offset < payloadLength) {
int read = in.read(payload, offset, payloadLength - offset);
if (read == -1) throw new FormatException();
offset += read;
}
state = State.BUFFER_FULL;
// Return if this is a known record type, otherwise continue
if (type == ACK || type == MESSAGE || type == OFFER ||
type == REQUEST) {
// Read the header
int offset = 0;
while (offset < PACKET_HEADER_LENGTH) {
int read = in.read(header, offset, PACKET_HEADER_LENGTH - offset);
if (read == -1) {
if (offset > 0) throw new FormatException();
state = State.EOF;
return;
}
offset += read;
}
// Check the protocol version
if (header[0] != PROTOCOL_VERSION) throw new FormatException();
// Read the payload length
payloadLength = ByteUtils.readUint16(header, 2);
if (payloadLength > MAX_PACKET_PAYLOAD_LENGTH) throw new FormatException();
// Read the payload
offset = 0;
while (offset < payloadLength) {
int read = in.read(payload, offset, payloadLength - offset);
if (read == -1) throw new FormatException();
offset += read;
}
state = State.BUFFER_FULL;
}
/**
* Returns true if there's another record available or false if we've
* reached the end of the input stream.
* <p>
* If a record is available, it's been read into the buffer by the time
* eof() returns, so the method that called eof() can access the record
* from the buffer, for example to check its type or extract its payload.
*/
@Override
public boolean eof() throws IOException {
if (state == State.BUFFER_EMPTY) readRecord();
if (state == State.BUFFER_EMPTY) readPacket();
if (state == State.BUFFER_EMPTY) throw new IllegalStateException();
return state == State.EOF;
}
@@ -142,12 +124,13 @@ class RecordReaderImpl implements RecordReader {
// Timestamp
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException();
// Body
byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH];
System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
payloadLength - MESSAGE_HEADER_LENGTH);
// Raw message
byte[] raw = new byte[payloadLength];
System.arraycopy(payload, 0, raw, 0, payloadLength);
state = State.BUFFER_EMPTY;
return messageFactory.createMessage(groupId, timestamp, body);
// Message ID
MessageId messageId = new MessageId(crypto.hash(MessageId.LABEL, raw));
return new Message(messageId, groupId, timestamp, raw);
}
@Override
@@ -171,5 +154,4 @@ class RecordReaderImpl implements RecordReader {
if (!hasRequest()) throw new FormatException();
return new Request(readMessageIds());
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.PacketWriter;
import org.briarproject.bramble.api.sync.PacketWriterFactory;
import java.io.OutputStream;
@NotNullByDefault
class PacketWriterFactoryImpl implements PacketWriterFactory {
@Override
public PacketWriter createPacketWriter(OutputStream out) {
return new PacketWriterImpl(out);
}
}

View File

@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordTypes;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.PacketTypes;
import org.briarproject.bramble.api.sync.PacketWriter;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.util.ByteUtils;
@@ -15,30 +15,30 @@ import java.io.OutputStream;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@NotThreadSafe
@NotNullByDefault
class RecordWriterImpl implements RecordWriter {
class PacketWriterImpl implements PacketWriter {
private final OutputStream out;
private final byte[] header;
private final ByteArrayOutputStream payload;
RecordWriterImpl(OutputStream out) {
PacketWriterImpl(OutputStream out) {
this.out = out;
header = new byte[RECORD_HEADER_LENGTH];
header = new byte[PACKET_HEADER_LENGTH];
header[0] = PROTOCOL_VERSION;
payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
payload = new ByteArrayOutputStream(MAX_PACKET_PAYLOAD_LENGTH);
}
private void writeRecord(byte recordType) throws IOException {
header[1] = recordType;
private void writePacket(byte packetType) throws IOException {
header[1] = packetType;
ByteUtils.writeUint16(payload.size(), header, 2);
out.write(header);
payload.writeTo(out);
@@ -49,12 +49,12 @@ class RecordWriterImpl implements RecordWriter {
public void writeAck(Ack a) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
writeRecord(ACK);
writePacket(ACK);
}
@Override
public void writeMessage(byte[] raw) throws IOException {
header[1] = RecordTypes.MESSAGE;
header[1] = PacketTypes.MESSAGE;
ByteUtils.writeUint16(raw.length, header, 2);
out.write(header);
out.write(raw);
@@ -64,14 +64,14 @@ class RecordWriterImpl implements RecordWriter {
public void writeOffer(Offer o) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
writeRecord(OFFER);
writePacket(OFFER);
}
@Override
public void writeRequest(Request r) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
writeRecord(REQUEST);
writePacket(REQUEST);
}
@Override

View File

@@ -1,28 +0,0 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.RecordReaderFactory;
import java.io.InputStream;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class RecordReaderFactoryImpl implements RecordReaderFactory {
private final MessageFactory messageFactory;
@Inject
RecordReaderFactoryImpl(MessageFactory messageFactory) {
this.messageFactory = messageFactory;
}
@Override
public RecordReader createRecordReader(InputStream in) {
return new RecordReaderImpl(messageFactory, in);
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.RecordWriterFactory;
import java.io.OutputStream;
@NotNullByDefault
class RecordWriterFactoryImpl implements RecordWriterFactory {
@Override
public RecordWriter createRecordWriter(OutputStream out) {
return new RecordWriterImpl(out);
}
}

View File

@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.PacketWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import java.io.IOException;
@@ -29,12 +29,12 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
/**
* An outgoing {@link SyncSession} suitable for simplex transports. The session
* sends messages without offering them first, and closes its output stream
* when there are no more records to send.
* when there are no more packets to send.
*/
@ThreadSafe
@NotNullByDefault
@@ -55,7 +55,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final EventBus eventBus;
private final ContactId contactId;
private final int maxLatency;
private final RecordWriter recordWriter;
private final PacketWriter packetWriter;
private final AtomicInteger outstandingQueries;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -63,14 +63,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId,
int maxLatency, RecordWriter recordWriter) {
int maxLatency, PacketWriter packetWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.contactId = contactId;
this.maxLatency = maxLatency;
this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record
this.packetWriter = packetWriter;
outstandingQueries = new AtomicInteger(2); // One per type of packet
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
}
@@ -79,19 +79,19 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
public void run() throws IOException {
eventBus.addListener(this);
try {
// Start a query for each type of record
// Start a query for each type of packet
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
// Write records until interrupted or no more records to write
// Write packets until interrupted or no more packets to write
try {
while (!interrupted) {
ThrowingRunnable<IOException> task = writerTasks.take();
if (task == CLOSE) break;
task.run();
}
recordWriter.flush();
packetWriter.flush();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for a record to write");
LOG.info("Interrupted while waiting for a packet to write");
Thread.currentThread().interrupt();
}
} finally {
@@ -157,7 +157,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() throws IOException {
if (interrupted) return;
recordWriter.writeAck(ack);
packetWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(new GenerateAck());
}
@@ -174,7 +174,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
Transaction txn = db.startTransaction(false);
try {
b = db.generateBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -202,7 +202,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() throws IOException {
if (interrupted) return;
for (byte[] raw : batch) recordWriter.writeMessage(raw);
for (byte[] raw : batch) packetWriter.writeMessage(raw);
LOG.info("Sent batch");
dbExecutor.execute(new GenerateBatch());
}

View File

@@ -1,16 +1,14 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.RecordReaderFactory;
import org.briarproject.bramble.api.sync.RecordWriterFactory;
import org.briarproject.bramble.api.sync.PacketReaderFactory;
import org.briarproject.bramble.api.sync.PacketWriterFactory;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
@@ -31,16 +29,6 @@ public class SyncModule {
ValidationManager validationManager;
}
/**
* The maximum number of validation tasks to delegate to the crypto
* executor concurrently.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_CONCURRENT_VALIDATION_TASKS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
@Provides
GroupFactory provideGroupFactory(CryptoComponent crypto) {
return new GroupFactoryImpl(crypto);
@@ -52,42 +40,31 @@ public class SyncModule {
}
@Provides
RecordReaderFactory provideRecordReaderFactory(
RecordReaderFactoryImpl recordReaderFactory) {
return recordReaderFactory;
PacketReaderFactory providePacketReaderFactory(CryptoComponent crypto) {
return new PacketReaderFactoryImpl(crypto);
}
@Provides
RecordWriterFactory provideRecordWriterFactory() {
return new RecordWriterFactoryImpl();
PacketWriterFactory providePacketWriterFactory() {
return new PacketWriterFactoryImpl();
}
@Provides
@Singleton
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
Clock clock, RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory) {
Clock clock, PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory) {
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
recordReaderFactory, recordWriterFactory);
packetReaderFactory, packetWriterFactory);
}
@Provides
@Singleton
ValidationManager provideValidationManager(
LifecycleManager lifecycleManager, EventBus eventBus,
ValidationManagerImpl validationManager) {
ValidationManager getValidationManager(LifecycleManager lifecycleManager,
EventBus eventBus, ValidationManagerImpl validationManager) {
lifecycleManager.registerService(validationManager);
eventBus.addListener(validationManager);
return validationManager;
}
@Provides
@Singleton
@ValidationExecutor
Executor provideValidationExecutor(
@CryptoExecutor Executor cryptoExecutor) {
return new PoliteExecutor("ValidationExecutor", cryptoExecutor,
MAX_CONCURRENT_VALIDATION_TASKS);
}
}

View File

@@ -5,10 +5,10 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.RecordReaderFactory;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.RecordWriterFactory;
import org.briarproject.bramble.api.sync.PacketReader;
import org.briarproject.bramble.api.sync.PacketReaderFactory;
import org.briarproject.bramble.api.sync.PacketWriter;
import org.briarproject.bramble.api.sync.PacketWriterFactory;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.system.Clock;
@@ -28,41 +28,41 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
private final Executor dbExecutor;
private final EventBus eventBus;
private final Clock clock;
private final RecordReaderFactory recordReaderFactory;
private final RecordWriterFactory recordWriterFactory;
private final PacketReaderFactory packetReaderFactory;
private final PacketWriterFactory packetWriterFactory;
@Inject
SyncSessionFactoryImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
Clock clock, RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory) {
Clock clock, PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.clock = clock;
this.recordReaderFactory = recordReaderFactory;
this.recordWriterFactory = recordWriterFactory;
this.packetReaderFactory = packetReaderFactory;
this.packetWriterFactory = packetWriterFactory;
}
@Override
public SyncSession createIncomingSession(ContactId c, InputStream in) {
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
PacketReader packetReader = packetReaderFactory.createPacketReader(in);
return new IncomingSession(db, dbExecutor, eventBus, c, packetReader);
}
@Override
public SyncSession createSimplexOutgoingSession(ContactId c,
int maxLatency, OutputStream out) {
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
maxLatency, recordWriter);
maxLatency, packetWriter);
}
@Override
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, OutputStream out) {
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
maxLatency, maxIdleTime, recordWriter);
maxLatency, maxIdleTime, packetWriter);
}
}

View File

@@ -1,25 +0,0 @@
package org.briarproject.bramble.sync;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for validation tasks. Also used for
* annotating methods that should run on the validation executor.
* <p>
* The contract of this executor is that tasks may be run concurrently, and
* submitting a task will never block. Tasks must not run indefinitely. Tasks
* submitted during shutdown are discarded.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
@interface ValidationExecutor {
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
@@ -49,7 +50,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
Logger.getLogger(ValidationManagerImpl.class.getName());
private final DatabaseComponent db;
private final Executor dbExecutor, validationExecutor;
private final Executor dbExecutor;
private final Executor cryptoExecutor;
private final MessageFactory messageFactory;
private final Map<ClientId, MessageValidator> validators;
private final Map<ClientId, IncomingMessageHook> hooks;
@@ -58,11 +60,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
@Inject
ValidationManagerImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor,
@ValidationExecutor Executor validationExecutor,
@CryptoExecutor Executor cryptoExecutor,
MessageFactory messageFactory) {
this.db = db;
this.dbExecutor = dbExecutor;
this.validationExecutor = validationExecutor;
this.cryptoExecutor = cryptoExecutor;
this.messageFactory = messageFactory;
validators = new ConcurrentHashMap<ClientId, MessageValidator>();
hooks = new ConcurrentHashMap<ClientId, IncomingMessageHook>();
@@ -102,7 +104,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void validateOutstandingMessages(ClientId c) {
try {
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
@@ -129,7 +130,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void validateNextMessage(Queue<MessageId> unvalidated) {
try {
Message m;
@@ -167,7 +167,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void deliverOutstandingMessages(ClientId c) {
try {
Queue<MessageId> pending = new LinkedList<MessageId>();
@@ -195,7 +194,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void deliverNextPendingMessage(Queue<MessageId> pending) {
try {
boolean anyInvalid = false, allDelivered = true;
@@ -222,8 +220,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
Message m = messageFactory.createMessage(id, raw);
Group g = db.getGroup(txn, m.getGroupId());
ClientId c = g.getClientId();
Metadata meta =
db.getMessageMetadataForValidator(txn, id);
Metadata meta = db.getMessageMetadataForValidator(txn,
id);
DeliveryResult result = deliverMessage(txn, m, c, meta);
if (result.valid) {
pending.addAll(getPendingDependents(txn, id));
@@ -242,8 +240,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
db.endTransaction(txn);
}
if (invalidate != null) invalidateNextMessageAsync(invalidate);
if (toShare != null) shareNextMessageAsync(toShare);
deliverNextPendingMessageAsync(pending);
if (toShare != null) shareNextMessageAsync(toShare);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before delivery");
deliverNextPendingMessageAsync(pending);
@@ -251,12 +249,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
LOG.info("Group removed before delivery");
deliverNextPendingMessageAsync(pending);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
private void validateMessageAsync(final Message m, final Group g) {
validationExecutor.execute(new Runnable() {
cryptoExecutor.execute(new Runnable() {
@Override
public void run() {
validateMessage(m, g);
@@ -264,12 +263,10 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@ValidationExecutor
private void validateMessage(Message m, Group g) {
MessageValidator v = validators.get(g.getClientId());
if (v == null) {
if (LOG.isLoggable(WARNING))
LOG.warning("No validator for " + g.getClientId().getString());
LOG.warning("No validator");
} else {
try {
MessageContext context = v.validateMessage(m, g);
@@ -294,7 +291,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void storeMessageContext(Message m, ClientId c,
MessageContext context) {
try {
@@ -357,7 +353,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
}
@DatabaseExecutor
private DeliveryResult deliverMessage(Transaction txn, Message m,
ClientId c, Metadata meta) throws DbException {
// Deliver the message to the client if it's registered a hook
@@ -367,7 +362,10 @@ class ValidationManagerImpl implements ValidationManager, Service,
try {
shareMsg = hook.incomingMessage(txn, m, meta);
} catch (InvalidMessageException e) {
invalidateMessage(txn, m.getId());
// message is invalid, mark it as such and delete it
db.setMessageState(txn, m.getId(), INVALID);
db.deleteMessageMetadata(txn, m.getId());
db.deleteMessage(txn, m.getId());
return new DeliveryResult(false, false);
}
}
@@ -375,7 +373,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
return new DeliveryResult(true, shareMsg);
}
@DatabaseExecutor
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
throws DbException {
Queue<MessageId> pending = new LinkedList<MessageId>();
@@ -395,7 +392,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void shareOutstandingMessages(ClientId c) {
try {
Queue<MessageId> toShare = new LinkedList<MessageId>();
@@ -428,7 +424,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void shareNextMessage(Queue<MessageId> toShare) {
try {
Transaction txn = db.startTransaction(false);
@@ -462,7 +457,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void invalidateNextMessage(Queue<MessageId> invalidate) {
try {
Transaction txn = db.startTransaction(false);
@@ -485,7 +479,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
}
@DatabaseExecutor
private void invalidateMessage(Transaction txn, MessageId m)
throws DbException {
db.setMessageState(txn, m, INVALID);
@@ -493,7 +486,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
db.deleteMessageMetadata(txn, m);
}
@DatabaseExecutor
private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
MessageId m) throws DbException {
Queue<MessageId> invalidate = new LinkedList<MessageId>();
@@ -523,7 +515,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void loadGroupAndValidate(final Message m) {
try {
Group g;
@@ -543,7 +534,6 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
private static class DeliveryResult {
private final boolean valid, share;
private DeliveryResult(boolean valid, boolean share) {

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
// Contribute whatever slightly unpredictable info we have to the pool
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeLong(System.currentTimeMillis());
out.writeLong(System.nanoTime());
out.writeLong(Runtime.getRuntime().freeMemory());
List<NetworkInterface> ifaces =
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface i : ifaces) {
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
for (InetAddress a : addrs) out.write(a.getAddress());
byte[] hardware = i.getHardwareAddress();
if (hardware != null) out.write(hardware);
}
for (Entry<String, String> e : System.getenv().entrySet()) {
out.writeUTF(e.getKey());
out.writeUTF(e.getValue());
}
Properties properties = System.getProperties();
for (String key : properties.stringPropertyNames())
out.writeUTF(properties.getProperty(key));
}
}

View File

@@ -1,69 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Provider;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomProvider.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final AtomicBoolean seeded = new AtomicBoolean(false);
private final File outputDevice;
LinuxSecureRandomProvider() {
this(RANDOM_DEVICE);
}
LinuxSecureRandomProvider(File outputDevice) {
this.outputDevice = outputDevice;
}
@Override
public Provider getProvider() {
if (!seeded.getAndSet(true)) writeSeed();
return new LinuxProvider();
}
protected void writeSeed() {
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputDevice));
writeToEntropyPool(out);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private static class LinuxProvider extends Provider {
private LinuxProvider() {
super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some callers
// explicitly request a SHA1PRNG SecureRandom and we need to
// prevent them from getting the default implementation whose
// output may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
}

View File

@@ -1,64 +0,0 @@
package org.briarproject.bramble.system;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.SecureRandomSpi;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
public class LinuxSecureRandomSpi extends SecureRandomSpi {
private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomSpi.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final File inputDevice, outputDevice;
public LinuxSecureRandomSpi() {
this(RANDOM_DEVICE, RANDOM_DEVICE);
}
LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
this.inputDevice = inputDevice;
this.outputDevice = outputDevice;
}
@Override
protected void engineSetSeed(byte[] seed) {
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputDevice));
out.write(seed);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
protected void engineNextBytes(byte[] bytes) {
try {
DataInputStream in = new DataInputStream(
new FileInputStream(inputDevice));
in.readFully(bytes);
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected byte[] engineGenerateSeed(int len) {
byte[] seed = new byte[len];
engineNextBytes(seed);
return seed;
}
}

View File

@@ -0,0 +1,75 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SeedProvider;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
class LinuxSeedProvider implements SeedProvider {
private static final Logger LOG =
Logger.getLogger(LinuxSeedProvider.class.getName());
private final String outputFile, inputFile;
LinuxSeedProvider() {
this("/dev/urandom", "/dev/urandom");
}
LinuxSeedProvider(String outputFile, String inputFile) {
this.outputFile = outputFile;
this.inputFile = inputFile;
}
@Override
public byte[] getSeed() {
byte[] seed = new byte[SEED_BYTES];
// Contribute whatever slightly unpredictable info we have to the pool
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputFile));
writeToEntropyPool(out);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// Read the seed from the pool
try {
DataInputStream in = new DataInputStream(
new FileInputStream(inputFile));
in.readFully(seed);
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return seed;
}
void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeLong(System.currentTimeMillis());
out.writeLong(System.nanoTime());
List<NetworkInterface> ifaces =
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface i : ifaces) {
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
for (InetAddress a : addrs) out.write(a.getAddress());
}
}
}

View File

@@ -1,142 +0,0 @@
package org.briarproject.bramble;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class PoliteExecutorTest extends BrambleTestCase {
private static final String TAG = "Test";
private static final int TASKS = 10;
@Test
public void testTasksAreDelegatedInOrderOfSubmission() throws Exception {
// Delegate to a single-threaded executor
Executor delegate = Executors.newSingleThreadExecutor();
// Allow all the tasks to be delegated straight away
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
list.add(result);
latch.countDown();
}
});
}
// Wait for all the tasks to finish
latch.await();
// The tasks should have run in the order they were submitted
assertEquals(ascendingOrder(), list);
}
@Test
public void testQueuedTasksAreDelegatedInOrderOfSubmission()
throws Exception {
// Delegate to a single-threaded executor
Executor delegate = Executors.newSingleThreadExecutor();
// Allow two tasks to be delegated at a time
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 2);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
list.add(result);
latch.countDown();
}
});
}
// Wait for all the tasks to finish
latch.await();
// The tasks should have run in the order they were submitted
assertEquals(ascendingOrder(), list);
}
@Test
public void testTasksRunInParallelOnDelegate() throws Exception {
// Delegate to a multi-threaded executor
Executor delegate = Executors.newCachedThreadPool();
// Allow all the tasks to be delegated straight away
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch[] latches = new CountDownLatch[TASKS];
for (int i = 0; i < TASKS; i++) latches[i] = new CountDownLatch(1);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
try {
// Each task waits for the next task, if any, to finish
if (result < TASKS - 1) latches[result + 1].await();
list.add(result);
} catch (InterruptedException e) {
fail();
}
latches[result].countDown();
}
});
}
// Wait for all the tasks to finish
for (int i = 0; i < TASKS; i++) latches[i].await();
// The tasks should have finished in reverse order
assertEquals(descendingOrder(), list);
}
@Test
public void testTasksDoNotRunInParallelOnDelegate() throws Exception {
// Delegate to a multi-threaded executor
Executor delegate = Executors.newCachedThreadPool();
// Allow one task to be delegated at a time
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 1);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
try {
// Each task runs faster than the previous task
Thread.sleep(TASKS - result);
list.add(result);
} catch (InterruptedException e) {
fail();
}
latch.countDown();
}
});
}
// Wait for all the tasks to finish
latch.await();
// The tasks should have finished in the order they were submitted
assertEquals(ascendingOrder(), list);
}
private List<Integer> ascendingOrder() {
Integer[] array = new Integer[TASKS];
for (int i = 0; i < TASKS; i++) array[i] = i;
return Arrays.asList(array);
}
private List<Integer> descendingOrder() {
Integer[] array = new Integer[TASKS];
for (int i = 0; i < TASKS; i++) array[i] = TASKS - 1 - i;
return Arrays.asList(array);
}
}

View File

@@ -1,50 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
public class HashTest extends BrambleTestCase {
private final CryptoComponent crypto;
private final String label = TestUtils.getRandomString(42);
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
private final byte[] inputBytes2 = new byte[0];
public HashTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
@Test
public void testIdenticalInputsProduceIdenticalHashes() {
byte[] hash1 = crypto.hash(label, inputBytes, inputBytes1, inputBytes2);
byte[] hash2 = crypto.hash(label, inputBytes, inputBytes1, inputBytes2);
assertArrayEquals(hash1, hash2);
}
@Test
public void testDifferentInputsProduceDifferentHashes() {
byte[] hash1 = crypto.hash(label, inputBytes, inputBytes1, inputBytes2);
byte[] hash2 = crypto.hash(label, inputBytes2, inputBytes1, inputBytes);
assertFalse(Arrays.equals(hash1, hash2));
}
@Test
public void testDifferentLabelsProduceDifferentHashes() {
String label2 = TestUtils.getRandomString(42);
byte[] hash1 = crypto.hash(label, inputBytes, inputBytes1, inputBytes2);
byte[] hash2 =
crypto.hash(label2, inputBytes, inputBytes1, inputBytes2);
assertFalse(Arrays.equals(hash1, hash2));
}
}

View File

@@ -1,48 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
class PseudoSecureRandom extends SecureRandom {
private static final Provider PROVIDER = new PseudoSecureRandomProvider();
PseudoSecureRandom(byte[] seed) {
super(new PseudoSecureRandomSpi(seed), PROVIDER);
}
private static class PseudoSecureRandomSpi extends SecureRandomSpi {
private final PseudoRandom pseudoRandom;
private PseudoSecureRandomSpi(byte[] seed) {
pseudoRandom = new PseudoRandomImpl(seed);
}
@Override
protected byte[] engineGenerateSeed(int length) {
return pseudoRandom.nextBytes(length);
}
@Override
protected void engineNextBytes(byte[] b) {
byte[] random = pseudoRandom.nextBytes(b.length);
System.arraycopy(random, 0, b, 0, b.length);
}
@Override
protected void engineSetSeed(byte[] seed) {
// Thank you for your input
}
}
private static class PseudoSecureRandomProvider extends Provider {
private PseudoSecureRandomProvider() {
super("PseudoSecureRandom", 1.0, "Only for testing");
}
}
}

View File

@@ -1,251 +0,0 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.ByteUtils;
import org.jmock.Expectations;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class KeyAgreementTransportTest extends BrambleMockTestCase {
private final DuplexTransportConnection duplexTransportConnection =
context.mock(DuplexTransportConnection.class);
private final TransportConnectionReader transportConnectionReader =
context.mock(TransportConnectionReader.class);
private final TransportConnectionWriter transportConnectionWriter =
context.mock(TransportConnectionWriter.class);
private final TransportId transportId = new TransportId("test");
private final KeyAgreementConnection keyAgreementConnection =
new KeyAgreementConnection(duplexTransportConnection, transportId);
private ByteArrayInputStream inputStream;
private ByteArrayOutputStream outputStream;
private KeyAgreementTransport kat;
@Test
public void testSendKey() throws Exception {
setup(new byte[0]);
byte[] key = TestUtils.getRandomBytes(123);
kat.sendKey(key);
assertRecordSent(KEY, key);
}
@Test
public void testSendConfirm() throws Exception {
setup(new byte[0]);
byte[] confirm = TestUtils.getRandomBytes(123);
kat.sendConfirm(confirm);
assertRecordSent(CONFIRM, confirm);
}
@Test
public void testSendAbortWithException() throws Exception {
setup(new byte[0]);
context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(true, true);
oneOf(transportConnectionWriter).dispose(true);
}});
kat.sendAbort(true);
assertRecordSent(ABORT, new byte[0]);
}
@Test
public void testSendAbortWithoutException() throws Exception {
setup(new byte[0]);
context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(false, true);
oneOf(transportConnectionWriter).dispose(false);
}});
kat.sendAbort(false);
assertRecordSent(ABORT, new byte[0]);
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
throws Exception {
setup(new byte[0]);
kat.receiveKey();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfHeaderIsTooShort()
throws Exception {
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
input[0] = PROTOCOL_VERSION;
input[1] = KEY;
setup(input);
kat.receiveKey();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfPayloadIsTooShort()
throws Exception {
int payloadLength = 123;
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
input[0] = PROTOCOL_VERSION;
input[1] = KEY;
ByteUtils.writeUint16(payloadLength, input, 2);
setup(input);
kat.receiveKey();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception {
setup(createRecord((byte) (PROTOCOL_VERSION + 1), KEY, new byte[123]));
kat.receiveKey();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfAbortIsReceived()
throws Exception {
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0]));
kat.receiveKey();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfConfirmIsReceived()
throws Exception {
setup(createRecord(PROTOCOL_VERSION, CONFIRM, new byte[123]));
kat.receiveKey();
}
@Test
public void testReceiveKeySkipsUnrecognisedRecordTypes() throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1),
new byte[123]);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2),
new byte[0]);
byte[] payload = TestUtils.getRandomBytes(123);
byte[] key = createRecord(PROTOCOL_VERSION, KEY, payload);
ByteArrayOutputStream input = new ByteArrayOutputStream();
input.write(skip1);
input.write(skip2);
input.write(key);
setup(input.toByteArray());
assertArrayEquals(payload, kat.receiveKey());
}
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
throws Exception {
setup(new byte[0]);
kat.receiveConfirm();
}
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfHeaderIsTooShort()
throws Exception {
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
input[0] = PROTOCOL_VERSION;
input[1] = CONFIRM;
setup(input);
kat.receiveConfirm();
}
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfPayloadIsTooShort()
throws Exception {
int payloadLength = 123;
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
input[0] = PROTOCOL_VERSION;
input[1] = CONFIRM;
ByteUtils.writeUint16(payloadLength, input, 2);
setup(input);
kat.receiveConfirm();
}
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception {
setup(createRecord((byte) (PROTOCOL_VERSION + 1), CONFIRM,
new byte[123]));
kat.receiveConfirm();
}
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfAbortIsReceived()
throws Exception {
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0]));
kat.receiveConfirm();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfKeyIsReceived()
throws Exception {
setup(createRecord(PROTOCOL_VERSION, KEY, new byte[123]));
kat.receiveConfirm();
}
@Test
public void testReceiveConfirmSkipsUnrecognisedRecordTypes()
throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1),
new byte[123]);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2),
new byte[0]);
byte[] payload = TestUtils.getRandomBytes(123);
byte[] confirm = createRecord(PROTOCOL_VERSION, CONFIRM, payload);
ByteArrayOutputStream input = new ByteArrayOutputStream();
input.write(skip1);
input.write(skip2);
input.write(confirm);
setup(input.toByteArray());
assertArrayEquals(payload, kat.receiveConfirm());
}
private void setup(byte[] input) throws Exception {
inputStream = new ByteArrayInputStream(input);
outputStream = new ByteArrayOutputStream();
context.checking(new Expectations() {{
allowing(duplexTransportConnection).getReader();
will(returnValue(transportConnectionReader));
allowing(transportConnectionReader).getInputStream();
will(returnValue(inputStream));
allowing(duplexTransportConnection).getWriter();
will(returnValue(transportConnectionWriter));
allowing(transportConnectionWriter).getOutputStream();
will(returnValue(outputStream));
}});
kat = new KeyAgreementTransport(keyAgreementConnection);
}
private void assertRecordSent(byte expectedType, byte[] expectedPayload) {
byte[] output = outputStream.toByteArray();
assertEquals(RECORD_HEADER_LENGTH + expectedPayload.length,
output.length);
assertEquals(PROTOCOL_VERSION, output[0]);
assertEquals(expectedType, output[1]);
assertEquals(expectedPayload.length, ByteUtils.readUint16(output, 2));
byte[] payload = new byte[output.length - RECORD_HEADER_LENGTH];
System.arraycopy(output, RECORD_HEADER_LENGTH, payload, 0,
payload.length);
assertArrayEquals(expectedPayload, payload);
}
private byte[] createRecord(byte version, byte type, byte[] payload) {
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
b[0] = version;
b[1] = type;
ByteUtils.writeUint16(payload.length, b, 2);
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
return b;
}
}

View File

@@ -1,219 +0,0 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class RecordReaderImplTest extends BrambleMockTestCase {
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
byte[] b = createAck(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsEmpty() throws Exception {
byte[] b = createEmptyAck();
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
byte[] b = createOffer(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
byte[] b = createOffer(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
byte[] b = createEmptyOffer();
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
byte[] b = createRequest(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readRequest();
}
@Test
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
byte[] b = createRequest(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readRequest();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
byte[] b = createEmptyRequest();
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readRequest();
}
@Test
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
assertTrue(reader.eof());
}
@Test
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
assertFalse(reader.eof());
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfHeaderIsTooShort() throws Exception {
byte[] b = new byte[RECORD_HEADER_LENGTH - 1];
b[0] = PROTOCOL_VERSION;
b[1] = ACK;
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfPayloadIsTooShort() throws Exception {
int payloadLength = 123;
byte[] b = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
b[0] = PROTOCOL_VERSION;
b[1] = ACK;
ByteUtils.writeUint16(payloadLength, b, 2);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception {
byte version = (byte) (PROTOCOL_VERSION + 1);
byte[] b = createRecord(version, ACK, new byte[0]);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfPayloadIsTooLong() throws Exception {
byte[] payload = new byte[MAX_RECORD_PAYLOAD_LENGTH + 1];
byte[] b = createRecord(PROTOCOL_VERSION, ACK, payload);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test
public void testSkipsUnrecognisedRecordTypes() throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 1),
new byte[123]);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 2),
new byte[0]);
byte[] ack = createAck(false);
ByteArrayOutputStream input = new ByteArrayOutputStream();
input.write(skip1);
input.write(skip2);
input.write(ack);
ByteArrayInputStream in = new ByteArrayInputStream(input.toByteArray());
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
assertTrue(reader.hasAck());
Ack a = reader.readAck();
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
}
private byte[] createAck(boolean tooBig) throws Exception {
return createRecord(PROTOCOL_VERSION, ACK, createPayload(tooBig));
}
private byte[] createEmptyAck() throws Exception {
return createRecord(PROTOCOL_VERSION, ACK, new byte[0]);
}
private byte[] createOffer(boolean tooBig) throws Exception {
return createRecord(PROTOCOL_VERSION, OFFER, createPayload(tooBig));
}
private byte[] createEmptyOffer() throws Exception {
return createRecord(PROTOCOL_VERSION, OFFER, new byte[0]);
}
private byte[] createRequest(boolean tooBig) throws Exception {
return createRecord(PROTOCOL_VERSION, REQUEST, createPayload(tooBig));
}
private byte[] createEmptyRequest() throws Exception {
return createRecord(PROTOCOL_VERSION, REQUEST, new byte[0]);
}
private byte[] createRecord(byte version, byte type, byte[] payload) {
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
b[0] = version;
b[1] = type;
ByteUtils.writeUint16(payload.length, b, 2);
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
return b;
}
private byte[] createPayload(boolean tooBig) throws Exception {
ByteArrayOutputStream payload = new ByteArrayOutputStream();
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_LENGTH) {
payload.write(TestUtils.getRandomId());
}
if (tooBig) payload.write(TestUtils.getRandomId());
assertEquals(tooBig, payload.size() > MAX_RECORD_PAYLOAD_LENGTH);
return payload.toByteArray();
}
}

View File

@@ -1,57 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.security.Provider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class LinuxSecureRandomProviderTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testGetProviderWritesToRandomDeviceOnFirstCall()
throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the provider's output to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
// Getting a provider should write entropy to the file
Provider provider = p.getProvider();
assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName());
// There should be at least 16 bytes from the clock, 8 from the runtime
long length = urandom.length();
assertTrue(length >= 24);
// Getting another provider should not write to the file again
provider = p.getProvider();
assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName());
assertEquals(length, urandom.length());
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -1,128 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class LinuxSecureRandomSpiTest extends BrambleTestCase {
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private static final int SEED_BYTES = 32;
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testSeedsAreDistinct() {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
Set<Bytes> seeds = new HashSet<Bytes>();
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
for (int i = 0; i < 1000; i++) {
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
assertEquals(SEED_BYTES, seed.length);
assertTrue(seeds.add(new Bytes(seed)));
}
}
@Test
public void testEngineSetSeedWritesToRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the engine's output to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
// Generate a seed
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
// Check that the engine writes the seed to the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
urandom);
engine.engineSetSeed(seed);
assertEquals(SEED_BYTES, urandom.length());
byte[] written = new byte[SEED_BYTES];
FileInputStream in = new FileInputStream(urandom);
IoUtils.read(in, written);
in.close();
assertArrayEquals(seed, written);
}
@Test
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy);
out.flush();
out.close();
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
RANDOM_DEVICE);
byte[] b = new byte[SEED_BYTES];
engine.engineNextBytes(b);
assertArrayEquals(entropy, b);
}
@Test
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy);
out.flush();
out.close();
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
RANDOM_DEVICE);
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
assertArrayEquals(entropy, b);
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.security.Provider;
@NotNullByDefault
public class TestSecureRandomProvider implements SecureRandomProvider {
@Override
public Provider getProvider() {
// Use the default provider
return null;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class TestSeedProviderModule {
@Provides
@Singleton
SecureRandomProvider provideSeedProvider() {
return new TestSecureRandomProvider();
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.test;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
@Module
public class TestSocksModule {
@Provides
SocketFactory provideSocketFactory() {
return SocketFactory.getDefault();
}
}

View File

@@ -7,10 +7,4 @@ apply plugin: 'witness'
dependencies {
compile project(':bramble-core')
compile fileTree(dir: 'libs', include: '*.jar')
testCompile project(path: ':bramble-core', configuration: 'testOutput')
}
tasks.withType(Test) {
systemProperty 'java.library.path', 'libs'
}

View File

@@ -9,15 +9,14 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.OsUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
@@ -57,26 +56,50 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
class BluetoothPlugin implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final Semaphore discoverySemaphore = new Semaphore(1);
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile StreamConnectionNotifier socket = null;
private volatile LocalDevice localDevice = null;
BluetoothPlugin(Executor ioExecutor,
SecureRandom secureRandom,
Backoff backoff, int maxLatency,
DuplexPluginCallback callback) {
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
public void start() throws PluginException {
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// Initialise the Bluetooth stack
try {
@@ -85,14 +108,13 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
// On Linux the user may need to install libbluetooth-dev
if (OsUtils.isLinux())
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
throw new PluginException(e);
} catch (BluetoothStateException e) {
throw new PluginException(e);
return false;
}
if (LOG.isLoggable(INFO))
LOG.info("Local address " + localDevice.getBluetoothAddress());
running = true;
bind();
return true;
}
private void bind() {
@@ -115,7 +137,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return;
}
if (!running) {
tryToClose((S)ss);
tryToClose(ss);
return;
}
socket = ss;
@@ -129,16 +151,29 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
//
// private void tryToClose(@Nullable StreamConnectionNotifier ss) {
// try {
// if (ss != null) ss.close();
// } catch (IOException e) {
// if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
// } finally {
// callback.transportDisabled();
// }
// }
private String getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return uuid;
}
private void tryToClose(@Nullable StreamConnectionNotifier ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections(StreamConnectionNotifier ss) {
while (true) {
@@ -159,33 +194,54 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new BluetoothTransportConnection(this, s);
}
//
// @Override
// public void stop() {
// running = false;
// tryToClose(socket);
// }
@Override
protected void close(S ss) throws IOException {
((StreamConnection)ss).close();
public void stop() {
running = false;
tryToClose(socket);
}
@Override
public Runnable returnPollRunnable(final String address, final String uuid,
final ContactId c) {
public boolean isRunning() {
return running;
}
return new Runnable() {
@Override
public void run() {
if (!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(final Collection<ContactId> connected) {
if (!running) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
}
};
});
}
}
private StreamConnection connect(String url) {
@@ -201,13 +257,25 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
}
@Override
protected DuplexTransportConnection connectToAddress(String address, String uuid) {
String url = makeUrl(address, uuid);
public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
String url = makeUrl(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
@@ -227,7 +295,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null;
}
if (!running) {
tryToClose((S)ss);
tryToClose(ss);
return null;
}
// Create the background tasks
@@ -260,7 +328,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null;
} finally {
// Closing the socket will terminate the listener task
tryToClose((S)ss);
tryToClose(ss);
closeSockets(futures, chosen);
}
}
@@ -292,6 +360,11 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!running) return null;
@@ -310,7 +383,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null;
}
if (!running) {
tryToClose((S)ss);
tryToClose(ss);
return null;
}
BdfList descriptor = new BdfList();
@@ -320,6 +393,33 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) 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))
LOG.info("Connecting to key agreement UUID " + uuid);
String url = makeUrl(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
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 {

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
@@ -43,24 +42,17 @@ class RemovableDrivePlugin extends FilePlugin
}
@Override
public void start() throws PluginException {
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
try {
monitor.start(this);
} catch (IOException e) {
throw new PluginException(e);
}
monitor.start(this);
return true;
}
@Override
public void stop() throws PluginException {
public void stop() throws IOException {
running = false;
try {
monitor.stop();
} catch (IOException e) {
throw new PluginException(e);
}
monitor.stop();
}
@Override

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -69,7 +68,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
}
@Override
public void start() throws PluginException {
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO))
@@ -80,12 +79,12 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName);
running = true;
return;
return true;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
throw new PluginException();
return false;
}
@Override

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopSecureRandomModule {
@Provides
@Singleton
SecureRandomProvider provideSecureRandomProvider() {
return OsUtils.isLinux() ? new LinuxSecureRandomProvider() : null;
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopSeedProviderModule {
@Provides
@Singleton
SeedProvider provideSeedProvider() {
return OsUtils.isLinux() ? new LinuxSeedProvider() : null;
}
}

5
briar-android-tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
bin
gen
build
local.properties
.settings

View File

@@ -0,0 +1,27 @@
apply plugin: 'com.android.library'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
minSdkVersion 14
proguardFiles getDefaultProguardFile('proguard-android.txt'), '../briar-android/proguard-rules.txt'
consumerProguardFiles getDefaultProguardFile('proguard-android.txt'), '../briar-android/proguard-rules.txt'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
testCompile project(':briar-tests')
testCompile 'junit:junit:4.12'
testCompile 'net.jodah:concurrentunit:0.4.2'
testCompile 'com.android.support:appcompat-v7:23.2.1'
testApt 'com.google.dagger:dagger-compiler:2.0.2'
provided 'javax.annotation:jsr250-api:1.0'
}

View File

@@ -0,0 +1,12 @@
<manifest package="org.briarproject"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
>
</application>
</manifest>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">briar-android-tests</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More