Compare commits

..

2 Commits

Author SHA1 Message Date
akwizgran
b38c33bf58 Use WhisperSystems Curve25519 library. 2017-12-07 17:06:58 +00:00
akwizgran
5bb00597ef Add Curve25519 and Ed25519 to performance tests.
Note: Curve25519 is tested using standard ECDH and ECDHC over the Curve25519 curve.
2017-12-07 16:48:02 +00:00
283 changed files with 5534 additions and 10324 deletions

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="H2 Performance Test" 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="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.H2DatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<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="HyperSQL Performance Test" 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="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.HyperSqlDatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<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

@@ -55,16 +55,16 @@ dependencyVerification {
}
ext.torBinaryDir = 'src/main/res/raw'
ext.torVersion = '0.2.9.14'
ext.geoipVersion = '2017-11-06'
ext.torVersion = '0.2.9.12'
ext.geoipVersion = '2017-09-06'
ext.torDownloadUrl = 'https://briarproject.org/build/'
def torBinaries = [
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
"tor_arm" : '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688',
"tor_arm_pie": '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283',
"tor_x86" : '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab',
"tor_x86_pie": '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288',
"geoip" : 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
]
def downloadBinary(name) {

View File

@@ -10,8 +10,6 @@
-keep class net.i2p.crypto.eddsa.** { *; }
-keep class org.whispersystems.curve25519.** { *; }
-dontwarn sun.misc.Unsafe
-dontnote com.google.common.**

View File

@@ -13,8 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
@@ -23,7 +22,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.net.SocketFactory;
@@ -35,18 +33,16 @@ public class AndroidPluginModule {
@Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus) {
Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth =
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
appContext, random, eventBus, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
appContext, locationUtils, reporter, eventBus,
torSocketFactory, backoffFactory);
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
androidExecutor, appContext, random, eventBus, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
locationUtils, reporter, eventBus, torSocketFactory,
backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex =

View File

@@ -1,206 +0,0 @@
package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final Logger LOG =
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
private final AndroidExecutor androidExecutor;
private final Context appContext;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
}
@Override
public void start() throws PluginException {
super.start();
// Listen for changes to the Bluetooth state
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
}
@Override
public void stop() {
super.stop();
if (receiver != null) appContext.unregisterReceiver(receiver);
}
@Override
void initialiseAdapter() throws IOException {
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
try {
adapter = androidExecutor.runOnBackgroundThread(
BluetoothAdapter::getDefaultAdapter).get();
} catch (InterruptedException | ExecutionException e) {
throw new IOException(e);
}
if (adapter == null)
throw new IOException("Bluetooth is not supported");
}
@Override
boolean isAdapterEnabled() {
return adapter != null && adapter.isEnabled();
}
@Override
void enableAdapter() {
if (adapter != null && !adapter.isEnabled()) {
if (adapter.enable()) {
LOG.info("Enabling Bluetooth");
wasEnabledByUs = true;
} else {
LOG.info("Could not enable Bluetooth");
}
}
}
@Override
void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
wasEnabledByUs = false;
}
}
@Override
void setEnabledByUs() {
wasEnabledByUs = true;
}
@Override
@Nullable
String getBluetoothAddress() {
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
return address.isEmpty() ? null : address;
}
@Override
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
return adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", UUID.fromString(uuid));
}
@Override
void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
throws IOException {
return wrapSocket(ss.accept());
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this, s);
}
@Override
boolean isValidAddress(String address) {
return BluetoothAdapter.checkBluetoothAddress(address);
}
@Override
DuplexTransportConnection connectTo(String address, String uuid)
throws IOException {
BluetoothDevice d = adapter.getRemoteDevice(address);
UUID u = UUID.fromString(uuid);
BluetoothSocket s = null;
try {
s = d.createInsecureRfcommSocketToServiceRecord(u);
s.connect();
return wrapSocket(s);
} catch (IOException e) {
tryToClose(s);
throw e;
}
}
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);
}
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) onAdapterEnabled();
else if (state == STATE_OFF) onAdapterDisabled();
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_NONE) {
LOG.info("Scan mode: None");
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
LOG.info("Scan mode: Connectable");
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
LOG.info("Scan mode: Discoverable");
}
}
}
}

View File

@@ -0,0 +1,490 @@
package org.briarproject.bramble.plugin.droidtooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
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.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.plugin.event.DisableBluetoothEvent;
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
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;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class DroidtoothPlugin implements DuplexPlugin, EventListener {
private static final Logger LOG =
Logger.getLogger(DroidtoothPlugin.class.getName());
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;
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@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;
}
@Override
public void start() throws PluginException {
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
try {
adapter = androidExecutor.runOnBackgroundThread(
BluetoothAdapter::getDefaultAdapter).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.warning("Interrupted while getting BluetoothAdapter");
throw new PluginException(e);
} catch (ExecutionException e) {
throw new PluginException(e);
}
if (adapter == null) {
LOG.info("Bluetooth is not supported");
throw new PluginException();
}
running = true;
// Listen for changes to the Bluetooth state
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
// If Bluetooth is enabled, bind a socket
if (adapter.isEnabled()) {
bind();
} else {
// Enable Bluetooth if settings allow
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
enableAdapter();
} else {
LOG.info("Not enabling Bluetooth");
}
}
}
private void bind() {
ioExecutor.execute(() -> {
if (!isRunning()) return;
String address = AndroidUtils.getBluetoothAddress(appContext,
adapter);
if (LOG.isLoggable(INFO))
LOG.info("Local address " + scrubMacAddress(address));
if (!StringUtils.isNullOrEmpty(address)) {
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put(PROP_ADDRESS, address);
callback.mergeLocalProperties(p);
}
// Bind a server socket to accept connections from contacts
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", getUuid());
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return;
}
if (!isRunning()) {
tryToClose(ss);
return;
}
LOG.info("Socket bound");
socket = ss;
backoff.reset();
callback.transportEnabled();
acceptContactConnections();
});
}
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;
try {
s = socket.accept();
} catch (IOException e) {
// This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
return;
}
if (LOG.isLoggable(INFO)) {
String address = s.getRemoteDevice().getAddress();
LOG.info("Connection from " + scrubMacAddress(address));
}
backoff.reset();
callback.incomingConnectionCreated(wrapSocket(s));
}
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new DroidtoothTransportConnection(this, s);
}
private void enableAdapter() {
if (adapter != null && !adapter.isEnabled()) {
if (adapter.enable()) {
LOG.info("Enabling Bluetooth");
wasEnabledByUs = true;
} else {
LOG.info("Could not enable Bluetooth");
}
}
}
@Override
public void stop() {
running = false;
if (receiver != null) appContext.unregisterReceiver(receiver);
tryToClose(socket);
disableAdapter();
}
private void disableAdapter() {
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
}
}
@Override
public boolean isRunning() {
return running && adapter != null && adapter.isEnabled();
}
@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()) {
ContactId c = e.getKey();
if (connected.contains(c)) continue;
String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
});
}
}
@Nullable
private BluetoothSocket connect(String address, String uuid) {
// Validate the address
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
if (LOG.isLoggable(WARNING))
// not scrubbing here to be able to figure out the problem
LOG.warning("Invalid address " + address);
return null;
}
// Validate the UUID
UUID u;
try {
u = UUID.fromString(uuid);
} catch (IllegalArgumentException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
return null;
}
// Try to connect
BluetoothDevice d = adapter.getRemoteDevice(address);
BluetoothSocket s = null;
try {
s = d.createInsecureRfcommSocketToServiceRecord(u);
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
s.connect();
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubMacAddress(address));
return s;
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Failed to connect to " + scrubMacAddress(address)
+ ": " + e);
}
tryToClose(s);
return null;
}
}
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(c);
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 supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
// There's no point listening if we can't discover our own address
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
if (address.isEmpty()) return null;
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
// Bind a server socket for receiving key agreement connections
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", uuid);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
descriptor.add(StringUtils.macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, 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);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof EnableBluetoothEvent) {
enableAdapterAsync();
} else if (e instanceof DisableBluetoothEvent) {
disableAdapterAsync();
}
}
private void enableAdapterAsync() {
ioExecutor.execute(this::enableAdapter);
}
private void disableAdapterAsync() {
ioExecutor.execute(this::disableAdapter);
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) {
LOG.info("Bluetooth enabled");
bind();
} else if (state == STATE_OFF) {
LOG.info("Bluetooth disabled");
tryToClose(socket);
}
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_NONE) {
LOG.info("Scan mode: None");
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
LOG.info("Scan mode: Connectable");
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
LOG.info("Scan mode: Discoverable");
}
}
}
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
private final BluetoothServerSocket ss;
private BluetoothKeyAgreementListener(BdfList descriptor,
BluetoothServerSocket ss) {
super(descriptor);
this.ss = ss;
}
@Override
public Callable<KeyAgreementConnection> listen() {
return () -> {
BluetoothSocket s = ss.accept();
if (LOG.isLoggable(INFO))
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new DroidtoothTransportConnection(
DroidtoothPlugin.this, s), ID);
};
}
@Override
public void close() {
try {
ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.bluetooth;
package org.briarproject.bramble.plugin.droidtooth;
import android.content.Context;
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@Immutable
@NotNullByDefault
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
public class DroidtoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
@@ -35,7 +35,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final EventBus eventBus;
private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor,
public DroidtoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) {
@@ -61,7 +61,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
androidExecutor, appContext, secureRandom, backoff, callback,
MAX_LATENCY);
eventBus.addListener(plugin);

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.bluetooth;
package org.briarproject.bramble.plugin.droidtooth;
import android.bluetooth.BluetoothSocket;
@@ -11,12 +11,11 @@ import java.io.InputStream;
import java.io.OutputStream;
@NotNullByDefault
class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
private final BluetoothSocket socket;
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
super(plugin);
this.socket = socket;
}

View File

@@ -6,7 +6,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -20,15 +19,10 @@ import javax.annotation.Nullable;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.AndroidUtils.logNetworkState;
@NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final String WIFI_AP_STATE_ACTION =
"android.net.wifi.WIFI_AP_STATE_CHANGED";
private static final Logger LOG =
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
@@ -50,11 +44,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
running = true;
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(WIFI_AP_STATE_ACTION);
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
if (LOG.isLoggable(INFO)) logNetworkState(appContext, LOG);
}
@Override
@@ -70,27 +61,10 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
@Override
public void onReceive(Context ctx, Intent i) {
if (!running) return;
if (LOG.isLoggable(INFO)) {
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
LOG.info("Connectivity change");
Bundle extras = i.getExtras();
if (extras != null) {
LOG.info("Extras:");
for (String key : extras.keySet())
LOG.info("\t" + key + ": " + extras.get(key));
}
} else if (WIFI_AP_STATE_ACTION.equals(i.getAction())) {
int state = i.getIntExtra(EXTRA_WIFI_STATE, 0);
if (state == 13) LOG.info("Wifi AP enabled");
else LOG.info("Wifi AP state " + state);
}
logNetworkState(appContext, LOG);
}
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
if (net != null && net.getType() == TYPE_WIFI
&& net.isConnected()) {
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
LOG.info("Connected to Wi-Fi");
if (socket == null || socket.isClosed()) bind();
} else {

View File

@@ -59,12 +59,7 @@ import java.util.Map.Entry;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
@@ -75,15 +70,10 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
@@ -112,7 +102,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Logger.getLogger(TorPlugin.class.getName());
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext;
private final LocationUtils locationUtils;
private final DevReporter reporter;
@@ -125,9 +114,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final File torDirectory, torFile, geoIpFile, configFile;
private final File doneFile, cookieFile;
private final PowerManager.WakeLock wakeLock;
private final Lock connectionStatusLock;
private final AtomicReference<Future<?>> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
@@ -136,13 +122,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile TorControlConnection controlConnection = null;
private volatile BroadcastReceiver networkStateReceiver = null;
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
Context appContext, LocationUtils locationUtils,
DevReporter reporter, SocketFactory torSocketFactory,
Backoff backoff, DuplexPluginCallback callback,
String architecture, int maxLatency, int maxIdleTime) {
TorPlugin(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DevReporter reporter,
SocketFactory torSocketFactory, Backoff backoff,
DuplexPluginCallback callback, String architecture, int maxLatency,
int maxIdleTime) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext;
this.locationUtils = locationUtils;
this.reporter = reporter;
@@ -167,7 +152,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// This tag will prevent Huawei's powermanager from killing us.
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
wakeLock.setReferenceCounted(false);
connectionStatusLock = new ReentrantLock();
}
@Override
@@ -220,11 +204,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) {
if (stdout.hasNextLine()) {
while (stdout.hasNextLine() || stderr.hasNextLine()){
if(stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if (stderr.hasNextLine()) {
if(stderr.hasNextLine()){
LOG.info(stderr.nextLine());
}
}
@@ -273,11 +257,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(ACTION_SCREEN_ON);
filter.addAction(ACTION_SCREEN_OFF);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
// Bind a server socket to receive incoming hidden service connections
bind();
@@ -614,7 +594,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
byte[] commitment, BdfList descriptor, long timeout) {
throw new UnsupportedOperationException();
}
@@ -638,8 +618,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CLOSED") || status.equals("FAILED"))
updateConnectionStatus(); // Check whether we've lost connectivity
}
@Override
@@ -679,7 +657,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public void onEvent(int event, @Nullable String path) {
public void onEvent(int event, String path) {
stopWatching();
latch.countDown();
}
@@ -699,72 +677,53 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus() {
ioExecutor.execute(() -> {
if (!running) return;
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
connectionStatusLock.lock();
updateConnectionStatusLocked();
} finally {
connectionStatusLock.unlock();
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} 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)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
});
}
// Locking: connectionStatusLock
private void updateConnectionStatusLocked() {
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} 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)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void scheduleConnectionStatusUpdate() {
Future<?> newConnectivityCheck =
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
Future<?> oldConnectivityCheck =
connectivityCheck.getAndSet(newConnectivityCheck);
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
}
private class NetworkStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent i) {
if (!running) return;
String action = i.getAction();
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
updateConnectionStatus();
if (ACTION_SCREEN_ON.equals(action)
|| ACTION_SCREEN_OFF.equals(action)) {
scheduleConnectionStatusUpdate();
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
LOG.info("Detected connectivity change");
updateConnectionStatus();
}
}
}

View File

@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.util.AndroidUtils;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
@@ -37,7 +36,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext;
private final LocationUtils locationUtils;
private final DevReporter reporter;
@@ -45,13 +43,11 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
public TorPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, Context appContext,
public TorPluginFactory(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext;
this.locationUtils = locationUtils;
this.reporter = reporter;
@@ -93,9 +89,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
locationUtils, reporter, torSocketFactory, backoff, callback,
architecture, MAX_LATENCY, MAX_IDLE_TIME);
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
reporter, torSocketFactory, backoff, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -1,41 +1,18 @@
package org.briarproject.bramble.util;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import java.io.File;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.StringUtils.ipToString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@SuppressLint("HardwareIds")
public class AndroidUtils {
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
@@ -46,7 +23,7 @@ public class AndroidUtils {
@SuppressWarnings("deprecation")
public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>();
if (SDK_INT >= 21) {
if (Build.VERSION.SDK_INT >= 21) {
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS));
} else {
abis.add(Build.CPU_ABI);
@@ -90,123 +67,4 @@ public class AndroidUtils {
public static File getReportDir(Context ctx) {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
}
public static void logNetworkState(Context ctx, Logger logger) {
if (!logger.isLoggable(INFO)) return;
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
if (o == null) throw new AssertionError();
ConnectivityManager cm = (ConnectivityManager) o;
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
if (o == null) throw new AssertionError();
WifiManager wm = (WifiManager) o;
StringBuilder s = new StringBuilder();
logWifiInfo(s, wm.getConnectionInfo());
logNetworkInfo(s, cm.getActiveNetworkInfo(), true);
if (SDK_INT >= 21) {
for (Network network : cm.getAllNetworks())
logNetworkInfo(s, cm.getNetworkInfo(network), false);
} else {
for (NetworkInfo info : cm.getAllNetworkInfo())
logNetworkInfo(s, info, false);
}
try {
for (NetworkInterface iface : list(getNetworkInterfaces()))
logNetworkInterface(s, iface);
} catch (SocketException e) {
logger.log(WARNING, e.toString(), e);
}
logger.log(INFO, s.toString());
}
private static void logWifiInfo(StringBuilder s, @Nullable WifiInfo info) {
if (info == null) {
s.append("Wifi info: null\n");
return;
}
s.append("Wifi info:\n");
s.append("\tSSID: ").append(info.getSSID()).append("\n");
s.append("\tBSSID: ").append(info.getBSSID()).append("\n");
s.append("\tMAC address: ").append(info.getMacAddress()).append("\n");
s.append("\tIP address: ")
.append(ipToString(info.getIpAddress())).append("\n");
s.append("\tSupplicant state: ")
.append(info.getSupplicantState()).append("\n");
s.append("\tNetwork ID: ").append(info.getNetworkId()).append("\n");
s.append("\tLink speed: ").append(info.getLinkSpeed()).append("\n");
s.append("\tRSSI: ").append(info.getRssi()).append("\n");
if (info.getHiddenSSID()) s.append("\tHidden SSID\n");
if (SDK_INT >= 21)
s.append("\tFrequency: ").append(info.getFrequency()).append("\n");
}
private static void logNetworkInfo(StringBuilder s,
@Nullable NetworkInfo info, boolean active) {
if (info == null) {
if (active) s.append("Active network info: null\n");
else s.append("Network info: null\n");
return;
}
if (active) s.append("Active network info:\n");
else s.append("Network info:\n");
s.append("\tType: ").append(info.getTypeName())
.append(" (").append(info.getType()).append(")\n");
s.append("\tSubtype: ").append(info.getSubtypeName())
.append(" (").append(info.getSubtype()).append(")\n");
s.append("\tState: ").append(info.getState()).append("\n");
s.append("\tDetailed state: ")
.append(info.getDetailedState()).append("\n");
s.append("\tReason: ").append(info.getReason()).append("\n");
s.append("\tExtra info: ").append(info.getExtraInfo()).append("\n");
if (info.isAvailable()) s.append("\tAvailable\n");
if (info.isConnected()) s.append("\tConnected\n");
if (info.isConnectedOrConnecting())
s.append("\tConnected or connecting\n");
if (info.isFailover()) s.append("\tFailover\n");
if (info.isRoaming()) s.append("\tRoaming\n");
}
private static void logNetworkInterface(StringBuilder s,
NetworkInterface iface) throws SocketException {
s.append("Network interface:\n");
s.append("\tName: ").append(iface.getName()).append("\n");
s.append("\tDisplay name: ")
.append(iface.getDisplayName()).append("\n");
s.append("\tHardware address: ")
.append(hexOrNull(iface.getHardwareAddress())).append("\n");
if (iface.isLoopback()) s.append("\tLoopback\n");
if (iface.isPointToPoint()) s.append("\tPoint-to-point\n");
if (iface.isVirtual()) s.append("\tVirtual\n");
if (iface.isUp()) s.append("\tUp\n");
if (SDK_INT >= 19)
s.append("\tIndex: ").append(iface.getIndex()).append("\n");
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
s.append("\tInterface address:\n");
logInetAddress(s, addr.getAddress());
s.append("\t\tPrefix length: ")
.append(addr.getNetworkPrefixLength()).append("\n");
}
}
private static void logInetAddress(StringBuilder s, InetAddress addr) {
s.append("\t\tAddress: ")
.append(hexOrNull(addr.getAddress())).append("\n");
s.append("\t\tHost address: ")
.append(addr.getHostAddress()).append("\n");
if (addr.isLoopbackAddress()) s.append("\t\tLoopback\n");
if (addr.isLinkLocalAddress()) s.append("\t\tLink-local\n");
if (addr.isSiteLocalAddress()) s.append("\t\tSite-local\n");
if (addr.isAnyLocalAddress()) s.append("\t\tAny local (wildcard)\n");
if (addr.isMCNodeLocal()) s.append("\t\tMulticast node-local\n");
if (addr.isMCLinkLocal()) s.append("\t\tMulticast link-local\n");
if (addr.isMCSiteLocal()) s.append("\t\tMulticast site-local\n");
if (addr.isMCOrgLocal()) s.append("\t\tMulticast org-local\n");
if (addr.isMCGlobal()) s.append("\t\tMulticast global\n");
}
@Nullable
private static String hexOrNull(@Nullable byte[] b) {
return b == null ? null : toHexString(b);
}
}

View File

@@ -1,101 +0,0 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
public class Multiset<T> {
private final Map<T, Integer> map = new HashMap<>();
private int total = 0;
/**
* Returns how many items the multiset contains in total.
*/
public int getTotal() {
return total;
}
/**
* Returns how many unique items the multiset contains.
*/
public int getUnique() {
return map.size();
}
/**
* Returns how many of the given item the multiset contains.
*/
public int getCount(T t) {
Integer count = map.get(t);
return count == null ? 0 : count;
}
/**
* Adds the given item to the multiset and returns how many of the item
* the multiset now contains.
*/
public int add(T t) {
Integer count = map.get(t);
if (count == null) count = 0;
map.put(t, count + 1);
total++;
return count + 1;
}
/**
* Removes the given item from the multiset and returns how many of the
* item the multiset now contains.
* @throws NoSuchElementException if the item is not in the multiset.
*/
public int remove(T t) {
Integer count = map.get(t);
if (count == null) throw new NoSuchElementException();
if (count == 1) map.remove(t);
else map.put(t, count - 1);
total--;
return count - 1;
}
/**
* Removes all occurrences of the given item from the multiset.
*/
public int removeAll(T t) {
Integer count = map.remove(t);
if (count == null) return 0;
total -= count;
return count;
}
/**
* Returns true if the multiset contains any occurrences of the given item.
*/
public boolean contains(T t) {
return map.containsKey(t);
}
/**
* Removes all items from the multiset.
*/
public void clear() {
map.clear();
total = 0;
}
/**
* Returns the set of unique items the multiset contains. The returned set
* is unmodifiable.
*/
public Set<T> keySet() {
return Collections.unmodifiableSet(map.keySet());
}
}

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api;
import java.io.IOException;
/**
* An exception that indicates an unrecoverable version mismatch.
*/
public class UnsupportedVersionException extends IOException {
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
@@ -94,13 +93,10 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException;
BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException;
void verifySignature(String label, byte[] sig, byte[] publicKey,
BdfList signed) throws FormatException, GeneralSecurityException;
Author parseAndValidateAuthor(BdfList author) throws FormatException;
}

View File

@@ -1,13 +1,8 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.annotation.Nullable;
@NotNullByDefault
public interface CryptoComponent {
SecretKey generateSecretKey();
@@ -22,6 +17,10 @@ public interface CryptoComponent {
KeyParser getSignatureKeyParser();
KeyPair generateEdKeyPair();
KeyParser getEdKeyParser();
KeyParser getMessageKeyParser();
/**
@@ -49,7 +48,7 @@ public interface CryptoComponent {
throws GeneralSecurityException;
/**
* Signs the given byte[] with the given private key.
* Signs the given byte[] with the given ECDSA private key.
*
* @param label a namespaced label indicating the purpose of this
* signature, to prevent it from being repurposed or colliding with a
@@ -58,9 +57,18 @@ public interface CryptoComponent {
byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException;
/**
* Signs the given byte[] with the given Ed25519 private key.
*
* @param label A label specific to this signature
* to ensure that the signature cannot be repurposed
*/
byte[] signEd(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signed data
* and the given public key.
* and the given ECDSA public key.
*
* @param label a namespaced label indicating the purpose of this
* signature, to prevent it from being repurposed or colliding with a
@@ -70,6 +78,17 @@ public interface CryptoComponent {
boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signed data
* and the given Ed25519 public key.
*
* @param label A label that was specific to this signature
* to ensure that the signature cannot be repurposed
* @return true if the signature was valid, false otherwise.
*/
boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException;
/**
* Returns the hash of the given inputs. The inputs are unambiguously
* combined by prefixing each input with its length.
@@ -105,7 +124,6 @@ public interface CryptoComponent {
* given password. Returns null if the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
*/
@Nullable
byte[] decryptWithPassword(byte[] ciphertext, String password);
/**

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.api.crypto;
public interface CryptoConstants {
/**
* The maximum length of an agreement public key in bytes.
*/
int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 32;
/**
* The maximum length of a signature public key in bytes.
*/
int MAX_SIGNATURE_PUBLIC_KEY_BYTES = 32;
/**
* The maximum length of a signature in bytes.
*/
int MAX_SIGNATURE_BYTES = 64;
}

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
@NotNullByDefault
public interface ObjectReader<T> {
T readObject(BdfReader r) throws IOException;
}

View File

@@ -1,7 +0,0 @@
package org.briarproject.bramble.api.db;
/**
* Thrown when the database uses a newer schema than the current code.
*/
public class DataTooNewException extends DbException {
}

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.api.db;
/**
* Thrown when the database uses an older schema than the current code and
* cannot be migrated.
*/
public class DataTooOldException extends DbException {
}

View File

@@ -37,13 +37,8 @@ public interface DatabaseComponent {
/**
* Opens the database and returns true if the database already existed.
*
* @throws DataTooNewException if the data uses a newer schema than the
* current code
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/
boolean open(@Nullable MigrationListener listener) throws DbException;
boolean open() throws DbException;
/**
* Waits for any open transactions to finish and closes the database.
@@ -259,30 +254,31 @@ public interface DatabaseComponent {
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* Returns the IDs of any messages that need to be validated by the given
* client.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToValidate(Transaction txn)
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that are pending delivery due to
* dependencies on other messages.
* Returns the IDs of any messages that are valid but pending delivery due
* to dependencies on other messages for the given client.
* <p/>
* Read-only.
*/
Collection<MessageId> getPendingMessages(Transaction txn)
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that have shared dependents but have
* not yet been shared themselves.
* Returns the IDs of any messages from the given client
* that have a shared dependent, but are still not shared themselves.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToShare(Transaction txn)
throws DbException;
Collection<MessageId> getMessagesToShare(Transaction txn,
ClientId c) throws DbException;
/**
* Returns the message with the given ID, in serialised form, or null if
@@ -377,16 +373,6 @@ public interface DatabaseComponent {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException;
/*
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
* no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
/**
* Returns all settings in the given namespace.
* <p/>

View File

@@ -1,11 +0,0 @@
package org.briarproject.bramble.api.db;
public interface MigrationListener {
/**
* This is called when a migration is started while opening the database.
* It will be called once for each migration being applied.
*/
void onMigrationRun();
}

View File

@@ -1,13 +1,11 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import java.io.UnsupportedEncodingException;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
/**
* A pseudonym for a user.
*/
@@ -19,25 +17,20 @@ public class Author {
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
}
/**
* The current version of the author structure.
*/
public static final int FORMAT_VERSION = 1;
private final AuthorId id;
private final int formatVersion;
private final String name;
private final byte[] publicKey;
public Author(AuthorId id, int formatVersion, String name,
byte[] publicKey) {
int nameLength = StringUtils.toUtf8(name).length;
if (nameLength == 0 || nameLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
if (publicKey.length == 0 || publicKey.length > MAX_PUBLIC_KEY_LENGTH)
public Author(AuthorId id, String name, byte[] publicKey) {
int length;
try {
length = name.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
if (length == 0 || length > AuthorConstants.MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
this.id = id;
this.formatVersion = formatVersion;
this.name = name;
this.publicKey = publicKey;
}
@@ -49,13 +42,6 @@ public class Author {
return id;
}
/**
* Returns the version of the author structure used to create the author.
*/
public int getFormatVersion() {
return formatVersion;
}
/**
* Returns the author's name.
*/

View File

@@ -1,8 +1,5 @@
package org.briarproject.bramble.api.identity;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
public interface AuthorConstants {
/**
@@ -11,14 +8,26 @@ public interface AuthorConstants {
int MAX_AUTHOR_NAME_LENGTH = 50;
/**
* The maximum length of a public key in bytes. This applies to the
* signature algorithm used by the current {@link Author format version}.
* The maximum length of a public key in bytes.
* <p>
* Public keys use SEC1 format: 0x04 x y, where x and y are unsigned
* big-endian integers.
* <p>
* For a 256-bit elliptic curve, the maximum length is 2 * 256 / 8 + 1.
*/
int MAX_PUBLIC_KEY_LENGTH = MAX_SIGNATURE_PUBLIC_KEY_BYTES;
int MAX_PUBLIC_KEY_LENGTH = 65;
/**
* The maximum length of a signature in bytes. This applies to the
* signature algorithm used by the current {@link Author format version}.
* The maximum length of a signature in bytes.
* <p>
* A signature is an ASN.1 DER sequence containing two integers, r and s.
* The format is 0x30 len1 0x02 len2 r 0x02 len3 s, where len1 is
* len(0x02 len2 r 0x02 len3 s) as a DER length, len2 is len(r) as a DER
* length, len3 is len(s) as a DER length, and r and s are signed
* big-endian integers of minimal length.
* <p>
* For a 256-bit elliptic curve, the lengths are one byte each, so the
* maximum length is 2 * 256 / 8 + 8.
*/
int MAX_SIGNATURE_LENGTH = MAX_SIGNATURE_BYTES;
int MAX_SIGNATURE_LENGTH = 72;
}

View File

@@ -5,27 +5,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface AuthorFactory {
/**
* Creates an author with the current format version and the given name and
* public key.
*/
Author createAuthor(String name, byte[] publicKey);
/**
* Creates an author with the given format version, name and public key.
*/
Author createAuthor(int formatVersion, String name, byte[] publicKey);
/**
* Creates a local author with the current format version and the given
* name and keys.
*/
LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey);
/**
* Creates a local author with the given format version, name and keys.
*/
LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey);
}

View File

@@ -14,9 +14,9 @@ public class LocalAuthor extends Author {
private final byte[] privateKey;
private final long created;
public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, long created) {
super(id, formatVersion, name, publicKey);
public LocalAuthor(AuthorId id, String name, byte[] publicKey,
byte[] privateKey, long created) {
super(id, name, publicKey);
this.privateKey = privateKey;
this.created = created;
}

View File

@@ -3,9 +3,9 @@ package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants {
/**
* The current version of the BQP protocol. Version number 89 is reserved.
* The current version of the BQP protocol.
*/
byte PROTOCOL_VERSION = 4;
byte PROTOCOL_VERSION = 3;
/**
* The length of the record header in bytes.

View File

@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
import org.briarproject.bramble.api.data.BdfList;
import java.io.IOException;
import java.util.concurrent.Callable;
/**
* An class for managing a particular key agreement listener.
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
}
/**
* Blocks until an incoming connection is received and returns it.
*
* @throws IOException if an error occurs or {@link #close()} is called.
* Starts listening for incoming connections, and returns a Callable that
* will return a KeyAgreementConnection when an incoming connection is
* received.
*/
public abstract KeyAgreementConnection accept() throws IOException;
public abstract Callable<KeyAgreementConnection> listen();
/**
* Closes the underlying server socket.

View File

@@ -21,25 +21,7 @@ public interface LifecycleManager {
* The result of calling {@link #startServices(String)}.
*/
enum StartResult {
ALREADY_RUNNING,
DB_ERROR,
DATA_TOO_OLD_ERROR,
DATA_TOO_NEW_ERROR,
SERVICE_ERROR,
SUCCESS
}
/**
* The state the lifecycle can be in.
* Returned by {@link #getLifecycleState()}
*/
enum LifecycleState {
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
public boolean isAfter(LifecycleState state) {
return ordinal() > state.ordinal();
}
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
}
/**
@@ -89,10 +71,4 @@ public interface LifecycleManager {
* the {@link DatabaseComponent} to be closed before returning.
*/
void waitForShutdown() throws InterruptedException;
/**
* Returns the current state of the lifecycle.
*/
LifecycleState getLifecycleState();
}

View File

@@ -1,20 +0,0 @@
package org.briarproject.bramble.api.lifecycle.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
/**
* An event that is broadcast when the app enters a new lifecycle state.
*/
public class LifecycleEvent extends Event {
private final LifecycleState state;
public LifecycleEvent(LifecycleState state) {
this.state = state;
}
public LifecycleState getLifecycleState() {
return state;
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.lifecycle.event;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast when the app is shutting down.
*/
public class ShutdownEvent extends Event {
}

View File

@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
/**
* Attempts to connect to the remote peer specified in the given descriptor.
* Returns null if no connection can be established.
* Returns null if no connection can be established within the given time.
*/
@Nullable
DuplexTransportConnection createKeyAgreementConnection(
byte[] remoteCommitment, BdfList descriptor);
byte[] remoteCommitment, BdfList descriptor, long timeout);
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that informs the Bluetooth plugin that we have enabled the
* Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class BluetoothEnabledEvent extends Event {
}

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
*/
@Immutable
@NotNullByDefault

View File

@@ -126,10 +126,6 @@ public class StringUtils {
return toUtf8(s).length > maxLength;
}
public static boolean isValidMac(String mac) {
return MAC.matcher(mac).matches();
}
public static byte[] macToBytes(String mac) {
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
return fromHexString(mac.replaceAll(":", ""));
@@ -146,14 +142,6 @@ public class StringUtils {
return s.toString();
}
public static String ipToString(int ip) {
int ip1 = ip & 0xFF;
int ip2 = (ip >> 8) & 0xFF;
int ip3 = (ip >> 16) & 0xFF;
int ip4 = (ip >> 24) & 0xFF;
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
}
public static String getRandomString(int length) {
char[] c = new char[length];
for (int i = 0; i < length; i++)

View File

@@ -2,32 +2,12 @@ package org.briarproject.bramble.test;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class TestUtils {
private static final AtomicInteger nextTestDir =
@@ -58,85 +38,4 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
}
public static LocalAuthor getLocalAuthor() {
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static LocalAuthor getLocalAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
long created = System.currentTimeMillis();
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
created);
}
public static Author getAuthor() {
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static Author getAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, FORMAT_VERSION, name, publicKey);
}
public static Group getGroup(ClientId clientId) {
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
return getGroup(clientId, descriptorLength);
}
public static Group getGroup(ClientId clientId, int descriptorLength) {
GroupId groupId = new GroupId(getRandomId());
byte[] descriptor = getRandomBytes(descriptorLength);
return new Group(groupId, clientId, descriptor);
}
public static Message getMessage(GroupId groupId) {
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
}
public static Message getMessage(GroupId groupId, int rawLength) {
MessageId id = new MessageId(getRandomId());
byte[] raw = getRandomBytes(rawLength);
long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw);
}
public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size();
if (size == 0) throw new IllegalArgumentException();
List<Double> sorted = new ArrayList<>(size);
for (Number n : samples) sorted.add(n.doubleValue());
Collections.sort(sorted);
if (size % 2 == 1) return sorted.get(size / 2);
double low = sorted.get(size / 2 - 1), high = sorted.get(size / 2);
return (low + high) / 2;
}
public static double getMean(Collection<? extends Number> samples) {
if (samples.isEmpty()) throw new IllegalArgumentException();
double sum = 0;
for (Number n : samples) sum += n.doubleValue();
return sum / samples.size();
}
public static double getVariance(Collection<? extends Number> samples) {
if (samples.size() < 2) throw new IllegalArgumentException();
double mean = getMean(samples);
double sumSquareDiff = 0;
for (Number n : samples) {
double diff = n.doubleValue() - mean;
sumSquareDiff += diff * diff;
}
return sumSquareDiff / (samples.size() - 1);
}
public static double getStandardDeviation(
Collection<? extends Number> samples) {
return Math.sqrt(getVariance(samples));
}
}

View File

@@ -12,7 +12,6 @@ dependencies {
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.4.1'
apt 'com.google.dagger:dagger-compiler:2.0.2'
@@ -24,6 +23,7 @@ dependencies {
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3"
testImplementation "org.whispersystems:curve25519-java:0.4.1"
testApt 'com.google.dagger:dagger-compiler:2.0.2'
}

View File

@@ -15,8 +15,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
@@ -34,12 +32,7 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
@@ -58,14 +51,12 @@ class ClientHelperImpl implements ClientHelper {
private final MetadataParser metadataParser;
private final MetadataEncoder metadataEncoder;
private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
@Inject
ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder, CryptoComponent crypto,
AuthorFactory authorFactory) {
MetadataEncoder metadataEncoder, CryptoComponent crypto) {
this.db = db;
this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory;
@@ -73,7 +64,6 @@ class ClientHelperImpl implements ClientHelper {
this.metadataParser = metadataParser;
this.metadataEncoder = metadataEncoder;
this.crypto = crypto;
this.authorFactory = authorFactory;
}
@Override
@@ -351,11 +341,6 @@ class ClientHelperImpl implements ClientHelper {
raw.length - MESSAGE_HEADER_LENGTH);
}
@Override
public BdfList toList(Author a) {
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
}
@Override
public byte[] sign(String label, BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException {
@@ -370,16 +355,4 @@ class ClientHelperImpl implements ClientHelper {
}
}
@Override
public Author parseAndValidateAuthor(BdfList author)
throws FormatException {
checkSize(author, 3);
int formatVersion = author.getLong(0).intValue();
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = author.getString(1);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = author.getRaw(2);
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
return authorFactory.createAuthor(formatVersion, name, publicKey);
}
}

View File

@@ -2,6 +2,14 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.MessageFactory;
import dagger.Module;
import dagger.Provides;
@@ -10,14 +18,19 @@ import dagger.Provides;
public class ClientModule {
@Provides
ClientHelper provideClientHelper(ClientHelperImpl clientHelper) {
return clientHelper;
ClientHelper provideClientHelper(DatabaseComponent db,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
}
@Provides
ContactGroupFactory provideContactGroupFactory(
ContactGroupFactoryImpl contactGroupFactory) {
return contactGroupFactory;
ContactGroupFactory provideContactGroupFactory(GroupFactory groupFactory,
ClientHelper clientHelper) {
return new ContactGroupFactoryImpl(groupFactory, clientHelper);
}
}

View File

@@ -43,7 +43,6 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
@@ -228,7 +227,6 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Write the name, public key and signature
w.writeListStart();
w.writeLong(localAuthor.getFormatVersion());
w.writeString(localAuthor.getName());
w.writeRaw(localAuthor.getPublicKey());
w.writeRaw(sig);
@@ -238,16 +236,11 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private Author receivePseudonym(BdfReader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the format version, name, public key and signature
// Read the name, public key and signature
r.readListStart();
int formatVersion = (int) r.readLong();
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
if (name.isEmpty()) throw new FormatException();
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
if (publicKey.length == 0) throw new FormatException();
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
if (sig.length == 0) throw new FormatException();
r.readListEnd();
LOG.info("Received pseudonym");
// Verify the signature
@@ -256,7 +249,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
LOG.info("Invalid signature");
throw new GeneralSecurityException();
}
return authorFactory.createAuthor(formatVersion, name, publicKey);
return authorFactory.createAuthor(name, publicKey);
}
private void sendTimestamp(BdfWriter w, long timestamp)

View File

@@ -0,0 +1,547 @@
package org.briarproject.bramble.crypto;
/*
The BLAKE2 cryptographic hash function was designed by Jean-
Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn, and Christian
Winnerlein.
Reference Implementation and Description can be found at: https://blake2.net/
RFC: https://tools.ietf.org/html/rfc7693
This implementation does not support the Tree Hashing Mode.
For unkeyed hashing, developers adapting BLAKE2 to ASN.1 - based
message formats SHOULD use the OID tree at x = 1.3.6.1.4.1.1722.12.2.
Algorithm | Target | Collision | Hash | Hash ASN.1 |
Identifier | Arch | Security | nn | OID Suffix |
---------------+--------+-----------+------+------------+
id-blake2s128 | 32-bit | 2**64 | 16 | x.2.4 |
id-blake2s160 | 32-bit | 2**80 | 20 | x.2.5 |
id-blake2s224 | 32-bit | 2**112 | 28 | x.2.7 |
id-blake2s256 | 32-bit | 2**128 | 32 | x.2.8 |
---------------+--------+-----------+------+------------+
Based on the BouncyCastle implementation of BLAKE2b. License:
Copyright (c) 2000 - 2015 The Legion of the Bouncy Castle Inc.
(http://www.bouncycastle.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import org.spongycastle.crypto.ExtendedDigest;
import org.spongycastle.util.Arrays;
/**
* Implementation of the cryptographic hash function BLAKE2s.
* <p/>
* BLAKE2s offers a built-in keying mechanism to be used directly
* for authentication ("Prefix-MAC") rather than a HMAC construction.
* <p/>
* BLAKE2s offers a built-in support for a salt for randomized hashing
* and a personal string for defining a unique hash function for each application.
* <p/>
* BLAKE2s is optimized for 32-bit platforms and produces digests of any size
* between 1 and 32 bytes.
*/
public class Blake2sDigest implements ExtendedDigest {
/** BLAKE2s Initialization Vector **/
private static final int blake2s_IV[] =
// Produced from the square root of primes 2, 3, 5, 7, 11, 13, 17, 19.
// The same as SHA-256 IV.
{
0x6a09e667, 0xbb67ae85, 0x3c6ef372,
0xa54ff53a, 0x510e527f, 0x9b05688c,
0x1f83d9ab, 0x5be0cd19
};
/** Message word permutations **/
private static final byte[][] blake2s_sigma =
{
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }
};
private static final int ROUNDS = 10; // to use for Catenas H'
private static final int BLOCK_LENGTH_BYTES = 64;// bytes
// General parameters:
private int digestLength = 32; // 1- 32 bytes
private int keyLength = 0; // 0 - 32 bytes for keyed hashing for MAC
private byte[] salt = null;
private byte[] personalization = null;
private byte[] key = null;
// Tree hashing parameters:
// Because this class does not implement the Tree Hashing Mode,
// these parameters can be treated as constants (see init() function)
/*
* private int fanout = 1; // 0-255
* private int depth = 1; // 1 - 255
* private int leafLength= 0;
* private long nodeOffset = 0L;
* private int nodeDepth = 0;
* private int innerHashLength = 0;
*/
/**
* Whenever this buffer overflows, it will be processed in the compress()
* function. For performance issues, long messages will not use this buffer.
*/
private byte[] buffer = null;
/** Position of last inserted byte **/
private int bufferPos = 0;// a value from 0 up to BLOCK_LENGTH_BYTES
/** Internal state, in the BLAKE2 paper it is called v **/
private int[] internalState = new int[16];
/** State vector, in the BLAKE2 paper it is called h **/
private int[] chainValue = null;
// counter (counts bytes): Length up to 2^64 are supported
/** holds least significant bits of counter **/
private int t0 = 0;
/** holds most significant bits of counter **/
private int t1 = 0;
/** finalization flag, for last block: ~0 **/
private int f0 = 0;
// For Tree Hashing Mode, not used here:
// private long f1 = 0L; // finalization flag, for last node: ~0L
/**
* BLAKE2s-256 for hashing.
*/
public Blake2sDigest() {
this(256);
}
public Blake2sDigest(Blake2sDigest digest) {
this.bufferPos = digest.bufferPos;
this.buffer = Arrays.clone(digest.buffer);
this.keyLength = digest.keyLength;
this.key = Arrays.clone(digest.key);
this.digestLength = digest.digestLength;
this.chainValue = Arrays.clone(digest.chainValue);
this.personalization = Arrays.clone(digest.personalization);
}
/**
* BLAKE2s for hashing.
*
* @param digestBits the desired digest length in bits. Must be one of
* [128, 160, 224, 256].
*/
public Blake2sDigest(int digestBits) {
if (digestBits != 128 && digestBits != 160 &&
digestBits != 224 && digestBits != 256) {
throw new IllegalArgumentException(
"BLAKE2s digest restricted to one of [128, 160, 224, 256]");
}
buffer = new byte[BLOCK_LENGTH_BYTES];
keyLength = 0;
digestLength = digestBits / 8;
init();
}
/**
* BLAKE2s for authentication ("Prefix-MAC mode").
* <p/>
* After calling the doFinal() method, the key will remain to be used for
* further computations of this instance. The key can be overwritten using
* the clearKey() method.
*
* @param key a key up to 32 bytes or null
*/
public Blake2sDigest(byte[] key) {
buffer = new byte[BLOCK_LENGTH_BYTES];
if (key != null) {
if (key.length > 32) {
throw new IllegalArgumentException(
"Keys > 32 are not supported");
}
this.key = new byte[key.length];
System.arraycopy(key, 0, this.key, 0, key.length);
keyLength = key.length;
System.arraycopy(key, 0, buffer, 0, key.length);
bufferPos = BLOCK_LENGTH_BYTES; // zero padding
}
digestLength = 32;
init();
}
/**
* BLAKE2s with key, required digest length, salt and personalization.
* <p/>
* After calling the doFinal() method, the key, the salt and the personal
* string will remain and might be used for further computations with this
* instance. The key can be overwritten using the clearKey() method, the
* salt (pepper) can be overwritten using the clearSalt() method.
*
* @param key a key up to 32 bytes or null
* @param digestBytes from 1 up to 32 bytes
* @param salt 8 bytes or null
* @param personalization 8 bytes or null
*/
public Blake2sDigest(byte[] key, int digestBytes, byte[] salt,
byte[] personalization) {
buffer = new byte[BLOCK_LENGTH_BYTES];
if (digestBytes < 1 || digestBytes > 32) {
throw new IllegalArgumentException(
"Invalid digest length (required: 1 - 32)");
}
digestLength = digestBytes;
if (salt != null) {
if (salt.length != 8) {
throw new IllegalArgumentException(
"Salt length must be exactly 8 bytes");
}
this.salt = new byte[8];
System.arraycopy(salt, 0, this.salt, 0, salt.length);
}
if (personalization != null) {
if (personalization.length != 8) {
throw new IllegalArgumentException(
"Personalization length must be exactly 8 bytes");
}
this.personalization = new byte[8];
System.arraycopy(personalization, 0, this.personalization, 0,
personalization.length);
}
if (key != null) {
if (key.length > 32) {
throw new IllegalArgumentException(
"Keys > 32 bytes are not supported");
}
this.key = new byte[key.length];
System.arraycopy(key, 0, this.key, 0, key.length);
keyLength = key.length;
System.arraycopy(key, 0, buffer, 0, key.length);
bufferPos = BLOCK_LENGTH_BYTES; // zero padding
}
init();
}
// initialize chainValue
private void init() {
if (chainValue == null) {
chainValue = new int[8];
chainValue[0] = blake2s_IV[0]
^ (digestLength | (keyLength << 8) | 0x1010000);
// 0x1010000 = ((fanout << 16) | (depth << 24));
// with fanout = 1; depth = 0;
chainValue[1] = blake2s_IV[1];// ^ leafLength; with leafLength = 0;
chainValue[2] = blake2s_IV[2];// ^ nodeOffset; with nodeOffset = 0;
chainValue[3] = blake2s_IV[3];// ^ ( (nodeOffset << 32) |
// (nodeDepth << 16) | (innerHashLength << 24) );
// with nodeDepth = 0; innerHashLength = 0;
chainValue[4] = blake2s_IV[4];
chainValue[5] = blake2s_IV[5];
if (salt != null) {
chainValue[4] ^= (bytes2int(salt, 0));
chainValue[5] ^= (bytes2int(salt, 4));
}
chainValue[6] = blake2s_IV[6];
chainValue[7] = blake2s_IV[7];
if (personalization != null) {
chainValue[6] ^= (bytes2int(personalization, 0));
chainValue[7] ^= (bytes2int(personalization, 4));
}
}
}
private void initializeInternalState() {
// initialize v:
System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
System.arraycopy(blake2s_IV, 0, internalState, chainValue.length, 4);
internalState[12] = t0 ^ blake2s_IV[4];
internalState[13] = t1 ^ blake2s_IV[5];
internalState[14] = f0 ^ blake2s_IV[6];
internalState[15] = blake2s_IV[7];// ^ f1 with f1 = 0
}
/**
* Update the message digest with a single byte.
*
* @param b the input byte to be entered.
*/
public void update(byte b) {
int remainingLength; // left bytes of buffer
// process the buffer if full else add to buffer:
remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
if (remainingLength == 0) { // full buffer
t0 += BLOCK_LENGTH_BYTES;
if (t0 == 0) { // if message > 2^32
t1++;
}
compress(buffer, 0);
Arrays.fill(buffer, (byte)0);// clear buffer
buffer[0] = b;
bufferPos = 1;
} else {
buffer[bufferPos] = b;
bufferPos++;
}
}
/**
* Update the message digest with a block of bytes.
*
* @param message the byte array containing the data.
* @param offset the offset into the byte array where the data starts.
* @param len the length of the data.
*/
public void update(byte[] message, int offset, int len) {
if (message == null || len == 0)
return;
int remainingLength = 0; // left bytes of buffer
if (bufferPos != 0) { // commenced, incomplete buffer
// complete the buffer:
remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
if (remainingLength < len) { // full buffer + at least 1 byte
System.arraycopy(message, offset, buffer, bufferPos,
remainingLength);
t0 += BLOCK_LENGTH_BYTES;
if (t0 == 0) { // if message > 2^32
t1++;
}
compress(buffer, 0);
bufferPos = 0;
Arrays.fill(buffer, (byte) 0);// clear buffer
} else {
System.arraycopy(message, offset, buffer, bufferPos, len);
bufferPos += len;
return;
}
}
// process blocks except last block (also if last block is full)
int messagePos;
int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES;
for (messagePos = offset + remainingLength;
messagePos < blockWiseLastPos;
messagePos += BLOCK_LENGTH_BYTES) { // block wise 64 bytes
// without buffer:
t0 += BLOCK_LENGTH_BYTES;
if (t0 == 0) {
t1++;
}
compress(message, messagePos);
}
// fill the buffer with left bytes, this might be a full block
System.arraycopy(message, messagePos, buffer, 0, offset + len
- messagePos);
bufferPos += offset + len - messagePos;
}
/**
* Close the digest, producing the final digest value. The doFinal() call
* leaves the digest reset. Key, salt and personal string remain.
*
* @param out the array the digest is to be copied into.
* @param outOffset the offset into the out array the digest is to start at.
*/
public int doFinal(byte[] out, int outOffset) {
f0 = 0xFFFFFFFF;
t0 += bufferPos;
// bufferPos may be < 64, so (t0 == 0) does not work
// for 2^32 < message length > 2^32 - 63
if ((t0 < 0) && (bufferPos > -t0)) {
t1++;
}
compress(buffer, 0);
Arrays.fill(buffer, (byte) 0);// Holds eventually the key if input is null
Arrays.fill(internalState, 0);
for (int i = 0; i < chainValue.length && (i * 4 < digestLength); i++) {
byte[] bytes = int2bytes(chainValue[i]);
if (i * 4 < digestLength - 4) {
System.arraycopy(bytes, 0, out, outOffset + i * 4, 4);
} else {
System.arraycopy(bytes, 0, out, outOffset + i * 4,
digestLength - (i * 4));
}
}
Arrays.fill(chainValue, 0);
reset();
return digestLength;
}
/**
* Reset the digest back to its initial state. The key, the salt and the
* personal string will remain for further computations.
*/
public void reset() {
bufferPos = 0;
f0 = 0;
t0 = 0;
t1 = 0;
chainValue = null;
if (key != null) {
Arrays.fill(buffer, (byte) 0);
System.arraycopy(key, 0, buffer, 0, key.length);
bufferPos = BLOCK_LENGTH_BYTES; // zero padding
}
init();
}
private void compress(byte[] message, int messagePos) {
initializeInternalState();
int[] m = new int[16];
for (int j = 0; j < 16; j++) {
m[j] = bytes2int(message, messagePos + j * 4);
}
for (int round = 0; round < ROUNDS; round++) {
// G apply to columns of internalState:m[blake2s_sigma[round][2 *
// blockPos]] /+1
G(m[blake2s_sigma[round][0]], m[blake2s_sigma[round][1]], 0, 4, 8,
12);
G(m[blake2s_sigma[round][2]], m[blake2s_sigma[round][3]], 1, 5, 9,
13);
G(m[blake2s_sigma[round][4]], m[blake2s_sigma[round][5]], 2, 6, 10,
14);
G(m[blake2s_sigma[round][6]], m[blake2s_sigma[round][7]], 3, 7, 11,
15);
// G apply to diagonals of internalState:
G(m[blake2s_sigma[round][8]], m[blake2s_sigma[round][9]], 0, 5, 10,
15);
G(m[blake2s_sigma[round][10]], m[blake2s_sigma[round][11]], 1, 6,
11, 12);
G(m[blake2s_sigma[round][12]], m[blake2s_sigma[round][13]], 2, 7,
8, 13);
G(m[blake2s_sigma[round][14]], m[blake2s_sigma[round][15]], 3, 4,
9, 14);
}
// update chain values:
for (int offset = 0; offset < chainValue.length; offset++) {
chainValue[offset] = chainValue[offset] ^ internalState[offset]
^ internalState[offset + 8];
}
}
private void G(int m1, int m2, int posA, int posB, int posC, int posD) {
internalState[posA] = internalState[posA] + internalState[posB] + m1;
internalState[posD] = rotr32(internalState[posD] ^ internalState[posA],
16);
internalState[posC] = internalState[posC] + internalState[posD];
internalState[posB] = rotr32(internalState[posB] ^ internalState[posC],
12);
internalState[posA] = internalState[posA] + internalState[posB] + m2;
internalState[posD] = rotr32(internalState[posD] ^ internalState[posA],
8);
internalState[posC] = internalState[posC] + internalState[posD];
internalState[posB] = rotr32(internalState[posB] ^ internalState[posC],
7);
}
private int rotr32(int x, int rot) {
return x >>> rot | (x << (32 - rot));
}
// convert one int value in byte array
// little-endian byte order!
private byte[] int2bytes(int intValue) {
return new byte[] {
(byte) intValue, (byte) (intValue >> 8),
(byte) (intValue >> 16), (byte) (intValue >> 24)
};
}
// little-endian byte order!
private int bytes2int(byte[] byteArray, int offset) {
return (((int) byteArray[offset] & 0xFF)
| (((int) byteArray[offset + 1] & 0xFF) << 8)
| (((int) byteArray[offset + 2] & 0xFF) << 16)
| (((int) byteArray[offset + 3] & 0xFF) << 24));
}
/**
* Return the algorithm name.
*
* @return the algorithm name
*/
public String getAlgorithmName() {
return "BLAKE2s";
}
/**
* Return the size in bytes of the digest produced by this message digest.
*
* @return the size in bytes of the digest produced by this message digest.
*/
public int getDigestSize() {
return digestLength;
}
/**
* Return the size in bytes of the internal buffer the digest applies its
* compression function to.
*
* @return byte length of the digest's internal buffer.
*/
public int getByteLength() {
return BLOCK_LENGTH_BYTES;
}
/**
* Overwrite the key if it is no longer used (zeroization).
*/
public void clearKey() {
if (key != null) {
Arrays.fill(key, (byte) 0);
Arrays.fill(buffer, (byte) 0);
}
}
/**
* Overwrite the salt (pepper) if it is secret and no longer used
* (zeroization).
*/
public void clearSalt() {
if (salt != null) {
Arrays.fill(salt, (byte) 0);
}
}
}

View File

@@ -10,50 +10,61 @@ import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.CryptoException;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter;
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;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@NotNullByDefault
class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName());
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int ED_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
private static final int PBKDF_FORMAT_SCRYPT = 0;
private static final int PBKDF_TARGET_MILLIS = 500;
private static final int PBKDF_SAMPLES = 30;
private final SecureRandom secureRandom;
private final PasswordBasedKdf passwordBasedKdf;
private final Curve25519 curve25519;
private final KeyPairGenerator signatureKeyPairGenerator;
private final ECKeyPairGenerator agreementKeyPairGenerator;
private final ECKeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser;
private final MessageEncrypter messageEncrypter;
private final KeyPairGenerator edKeyPairGenerator;
private final KeyParser edKeyParser;
@Inject
CryptoComponentImpl(SecureRandomProvider secureRandomProvider,
PasswordBasedKdf passwordBasedKdf) {
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
if (LOG.isLoggable(INFO)) {
SecureRandom defaultSecureRandom = new SecureRandom();
String name = defaultSecureRandom.getProvider().getName();
@@ -73,14 +84,20 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
secureRandom = new SecureRandom();
this.passwordBasedKdf = passwordBasedKdf;
curve25519 = Curve25519.getInstance("java");
signatureKeyPairGenerator = new KeyPairGenerator();
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
secureRandom);
agreementKeyParser = new Curve25519KeyParser();
signatureKeyParser = new EdKeyParser();
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
agreementKeyPairGenerator.init(params);
signatureKeyPairGenerator = new ECKeyPairGenerator();
signatureKeyPairGenerator.init(params);
agreementKeyParser = new Sec1KeyParser(PARAMETERS,
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
SIGNATURE_KEY_PAIR_BITS);
messageEncrypter = new MessageEncrypter(secureRandom);
edKeyPairGenerator = new KeyPairGenerator();
edKeyPairGenerator.initialize(ED_KEY_PAIR_BITS, secureRandom);
edKeyParser = new EdKeyParser();
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
@@ -123,29 +140,51 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException {
if (!(priv instanceof Curve25519PrivateKey))
if (!(priv instanceof Sec1PrivateKey))
throw new IllegalArgumentException();
if (!(pub instanceof Curve25519PublicKey))
if (!(pub instanceof Sec1PublicKey))
throw new IllegalArgumentException();
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
long now = System.currentTimeMillis();
byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
priv.getEncoded());
// If the shared secret is all zeroes, the public key is invalid
byte allZero = 0;
for (byte b : secret) allZero |= b;
if (allZero == 0) throw new GeneralSecurityException();
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(ecPriv);
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray();
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Deriving shared secret took " + duration + " ms");
return secret;
}
@Override
public KeyPair generateEdKeyPair() {
java.security.KeyPair keyPair = edKeyPairGenerator.generateKeyPair();
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
return new KeyPair(publicKey, privateKey);
}
@Override
public KeyParser getEdKeyParser() {
return edKeyParser;
}
@Override
public KeyPair generateAgreementKeyPair() {
Curve25519KeyPair keyPair = curve25519.generateKeyPair();
PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
return new KeyPair(pub, priv);
AsymmetricCipherKeyPair keyPair =
agreementKeyPairGenerator.generateKeyPair();
// Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
@Override
@@ -155,12 +194,17 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public KeyPair generateSignatureKeyPair() {
java.security.KeyPair keyPair =
AsymmetricCipherKeyPair keyPair =
signatureKeyPairGenerator.generateKeyPair();
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
// Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
@@ -197,8 +241,19 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
Signature sig = new EdSignature();
return sign(new SignatureImpl(secureRandom), signatureKeyParser, label,
toSign, privateKey);
}
@Override
public byte[] signEd(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
return sign(new EdSignature(), edKeyParser, label, toSign, privateKey);
}
private byte[] sign(Signature sig, KeyParser keyParser, String label,
byte[] toSign, byte[] privateKey) throws GeneralSecurityException {
PrivateKey key = keyParser.parsePrivateKey(privateKey);
sig.initSign(key);
updateSignature(sig, label, toSign);
return sig.sign();
@@ -207,8 +262,21 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
Signature sig = new EdSignature();
return verify(new SignatureImpl(secureRandom), signatureKeyParser,
label, signedData, publicKey, signature);
}
@Override
public boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
return verify(new EdSignature(), edKeyParser, label, signedData,
publicKey, signature);
}
private boolean verify(Signature sig, KeyParser keyParser, String label,
byte[] signedData, byte[] publicKey, byte[] signature)
throws GeneralSecurityException {
PublicKey key = keyParser.parsePublicKey(publicKey);
sig.initVerify(key);
updateSignature(sig, label, signedData);
return sig.verify(signature);
@@ -229,7 +297,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public byte[] hash(String label, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
digest.update(length, 0, length.length);
@@ -247,7 +315,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest mac = new Blake2bDigest(macKey.getBytes(), 32, null, null);
Digest mac = new Blake2sDigest(macKey.getBytes());
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
mac.update(length, 0, length.length);
@@ -270,33 +338,23 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] salt = new byte[PBKDF_SALT_BYTES];
secureRandom.nextBytes(salt);
// Calibrate the KDF
int cost = passwordBasedKdf.chooseCostParameter();
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
// Derive the key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
// Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv);
// The output contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
int outputLen = 1 + salt.length + INT_32_BYTES + iv.length
+ input.length + macBytes;
// The output contains the salt, iterations, IV, ciphertext and MAC
int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
+ macBytes;
byte[] output = new byte[outputLen];
int outputOff = 0;
// Format version
output[outputOff] = PBKDF_FORMAT_SCRYPT;
outputOff++;
// Salt
System.arraycopy(salt, 0, output, outputOff, salt.length);
outputOff += salt.length;
// Cost parameter
ByteUtils.writeUint32(cost, output, outputOff);
outputOff += INT_32_BYTES;
// IV
System.arraycopy(iv, 0, output, outputOff, iv.length);
outputOff += iv.length;
System.arraycopy(salt, 0, output, 0, salt.length);
ByteUtils.writeUint32(iterations, output, salt.length);
System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length);
// Initialise the cipher and encrypt the plaintext
try {
cipher.init(true, key, iv);
int outputOff = salt.length + INT_32_BYTES + iv.length;
cipher.process(input, 0, input.length, output, outputOff);
return output;
} catch (GeneralSecurityException e) {
@@ -305,36 +363,22 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password) {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes)
// The input contains the salt, iterations, IV, ciphertext and MAC
if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
+ macBytes)
return null; // Invalid input
int inputOff = 0;
// Format version
byte formatVersion = input[inputOff];
inputOff++;
if (formatVersion != PBKDF_FORMAT_SCRYPT)
return null; // Unknown format
// Salt
byte[] salt = new byte[PBKDF_SALT_BYTES];
System.arraycopy(input, inputOff, salt, 0, salt.length);
inputOff += salt.length;
// Cost parameter
long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE)
return null; // Invalid cost parameter
// IV
System.arraycopy(input, 0, salt, 0, salt.length);
long iterations = ByteUtils.readUint32(input, salt.length);
if (iterations < 0 || iterations > Integer.MAX_VALUE)
return null; // Invalid iteration count
byte[] iv = new byte[STORAGE_IV_BYTES];
System.arraycopy(input, inputOff, iv, 0, iv.length);
inputOff += iv.length;
System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
// Derive the key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
// Initialise the cipher
try {
cipher.init(false, key, iv);
@@ -343,6 +387,7 @@ class CryptoComponentImpl implements CryptoComponent {
}
// Try to decrypt the ciphertext (may be invalid)
try {
int inputOff = salt.length + INT_32_BYTES + iv.length;
int inputLen = input.length - inputOff;
byte[] output = new byte[inputLen - macBytes];
cipher.process(input, inputOff, inputLen, output, 0);
@@ -365,4 +410,64 @@ class CryptoComponentImpl implements CryptoComponent {
public String asciiArmour(byte[] b, int lineLength) {
return AsciiArmour.wrap(b, lineLength);
}
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
byte[] utf8 = StringUtils.toUtf8(password);
Digest digest = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
gen.init(utf8, salt, iterations);
int keyLengthInBits = SecretKey.LENGTH * 8;
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
return ((KeyParameter) p).getKey();
}
// Package access for testing
int chooseIterationCount(int targetMillis) {
List<Long> quickSamples = new ArrayList<>(PBKDF_SAMPLES);
List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES);
long iterationNanos = 0, initNanos = 0;
while (iterationNanos <= 0 || initNanos <= 0) {
// Sample the running time with one iteration and two iterations
for (int i = 0; i < PBKDF_SAMPLES; i++) {
quickSamples.add(sampleRunningTime(1));
slowSamples.add(sampleRunningTime(2));
}
// Calculate the iteration time and the initialisation time
long quickMedian = median(quickSamples);
long slowMedian = median(slowSamples);
iterationNanos = slowMedian - quickMedian;
initNanos = quickMedian - iterationNanos;
if (LOG.isLoggable(INFO)) {
LOG.info("Init: " + initNanos + ", iteration: "
+ iterationNanos);
}
}
long targetNanos = targetMillis * 1000L * 1000L;
long iterations = (targetNanos - initNanos) / iterationNanos;
if (LOG.isLoggable(INFO)) LOG.info("Target iterations: " + iterations);
if (iterations < 1) return 1;
if (iterations > Integer.MAX_VALUE) return Integer.MAX_VALUE;
return (int) iterations;
}
private long sampleRunningTime(int iterations) {
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();
Digest digest = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
gen.init(password, salt, iterations);
gen.generateDerivedParameters(keyLengthInBits);
return System.nanoTime() - start;
}
private long median(List<Long> list) {
int size = list.size();
if (size == 0) throw new IllegalArgumentException();
Collections.sort(list);
if (size % 2 == 1) return list.get(size / 2);
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
}
}

View File

@@ -67,9 +67,8 @@ public class CryptoModule {
@Provides
@Singleton
CryptoComponent provideCryptoComponent(
SecureRandomProvider secureRandomProvider,
ScryptKdf passwordBasedKdf) {
return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
SecureRandomProvider secureRandomProvider) {
return new CryptoComponentImpl(secureRandomProvider);
}
@Provides

View File

@@ -1,35 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@NotNullByDefault
class Curve25519KeyParser implements KeyParser {
@Override
public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PublicKey(encodedKey);
}
@Override
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PrivateKey(clamp(encodedKey));
}
static byte[] clamp(byte[] b) {
byte[] clamped = new byte[32];
System.arraycopy(b, 0, clamped, 0, 32);
clamped[0] &= 248;
clamped[31] &= 127;
clamped[31] |= 64;
return clamped;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class Curve25519PrivateKey extends Bytes implements PrivateKey {
Curve25519PrivateKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class Curve25519PublicKey extends Bytes implements PublicKey {
Curve25519PublicKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.crypto;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECMultiplier;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
/**
* Parameters for curve brainpoolp256r1 - see RFC 5639.
*/
class EllipticCurveConstants {
static final ECDomainParameters PARAMETERS;
static {
// Start with the default implementation of the curve
X9ECParameters x9 = TeleTrusTNamedCurves.getByName("brainpoolp256r1");
// Use a constant-time multiplier
ECMultiplier monty = new MontgomeryLadderMultiplier();
ECCurve curve = x9.getCurve().configure().setMultiplier(monty).create();
BigInteger gX = x9.getG().getAffineXCoord().toBigInteger();
BigInteger gY = x9.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(gX, gY);
// Convert to ECDomainParameters using the new multiplier
PARAMETERS = new ECDomainParameters(curve, g, x9.getN(), x9.getH());
}
}

View File

@@ -1,10 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
interface PasswordBasedKdf {
int chooseCostParameter();
SecretKey deriveKey(String password, byte[] salt, int cost);
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.generators.SCrypt;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
class ScryptKdf implements PasswordBasedKdf {
private static final Logger LOG =
Logger.getLogger(ScryptKdf.class.getName());
private static final int MIN_COST = 256; // Min parameter N
private static final int MAX_COST = 1024 * 1024; // Max parameter N
private static final int BLOCK_SIZE = 8; // Parameter r
private static final int PARALLELIZATION = 1; // Parameter p
private static final int TARGET_MS = 1000;
private final Clock clock;
@Inject
ScryptKdf(Clock clock) {
this.clock = clock;
}
@Override
public int chooseCostParameter() {
// Increase the cost from min to max while measuring performance
int cost = MIN_COST;
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
cost *= 2;
if (LOG.isLoggable(INFO))
LOG.info("KDF cost parameter " + cost);
return cost;
}
private long measureDuration(int cost) {
byte[] password = new byte[16], salt = new byte[32];
long start = clock.currentTimeMillis();
SCrypt.generate(password, salt, cost, BLOCK_SIZE, PARALLELIZATION,
SecretKey.LENGTH);
return clock.currentTimeMillis() - start;
}
@Override
public SecretKey deriveKey(String password, byte[] salt, int cost) {
long start = System.currentTimeMillis();
byte[] passwordBytes = StringUtils.toUtf8(password);
SecretKey k = new SecretKey(SCrypt.generate(passwordBytes, salt, cost,
BLOCK_SIZE, PARALLELIZATION, SecretKey.LENGTH));
long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO))
LOG.info("Deriving key from password took " + duration + " ms");
return k;
}
}

View File

@@ -0,0 +1,90 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.crypto.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.DSAKCalculator;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.logging.Logger;
import javax.annotation.concurrent.NotThreadSafe;
import static java.util.logging.Level.INFO;
@NotThreadSafe
@NotNullByDefault
class SignatureImpl implements Signature {
private static final Logger LOG =
Logger.getLogger(SignatureImpl.class.getName());
private final SecureRandom secureRandom;
private final DSADigestSigner signer;
SignatureImpl(SecureRandom secureRandom) {
this.secureRandom = secureRandom;
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
signer = new DSADigestSigner(new ECDSASigner(calculator), digest);
}
@Override
public void initSign(PrivateKey k) throws GeneralSecurityException {
if (!(k instanceof Sec1PrivateKey))
throw new IllegalArgumentException();
ECPrivateKeyParameters priv = ((Sec1PrivateKey) k).getKey();
signer.init(true, new ParametersWithRandom(priv, secureRandom));
}
@Override
public void initVerify(PublicKey k) throws GeneralSecurityException {
if (!(k instanceof Sec1PublicKey))
throw new IllegalArgumentException();
ECPublicKeyParameters pub = ((Sec1PublicKey) k).getKey();
signer.init(false, pub);
}
@Override
public void update(byte b) {
signer.update(b);
}
@Override
public void update(byte[] b) {
update(b, 0, b.length);
}
@Override
public void update(byte[] b, int off, int len) {
signer.update(b, off, len);
}
@Override
public byte[] sign() {
long now = System.currentTimeMillis();
byte[] signature = signer.generateSignature();
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Generating signature took " + duration + " ms");
return signature;
}
@Override
public boolean verify(byte[] signature) {
long now = System.currentTimeMillis();
boolean valid = signer.verifySignature(signature);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Verifying signature took " + duration + " ms");
return valid;
}
}

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import javax.inject.Inject;
@@ -116,7 +115,7 @@ class TransportCryptoImpl implements TransportCrypto {
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Initialise the PRF
Digest prf = new Blake2bDigest(tagKey.getBytes(), 32, null, null);
Digest prf = new Blake2sDigest(tagKey.getBytes());
// The output of the PRF must be long enough to use as a tag
int macLength = prf.getDigestSize();
if (macLength < TAG_LENGTH) throw new IllegalStateException();

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -40,13 +37,8 @@ interface Database<T> {
/**
* Opens the database and returns true if the database already existed.
*
* @throws DataTooNewException if the data uses a newer schema than the
* current code
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/
boolean open(@Nullable MigrationListener listener) throws DbException;
boolean open() throws DbException;
/**
* Prevents new transactions from starting, waits for all current
@@ -97,12 +89,9 @@ interface Database<T> {
/**
* Stores a message.
*
* @param sender the contact from whom the message was received, or null
* if the message was created locally.
*/
void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException;
void addMessage(T txn, Message m, State state, boolean shared)
throws DbException;
/**
* Adds a dependency between two messages in the given group.
@@ -115,6 +104,16 @@ interface Database<T> {
*/
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
/**
* Initialises the status of the given message with respect to the given
* contact.
*
* @param ack whether the message needs to be acknowledged.
* @param seen whether the contact has seen the message.
*/
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
throws DbException;
/**
* Stores a transport.
*/
@@ -273,7 +272,7 @@ interface Database<T> {
* <p/>
* Read-only.
*/
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
throws DbException;
/**
@@ -424,37 +423,31 @@ interface Database<T> {
throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* Returns the IDs of any messages that need to be validated by the given
* client.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that are pending delivery due to
* dependencies on other messages.
* Returns the IDs of any messages that are still pending due to
* dependencies to other messages for the given client.
* <p/>
* Read-only.
*/
Collection<MessageId> getPendingMessages(T txn) throws DbException;
Collection<MessageId> getPendingMessages(T txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that have a shared dependent but have
* not yet been shared themselves.
* Returns the IDs of any messages from the given client
* that have a shared dependent, but are still not shared themselves.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
* if no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(T txn, ContactId c) throws DbException;
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
throws DbException;
/**
* Returns the message with the given ID, in serialised form, or null if
@@ -573,6 +566,13 @@ interface Database<T> {
*/
void removeMessage(T txn, MessageId m) throws DbException;
/**
* Removes an offered message that was offered by the given contact, or
* returns false if there is no such message.
*/
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
throws DbException;
/**
* Removes the given offered messages that were offered by the given
* contact.
@@ -580,6 +580,12 @@ interface Database<T> {
void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException;
/**
* Removes the status of the given message with respect to the given
* contact.
*/
void removeStatus(T txn, ContactId c, MessageId m) throws DbException;
/**
* Removes a transport (and all associated state) from the database.
*/

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -101,9 +100,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public boolean open(@Nullable MigrationListener listener)
throws DbException {
boolean reopened = db.open(listener);
public boolean open() throws DbException {
boolean reopened = db.open();
shutdown.addShutdownHook(() -> {
try {
close();
@@ -215,7 +213,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) {
db.addMessage(txn, m, DELIVERED, shared, null);
addMessage(txn, m, DELIVERED, shared, null);
transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED));
@@ -224,6 +222,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.mergeMessageMetadata(txn, m.getId(), meta);
}
private void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException {
db.addMessage(txn, m, state, shared);
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || (sender != null && c.equals(sender));
db.addStatus(txn, c, m.getId(), seen, seen);
}
}
@Override
public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException {
@@ -455,24 +463,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException {
public Collection<MessageId> getMessagesToValidate(Transaction transaction,
ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToValidate(txn);
return db.getMessagesToValidate(txn, c);
}
@Override
public Collection<MessageId> getPendingMessages(Transaction transaction)
throws DbException {
public Collection<MessageId> getPendingMessages(Transaction transaction,
ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getPendingMessages(txn);
return db.getPendingMessages(txn, c);
}
@Override
public Collection<MessageId> getMessagesToShare(Transaction transaction)
throws DbException {
public Collection<MessageId> getMessagesToShare(
Transaction transaction, ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToShare(txn);
return db.getMessagesToShare(txn, c);
}
@Nullable
@@ -571,13 +579,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageDependents(txn, m);
}
@Override
public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
return db.getNextSendTime(txn, c);
}
@Override
public Settings getSettings(Transaction transaction, String namespace)
throws DbException {
@@ -672,7 +673,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId());
} else {
db.addMessage(txn, m, UNKNOWN, false, c);
addMessage(txn, m, UNKNOWN, false, c);
transaction.attach(new MessageAddedEvent(m, c));
}
transaction.attach(new MessageToAckEvent(c));
@@ -740,8 +741,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
GroupId id = g.getId();
if (!db.containsGroup(txn, id))
throw new NoSuchGroupException();
Collection<ContactId> affected =
db.getGroupVisibility(txn, id).keySet();
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
@@ -811,9 +811,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchGroupException();
Visibility old = db.getGroupVisibility(txn, c, g);
if (old == v) return;
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED);
if (old == INVISIBLE) {
db.addGroupVisibility(txn, c, g, v == SHARED);
for (MessageId m : db.getMessageIds(txn, g)) {
boolean seen = db.removeOfferedMessage(txn, c, m);
db.addStatus(txn, c, m, seen, seen);
}
} else if (v == INVISIBLE) {
db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g))
db.removeStatus(txn, c, m);
} else {
db.setGroupVisibility(txn, c, g, v == SHARED);
}
List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}

View File

@@ -23,4 +23,10 @@ interface DatabaseConstants {
*/
String SCHEMA_VERSION_KEY = "schemaVersion";
/**
* The {@link Settings} key under which the minimum supported database
* schema version is stored.
*/
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import javax.annotation.Nullable;
import javax.inject.Inject;
/**
@@ -44,11 +42,10 @@ class H2Database extends JdbcDatabase {
}
@Override
public boolean open(@Nullable MigrationListener listener)
throws DbException {
public boolean open() throws DbException {
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen, listener);
super.open("org.h2.Driver", reopen);
return reopen;
}
@@ -95,10 +92,6 @@ class H2Database extends JdbcDatabase {
// Separate the file password from the user password with a space
String hex = StringUtils.toHexString(key.getBytes());
props.put("password", hex + " password");
return DriverManager.getConnection(getUrl(), props);
}
String getUrl() {
return url;
return DriverManager.getConnection(url, props);
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import javax.annotation.Nullable;
import javax.inject.Inject;
/**
@@ -46,10 +44,10 @@ class HyperSqlDatabase extends JdbcDatabase {
}
@Override
public boolean open(@Nullable MigrationListener listener) throws DbException {
public boolean open() throws DbException {
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
super.open("org.hsqldb.jdbc.JDBCDriver", reopen);
return reopen;
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
interface Migration<T> {
/**
* Returns the schema version from which this migration starts.
*/
int getStartVersion();
/**
* Returns the schema version at which this migration ends.
*/
int getEndVersion();
void migrate(T txn) throws DbException;
}

View File

@@ -1,65 +1,61 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorId.LABEL;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@Immutable
@NotNullByDefault
class AuthorFactoryImpl implements AuthorFactory {
private final CryptoComponent crypto;
private final BdfWriterFactory bdfWriterFactory;
private final Clock clock;
@Inject
AuthorFactoryImpl(CryptoComponent crypto, Clock clock) {
AuthorFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory,
Clock clock) {
this.crypto = crypto;
this.bdfWriterFactory = bdfWriterFactory;
this.clock = clock;
}
@Override
public Author createAuthor(String name, byte[] publicKey) {
return createAuthor(FORMAT_VERSION, name, publicKey);
}
@Override
public Author createAuthor(int formatVersion, String name,
byte[] publicKey) {
AuthorId id = getId(formatVersion, name, publicKey);
return new Author(id, formatVersion, name, publicKey);
return new Author(getId(name, publicKey), name, publicKey);
}
@Override
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey) {
return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey);
return new LocalAuthor(getId(name, publicKey), name, publicKey,
privateKey, clock.currentTimeMillis());
}
@Override
public LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey) {
AuthorId id = getId(formatVersion, name, publicKey);
return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
clock.currentTimeMillis());
}
private AuthorId getId(int formatVersion, String name, byte[] publicKey) {
byte[] formatVersionBytes = new byte[INT_32_BYTES];
ByteUtils.writeUint32(formatVersion, formatVersionBytes, 0);
return new AuthorId(crypto.hash(LABEL, formatVersionBytes,
StringUtils.toUtf8(name), publicKey));
private AuthorId getId(String name, byte[] publicKey) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart();
w.writeString(name);
w.writeRaw(publicKey);
w.writeListEnd();
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException(e);
}
return new AuthorId(crypto.hash(AuthorId.LABEL, out.toByteArray()));
}
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.ObjectReader;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@Immutable
@NotNullByDefault
class AuthorReader implements ObjectReader<Author> {
private final AuthorFactory authorFactory;
AuthorReader(AuthorFactory authorFactory) {
this.authorFactory = authorFactory;
}
@Override
public Author readObject(BdfReader r) throws IOException {
r.readListStart();
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
if (name.length() == 0) throw new FormatException();
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
r.readListEnd();
return authorFactory.createAuthor(name, publicKey);
}
}

View File

@@ -1,7 +1,13 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.data.ObjectReader;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.system.Clock;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -18,14 +24,19 @@ public class IdentityModule {
}
@Provides
AuthorFactory provideAuthorFactory(AuthorFactoryImpl authorFactory) {
return authorFactory;
AuthorFactory provideAuthorFactory(CryptoComponent crypto,
BdfWriterFactory bdfWriterFactory, Clock clock) {
return new AuthorFactoryImpl(crypto, bdfWriterFactory, clock);
}
@Provides
@Singleton
IdentityManager provideIdentityManager(
IdentityManagerImpl identityManager) {
return identityManager;
IdentityManager provideIdentityModule(DatabaseComponent db) {
return new IdentityManagerImpl(db);
}
@Provides
ObjectReader<Author> provideAuthorReader(AuthorFactory authorFactory) {
return new AuthorReader(authorFactory);
}
}

View File

@@ -1,36 +0,0 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
interface ConnectionChooser {
/**
* Submits a connection task to the chooser.
*/
void submit(Callable<KeyAgreementConnection> task);
/**
* Returns a connection returned by any of the tasks submitted to the
* chooser, waiting up to the given amount of time for a connection if
* necessary. Returns null if the time elapses without a connection
* becoming available.
*
* @param timeout the timeout in milliseconds
* @throws InterruptedException if the thread is interrupted while waiting
* for a connection to become available
*/
@Nullable
KeyAgreementConnection poll(long timeout) throws InterruptedException;
/**
* Stops the chooser. Any connections already returned to the chooser are
* closed unless they have been removed from the chooser by calling
* {@link #poll(long)}. Any connections subsequently returned to the
* chooser will also be closed.
*/
void stop();
}

View File

@@ -1,112 +0,0 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
@NotNullByDefault
@ThreadSafe
class ConnectionChooserImpl implements ConnectionChooser {
private static final Logger LOG =
Logger.getLogger(ConnectionChooserImpl.class.getName());
private final Clock clock;
private final Executor ioExecutor;
private final Object lock = new Object();
// The following are locking: lock
private boolean stopped = false;
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
@Inject
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
this.clock = clock;
this.ioExecutor = ioExecutor;
}
@Override
public void submit(Callable<KeyAgreementConnection> task) {
ioExecutor.execute(() -> {
try {
KeyAgreementConnection c = task.call();
if (c != null) addResult(c);
} catch (Exception e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
});
}
@Nullable
@Override
public KeyAgreementConnection poll(long timeout)
throws InterruptedException {
long now = clock.currentTimeMillis();
long end = now + timeout;
synchronized (lock) {
while (!stopped && results.isEmpty() && now < end) {
lock.wait(end - now);
now = clock.currentTimeMillis();
}
return results.poll();
}
}
@Override
public void stop() {
List<KeyAgreementConnection> unused;
synchronized (lock) {
unused = new ArrayList<>(results);
results.clear();
stopped = true;
lock.notifyAll();
}
if (LOG.isLoggable(INFO))
LOG.info("Closing " + unused.size() + " unused connections");
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
}
private void addResult(KeyAgreementConnection c) {
if (LOG.isLoggable(INFO))
LOG.info("Got connection for " + c.getTransportId());
boolean close = false;
synchronized (lock) {
if (stopped) {
close = true;
} else {
results.add(c);
lock.notifyAll();
}
}
if (close) {
LOG.info("Already stopped");
tryToClose(c.getConnection());
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getReader().dispose(false, true);
conn.getWriter().dispose(false);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}

View File

@@ -13,19 +13,23 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import javax.annotation.Nullable;
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.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
@@ -41,27 +45,29 @@ class KeyAgreementConnector {
Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks;
private final Clock clock;
private final KeyAgreementCrypto keyAgreementCrypto;
private final PluginManager pluginManager;
private final ConnectionChooser connectionChooser;
private final CompletionService<KeyAgreementConnection> connect;
private final List<KeyAgreementListener> listeners =
new CopyOnWriteArrayList<>();
private final CountDownLatch aliceLatch = new CountDownLatch(1);
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
private final List<KeyAgreementListener> listeners = new ArrayList<>();
private final List<Future<KeyAgreementConnection>> pending =
new ArrayList<>();
private volatile boolean alice = false, stopped = false;
private volatile boolean connecting = false;
private volatile boolean alice = false;
KeyAgreementConnector(Callbacks callbacks,
KeyAgreementConnector(Callbacks callbacks, Clock clock,
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
ConnectionChooser connectionChooser) {
Executor ioExecutor) {
this.callbacks = callbacks;
this.clock = clock;
this.keyAgreementCrypto = keyAgreementCrypto;
this.pluginManager = pluginManager;
this.connectionChooser = connectionChooser;
connect = new ExecutorCompletionService<>(ioExecutor);
}
Payload listen(KeyPair localKeyPair) {
public Payload listen(KeyPair localKeyPair) {
LOG.info("Starting BQP listeners");
// Derive commitment
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
@@ -74,9 +80,8 @@ class KeyAgreementConnector {
if (l != null) {
TransportId id = plugin.getId();
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
pending.add(connect.submit(new ReadableTask(l.listen())));
listeners.add(l);
connectionChooser.submit(new ReadableTask(l::accept));
}
}
return new Payload(commitment, descriptors);
@@ -84,92 +89,125 @@ class KeyAgreementConnector {
void stopListening() {
LOG.info("Stopping BQP listeners");
stopped = true;
aliceLatch.countDown();
for (KeyAgreementListener l : listeners) l.close();
connectionChooser.stop();
for (KeyAgreementListener l : listeners) {
l.close();
}
listeners.clear();
}
@Nullable
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
// Let the ReadableTasks know if we are Alice
public KeyAgreementTransport connect(Payload remotePayload,
boolean alice) {
// Let the listeners know if we are Alice
this.connecting = true;
this.alice = alice;
aliceLatch.countDown();
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
// Start connecting over supported transports
if (LOG.isLoggable(INFO)) {
LOG.info("Starting outgoing BQP connections as "
+ (alice ? "Alice" : "Bob"));
}
LOG.info("Starting outgoing BQP connections");
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
Plugin p = pluginManager.getPlugin(d.getId());
if (p instanceof DuplexPlugin) {
if (LOG.isLoggable(INFO))
LOG.info("Connecting via " + d.getId());
DuplexPlugin plugin = (DuplexPlugin) p;
byte[] commitment = remotePayload.getCommitment();
BdfList descriptor = d.getDescriptor();
connectionChooser.submit(new ReadableTask(
new ConnectorTask(plugin, commitment, descriptor)));
pending.add(connect.submit(new ReadableTask(
new ConnectorTask(plugin, remotePayload.getCommitment(),
d.getDescriptor(), end))));
}
}
// Get chosen connection
KeyAgreementConnection chosen = null;
try {
KeyAgreementConnection chosen =
connectionChooser.poll(CONNECTION_TIMEOUT);
if (chosen == null) return null;
long now = clock.currentTimeMillis();
Future<KeyAgreementConnection> f =
connect.poll(end - now, MILLISECONDS);
if (f == null)
return null; // No task completed within the timeout.
chosen = f.get();
return new KeyAgreementTransport(chosen);
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for connection");
Thread.currentThread().interrupt();
return null;
} catch (IOException e) {
} catch (ExecutionException | IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} finally {
stopListening();
// Close all other connections
closePending(chosen);
}
}
private void waitingForAlice() {
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
private void closePending(@Nullable KeyAgreementConnection chosen) {
for (Future<KeyAgreementConnection> f : pending) {
try {
if (f.cancel(true)) {
LOG.info("Cancelled task");
} else if (!f.isCancelled()) {
KeyAgreementConnection c = f.get();
if (c != null && c != chosen)
tryToClose(c.getConnection(), false);
}
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
Thread.currentThread().interrupt();
return;
} catch (ExecutionException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
try {
if (LOG.isLoggable(INFO))
LOG.info("Closing connection, exception: " + exception);
conn.getReader().dispose(exception, true);
conn.getWriter().dispose(exception);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
private class ConnectorTask implements Callable<KeyAgreementConnection> {
private final byte[] commitment;
private final BdfList descriptor;
private final long end;
private final DuplexPlugin plugin;
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
BdfList descriptor) {
BdfList descriptor, long end) {
this.plugin = plugin;
this.commitment = commitment;
this.descriptor = descriptor;
this.end = end;
}
@Nullable
@Override
public KeyAgreementConnection call() throws Exception {
// Repeat attempts until we connect, get stopped, or get interrupted
while (!stopped) {
// Repeat attempts until we connect, get interrupted, or time out
while (true) {
long now = clock.currentTimeMillis();
if (now > end) throw new IOException();
DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment,
descriptor);
descriptor, end - now);
if (conn != null) {
if (LOG.isLoggable(INFO))
LOG.info(plugin.getId() + ": Outgoing connection");
LOG.info(plugin.getId().getString() +
": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId());
}
// Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000);
}
return null;
}
}
private class ReadableTask implements Callable<KeyAgreementConnection> {
private class ReadableTask
implements Callable<KeyAgreementConnection> {
private final Callable<KeyAgreementConnection> connectionTask;
@@ -177,23 +215,24 @@ class KeyAgreementConnector {
this.connectionTask = connectionTask;
}
@Nullable
@Override
public KeyAgreementConnection call() throws Exception {
KeyAgreementConnection c = connectionTask.call();
if (c == null) return null;
aliceLatch.await();
if (alice || stopped) return c;
// Bob waits here for Alice to scan his QR code, determine her
// role, and send her key
InputStream in = c.getConnection().getReader().getInputStream();
while (!stopped && in.available() == 0) {
if (LOG.isLoggable(INFO))
LOG.info(c.getTransportId() + ": Waiting for data");
waitingForAlice();
Thread.sleep(500);
boolean waitingSent = false;
while (!alice && in.available() == 0) {
if (!waitingSent && connecting && !alice) {
// Bob waits here until Alice obtains his payload.
callbacks.connectionWaiting();
waitingSent = true;
}
if (LOG.isLoggable(INFO)) {
LOG.info(c.getTransportId().getString() +
": Waiting for connection");
}
Thread.sleep(1000);
}
if (!stopped && LOG.isLoggable(INFO))
if (!alice && LOG.isLoggable(INFO))
LOG.info(c.getTransportId().getString() + ": Data available");
return c;
}

View File

@@ -27,10 +27,4 @@ public class KeyAgreementModule {
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
return new PayloadParserImpl(bdfReaderFactory);
}
@Provides
ConnectionChooser provideConnectionChooser(
ConnectionChooserImpl connectionChooser) {
return connectionChooser;
}
}

View File

@@ -99,8 +99,7 @@ class KeyAgreementProtocol {
PublicKey theirPublicKey;
if (alice) {
sendKey();
// Alice waits here for Bob to scan her QR code, determine his
// role, receive her key and respond with his key
// Alice waits here until Bob obtains her payload.
callbacks.connectionWaiting();
theirPublicKey = receiveKey();
} else {

View File

@@ -15,11 +15,14 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -28,8 +31,9 @@ import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
class KeyAgreementTaskImpl extends Thread implements
KeyAgreementTask, KeyAgreementConnector.Callbacks,
KeyAgreementProtocol.Callbacks {
private static final Logger LOG =
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
@@ -45,17 +49,17 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
private Payload remotePayload;
@Inject
KeyAgreementTaskImpl(CryptoComponent crypto,
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager,
ConnectionChooser connectionChooser) {
@IoExecutor Executor ioExecutor) {
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
pluginManager, connectionChooser);
connector = new KeyAgreementConnector(this, clock, keyAgreementCrypto,
pluginManager, ioExecutor);
}
@Override
@@ -69,8 +73,10 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
@Override
public synchronized void stopListening() {
if (localPayload != null) {
if (remotePayload == null) connector.stopListening();
else interrupt();
if (remotePayload == null)
connector.stopListening();
else
interrupt();
}
}

View File

@@ -29,10 +29,10 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override
public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(PROTOCOL_VERSION);
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart(); // Payload start
w.writeLong(PROTOCOL_VERSION);
w.writeRaw(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors())
w.writeList(d.getDescriptor());

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -40,22 +39,20 @@ class PayloadParserImpl implements PayloadParser {
@Override
public Payload parse(byte[] raw) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the protocol version
int protocolVersion = in.read();
if (protocolVersion == -1) throw new FormatException();
if (protocolVersion != PROTOCOL_VERSION)
throw new UnsupportedVersionException();
// The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in);
// The payload is a BDF list with two or more elements
BdfList payload = r.readList();
if (payload.isEmpty()) throw new FormatException();
if (payload.size() < 2) throw new FormatException();
if (!r.eof()) throw new FormatException();
// First element: the public key commitment
byte[] commitment = payload.getRaw(0);
// First element: the protocol version
long protocolVersion = payload.getLong(0);
if (protocolVersion != PROTOCOL_VERSION) throw new FormatException();
// Second element: the public key commitment
byte[] commitment = payload.getRaw(1);
if (commitment.length != COMMIT_LENGTH) throw new FormatException();
// Remaining elements: transport descriptors
List<TransportDescriptor> recognised = new ArrayList<>();
for (int i = 1; i < payload.size(); i++) {
for (int i = 2; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i);
long transportId = descriptor.getLong(0);
if (transportId == TRANSPORT_ID_BLUETOOTH) {

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.AuthorFactory;
@@ -15,7 +12,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
@@ -32,21 +29,14 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
@ThreadSafe
@NotNullByDefault
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
class LifecycleManagerImpl implements LifecycleManager {
private static final Logger LOG =
Logger.getLogger(LifecycleManagerImpl.class.getName());
@@ -64,8 +54,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private volatile LifecycleState state = STARTING;
@Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
CryptoComponent crypto, AuthorFactory authorFactory,
@@ -131,7 +119,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Starting services");
long start = System.currentTimeMillis();
boolean reopened = db.open(this);
boolean reopened = db.open();
long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) {
if (reopened)
@@ -143,10 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
registerLocalAuthor(createLocalAuthor(nickname));
}
state = STARTING_SERVICES;
dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
Transaction txn = db.startTransaction(false);
try {
for (Client c : clients) {
@@ -172,17 +157,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
+ " took " + duration + " ms");
}
}
state = RUNNING;
startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS;
} catch (DataTooOldException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DATA_TOO_OLD_ERROR;
} catch (DataTooNewException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DATA_TOO_NEW_ERROR;
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DB_ERROR;
@@ -194,12 +170,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}
}
@Override
public void onMigrationRun() {
state = MIGRATING_DATABASE;
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
}
@Override
public void stopServices() {
try {
@@ -210,8 +180,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}
try {
LOG.info("Stopping services");
state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
eventBus.broadcast(new ShutdownEvent());
for (Service s : services) {
long start = System.currentTimeMillis();
s.stopService();
@@ -256,8 +225,4 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
shutdownLatch.await();
}
@Override
public LifecycleState getLifecycleState() {
return state;
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.Multiset;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -37,14 +36,14 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
private final Lock lock = new ReentrantLock();
// The following are locking: lock
private final Map<TransportId, Multiset<ContactId>> connections;
private final Multiset<ContactId> contactCounts;
private final Map<TransportId, Map<ContactId, Integer>> connections;
private final Map<ContactId, Integer> contactCounts;
@Inject
ConnectionRegistryImpl(EventBus eventBus) {
this.eventBus = eventBus;
connections = new HashMap<>();
contactCounts = new Multiset<>();
contactCounts = new HashMap<>();
}
@Override
@@ -57,13 +56,21 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
boolean firstConnection = false;
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
Map<ContactId, Integer> m = connections.get(t);
if (m == null) {
m = new Multiset<>();
m = new HashMap<>();
connections.put(t, m);
}
m.add(c);
if (contactCounts.add(c) == 1) firstConnection = true;
Integer count = m.get(c);
if (count == null) m.put(c, 1);
else m.put(c, count + 1);
count = contactCounts.get(c);
if (count == null) {
firstConnection = true;
contactCounts.put(c, 1);
} else {
contactCounts.put(c, count + 1);
}
} finally {
lock.unlock();
}
@@ -84,10 +91,23 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
boolean lastConnection = false;
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
Map<ContactId, Integer> m = connections.get(t);
if (m == null) throw new IllegalArgumentException();
m.remove(c);
if (contactCounts.remove(c) == 0) lastConnection = true;
Integer count = m.remove(c);
if (count == null) throw new IllegalArgumentException();
if (count == 1) {
if (m.isEmpty()) connections.remove(t);
} else {
m.put(c, count - 1);
}
count = contactCounts.get(c);
if (count == null) throw new IllegalArgumentException();
if (count == 1) {
lastConnection = true;
contactCounts.remove(c);
} else {
contactCounts.put(c, count - 1);
}
} finally {
lock.unlock();
}
@@ -102,7 +122,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public Collection<ContactId> getConnectedContacts(TransportId t) {
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
Map<ContactId, Integer> m = connections.get(t);
if (m == null) return Collections.emptyList();
List<ContactId> ids = new ArrayList<>(m.keySet());
if (LOG.isLoggable(INFO))
@@ -117,8 +137,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public boolean isConnected(ContactId c, TransportId t) {
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
return m != null && m.contains(c);
Map<ContactId, Integer> m = connections.get(t);
return m != null && m.containsKey(c);
} finally {
lock.unlock();
}
@@ -128,7 +148,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public boolean isConnected(ContactId c) {
lock.lock();
try {
return contactCounts.contains(c);
return contactCounts.containsKey(c);
} finally {
lock.unlock();
}

View File

@@ -16,7 +16,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.system.Clock;
@@ -26,7 +25,6 @@ import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -52,7 +50,7 @@ class Poller implements EventListener {
private final SecureRandom random;
private final Clock clock;
private final Lock lock;
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
private final Map<TransportId, PollTask> tasks; // Locking: lock
@Inject
Poller(@IoExecutor Executor ioExecutor,
@@ -95,10 +93,6 @@ class Poller implements EventListener {
TransportEnabledEvent t = (TransportEnabledEvent) e;
// Poll the newly enabled transport
pollNow(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e;
// Cancel polling for the disabled transport
cancel(t.getTransportId());
}
}
@@ -157,31 +151,18 @@ class Poller implements EventListener {
TransportId t = p.getId();
lock.lock();
try {
ScheduledPollTask scheduled = tasks.get(t);
if (scheduled == null || due < scheduled.task.due) {
// If a later task exists, cancel it. If it's already started
// it will abort safely when it finds it's been replaced
if (scheduled != null) scheduled.future.cancel(false);
PollTask scheduled = tasks.get(t);
if (scheduled == null || due < scheduled.due) {
PollTask task = new PollTask(p, due, randomiseNext);
Future future = scheduler.schedule(
tasks.put(t, task);
scheduler.schedule(
() -> ioExecutor.execute(task), delay, MILLISECONDS);
tasks.put(t, new ScheduledPollTask(task, future));
}
} finally {
lock.unlock();
}
}
private void cancel(TransportId t) {
lock.lock();
try {
ScheduledPollTask scheduled = tasks.remove(t);
if (scheduled != null) scheduled.future.cancel(false);
} finally {
lock.unlock();
}
}
@IoExecutor
private void poll(Plugin p) {
TransportId t = p.getId();
@@ -189,17 +170,6 @@ class Poller implements EventListener {
p.poll(connectionRegistry.getConnectedContacts(t));
}
private class ScheduledPollTask {
private final PollTask task;
private final Future future;
private ScheduledPollTask(PollTask task, Future future) {
this.task = task;
this.future = future;
}
}
private class PollTask implements Runnable {
private final Plugin plugin;
@@ -218,9 +188,7 @@ class Poller implements EventListener {
lock.lock();
try {
TransportId t = plugin.getId();
ScheduledPollTask scheduled = tasks.get(t);
if (scheduled != null && scheduled.task != this)
return; // Replaced by another task
if (tasks.get(t) != this) return; // Replaced by another task
tasks.remove(t);
} finally {
lock.unlock();

View File

@@ -23,8 +23,9 @@ import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -43,9 +44,6 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG =
Logger.getLogger(LanTcpPlugin.class.getName());
private static final LanAddressComparator ADDRESS_COMPARATOR =
new LanAddressComparator();
private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ",";
@@ -65,18 +63,19 @@ class LanTcpPlugin extends TcpPlugin {
TransportProperties p = callback.getLocalProperties();
String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>();
List<InetSocketAddress> locals = new LinkedList<>();
for (InetAddress local : getLocalIpAddresses()) {
if (isAcceptableAddress(local)) {
// If this is the old address, try to use the same port
for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local))
locals.add(new InetSocketAddress(local, old.getPort()));
if (old.getAddress().equals(local)) {
int port = old.getPort();
locals.add(0, new InetSocketAddress(local, port));
}
}
locals.add(new InetSocketAddress(local, 0));
}
}
Collections.sort(locals, ADDRESS_COMPARATOR);
return locals;
}
@@ -154,39 +153,17 @@ class LanTcpPlugin extends TcpPlugin {
// Package access for testing
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
// 10.0.0.0/8
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
if (localIp[0] == 10) return remoteIp[0] == 10;
// 172.16.0.0/12
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16)
return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16;
// 192.168.0.0/16
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168)
return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168;
// Unrecognised prefix - may be compatible
return true;
}
private static boolean isPrefix10(byte[] ipv4) {
return ipv4[0] == 10;
}
private static boolean isPrefix172(byte[] ipv4) {
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
}
private static boolean isPrefix192(byte[] ipv4) {
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
}
// Returns the prefix length for an RFC 1918 address, or 0 for any other
// address
private static int getRfc1918PrefixLength(InetAddress addr) {
if (!(addr instanceof Inet4Address)) return 0;
if (!addr.isSiteLocalAddress()) return 0;
byte[] ipv4 = addr.getAddress();
if (isPrefix10(ipv4)) return 8;
if (isPrefix172(ipv4)) return 12;
if (isPrefix192(ipv4)) return 16;
return 0;
}
@Override
public boolean supportsKeyAgreement() {
return true;
@@ -223,7 +200,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
InetSocketAddress remote;
try {
@@ -282,11 +259,14 @@ class LanTcpPlugin extends TcpPlugin {
}
@Override
public KeyAgreementConnection accept() throws IOException {
Socket s = ss.accept();
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
return new KeyAgreementConnection(new TcpTransportConnection(
LanTcpPlugin.this, s), ID);
public Callable<KeyAgreementConnection> listen() {
return () -> {
Socket s = ss.accept();
if (LOG.isLoggable(INFO))
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new TcpTransportConnection(LanTcpPlugin.this, s), ID);
};
}
@Override
@@ -298,19 +278,4 @@ class LanTcpPlugin extends TcpPlugin {
}
}
}
static class LanAddressComparator implements Comparator<InetSocketAddress> {
@Override
public int compare(InetSocketAddress a, InetSocketAddress b) {
// Prefer addresses with non-zero ports
int aPort = a.getPort(), bPort = b.getPort();
if (aPort > 0 && bPort == 0) return -1;
if (aPort == 0 && bPort > 0) return 1;
// Prefer addresses with longer RFC 1918 prefixes
int aPrefix = getRfc1918PrefixLength(a.getAddress());
int bPrefix = getRfc1918PrefixLength(b.getAddress());
return bPrefix - aPrefix;
}
}
}

View File

@@ -297,7 +297,7 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
byte[] commitment, BdfList descriptor, long timeout) {
throw new UnsupportedOperationException();
}

View File

@@ -64,7 +64,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override
public void createLocalState(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup);
// Ensure we've set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
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;
@@ -29,8 +29,6 @@ import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
@@ -38,7 +36,6 @@ import javax.annotation.concurrent.ThreadSafe;
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.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
@@ -52,14 +49,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
@NotNullByDefault
class DuplexOutgoingSession implements SyncSession, EventListener {
// Check for retransmittable records once every 60 seconds
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
private static final Logger LOG =
Logger.getLogger(DuplexOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE = () -> {
};
private static final ThrowingRunnable<IOException>
NEXT_SEND_TIME_DECREASED = () -> {
};
private static final ThrowingRunnable<IOException> CLOSE = () -> {};
private final DatabaseComponent db;
private final Executor dbExecutor;
@@ -70,13 +65,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final RecordWriter recordWriter;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
private final AtomicBoolean generateBatchQueued = new AtomicBoolean(false);
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
private final AtomicBoolean generateRequestQueued =
new AtomicBoolean(false);
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
private volatile boolean interrupted = false;
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
@@ -99,21 +87,21 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
eventBus.addListener(this);
try {
// Start a query for each type of record
generateAck();
generateBatch();
generateOffer();
generateRequest();
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
dbExecutor.execute(new GenerateRequest());
long now = clock.currentTimeMillis();
long nextKeepalive = now + maxIdleTime;
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
boolean dataToFlush = true;
// Write records until interrupted
try {
while (!interrupted) {
// Work out how long we should wait for a record
now = clock.currentTimeMillis();
long keepaliveWait = Math.max(0, nextKeepalive - now);
long sendWait = Math.max(0, nextSendTime.get() - now);
long wait = Math.min(keepaliveWait, sendWait);
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();
@@ -125,25 +113,20 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
MILLISECONDS);
if (task == null) {
now = clock.currentTimeMillis();
if (now >= nextSendTime.get()) {
// Check for retransmittable messages
LOG.info("Checking for retransmittable messages");
setNextSendTime(Long.MAX_VALUE);
generateBatch();
generateOffer();
if (now >= nextRetxQuery) {
// Check for retransmittable records
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
nextRetxQuery = now + RETX_QUERY_INTERVAL;
}
if (now >= nextKeepalive) {
// Flush the stream to keep it alive
LOG.info("Sending keepalive");
recordWriter.flush();
dataToFlush = false;
nextKeepalive = now + maxIdleTime;
}
} else if (task == CLOSE) {
LOG.info("Closed");
break;
} else if (task == NEXT_SEND_TIME_DECREASED) {
LOG.info("Next send time decreased");
} else {
task.run();
dataToFlush = true;
@@ -159,31 +142,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
}
}
private void generateAck() {
if (generateAckQueued.compareAndSet(false, true))
dbExecutor.execute(new GenerateAck());
}
private void generateBatch() {
if (generateBatchQueued.compareAndSet(false, true))
dbExecutor.execute(new GenerateBatch());
}
private void generateOffer() {
if (generateOfferQueued.compareAndSet(false, true))
dbExecutor.execute(new GenerateOffer());
}
private void generateRequest() {
if (generateRequestQueued.compareAndSet(false, true))
dbExecutor.execute(new GenerateRequest());
}
private void setNextSendTime(long time) {
long old = nextSendTime.getAndSet(time);
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
}
@Override
public void interrupt() {
interrupted = true;
@@ -196,23 +154,22 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof MessageSharedEvent) {
generateOffer();
dbExecutor.execute(new GenerateOffer());
} else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getAffectedContacts().contains(contactId))
generateOffer();
dbExecutor.execute(new GenerateOffer());
} else if (e instanceof MessageRequestedEvent) {
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
generateBatch();
dbExecutor.execute(new GenerateBatch());
} else if (e instanceof MessageToAckEvent) {
if (((MessageToAckEvent) e).getContactId().equals(contactId))
generateAck();
dbExecutor.execute(new GenerateAck());
} else if (e instanceof MessageToRequestEvent) {
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
generateRequest();
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
dbExecutor.execute(new GenerateRequest());
} else if (e instanceof ShutdownEvent) {
interrupt();
}
}
@@ -222,7 +179,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() {
if (interrupted) return;
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
try {
Ack a;
Transaction txn = db.startTransaction(false);
@@ -256,7 +212,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return;
recordWriter.writeAck(ack);
LOG.info("Sent ack");
generateAck();
dbExecutor.execute(new GenerateAck());
}
}
@@ -266,15 +222,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() {
if (interrupted) return;
if (!generateBatchQueued.getAndSet(false))
throw new AssertionError();
try {
Collection<byte[]> b;
Transaction txn = db.startTransaction(false);
try {
b = db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -303,7 +256,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return;
for (byte[] raw : batch) recordWriter.writeMessage(raw);
LOG.info("Sent batch");
generateBatch();
dbExecutor.execute(new GenerateBatch());
}
}
@@ -313,15 +266,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() {
if (interrupted) return;
if (!generateOfferQueued.getAndSet(false))
throw new AssertionError();
try {
Offer o;
Transaction txn = db.startTransaction(false);
try {
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -350,7 +300,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return;
recordWriter.writeOffer(offer);
LOG.info("Sent offer");
generateOffer();
dbExecutor.execute(new GenerateOffer());
}
}
@@ -360,8 +310,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override
public void run() {
if (interrupted) return;
if (!generateRequestQueued.getAndSet(false))
throw new AssertionError();
try {
Request r;
Transaction txn = db.startTransaction(false);
@@ -395,7 +343,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return;
recordWriter.writeRequest(request);
LOG.info("Sent request");
generateRequest();
dbExecutor.execute(new GenerateRequest());
}
}
}

View File

@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
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.Message;
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
/**
* An incoming {@link SyncSession}.
@@ -97,9 +96,8 @@ class IncomingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof ShutdownEvent) {
interrupt();
}
}

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
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;
@@ -28,7 +28,6 @@ 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.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
@@ -110,9 +109,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof ShutdownEvent) {
interrupt();
}
}

View File

@@ -71,9 +71,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
@Override
public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException();
validateOutstandingMessagesAsync();
deliverOutstandingMessagesAsync();
shareOutstandingMessagesAsync();
for (ClientId c : validators.keySet()) {
validateOutstandingMessagesAsync(c);
deliverOutstandingMessagesAsync(c);
shareOutstandingMessagesAsync(c);
}
}
@Override
@@ -91,17 +93,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
hooks.put(c, hook);
}
private void validateOutstandingMessagesAsync() {
dbExecutor.execute(this::validateOutstandingMessages);
private void validateOutstandingMessagesAsync(ClientId c) {
dbExecutor.execute(() -> validateOutstandingMessages(c));
}
@DatabaseExecutor
private void validateOutstandingMessages() {
private void validateOutstandingMessages(ClientId c) {
try {
Queue<MessageId> unvalidated = new LinkedList<>();
Transaction txn = db.startTransaction(true);
try {
unvalidated.addAll(db.getMessagesToValidate(txn));
unvalidated.addAll(db.getMessagesToValidate(txn, c));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -146,17 +148,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
}
private void deliverOutstandingMessagesAsync() {
dbExecutor.execute(this::deliverOutstandingMessages);
private void deliverOutstandingMessagesAsync(ClientId c) {
dbExecutor.execute(() -> deliverOutstandingMessages(c));
}
@DatabaseExecutor
private void deliverOutstandingMessages() {
private void deliverOutstandingMessages(ClientId c) {
try {
Queue<MessageId> pending = new LinkedList<>();
Transaction txn = db.startTransaction(true);
try {
pending.addAll(db.getPendingMessages(txn));
pending.addAll(db.getPendingMessages(txn, c));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -351,17 +353,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
return pending;
}
private void shareOutstandingMessagesAsync() {
dbExecutor.execute(this::shareOutstandingMessages);
private void shareOutstandingMessagesAsync(ClientId c) {
dbExecutor.execute(() -> shareOutstandingMessages(c));
}
@DatabaseExecutor
private void shareOutstandingMessages() {
private void shareOutstandingMessages(ClientId c) {
try {
Queue<MessageId> toShare = new LinkedList<>();
Transaction txn = db.startTransaction(true);
try {
toShare.addAll(db.getMessagesToShare(txn));
toShare.addAll(db.getMessagesToShare(txn, c));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);

View File

@@ -15,8 +15,6 @@ import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
@@ -33,14 +31,9 @@ import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -61,8 +54,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
context.mock(MetadataEncoder.class);
private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class);
private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class);
private final ClientHelper clientHelper;
private final GroupId groupId = new GroupId(getRandomId());
private final BdfDictionary dictionary = new BdfDictionary();
@@ -74,15 +66,17 @@ public class ClientHelperImplTest extends BrambleTestCase {
private final Metadata metadata = new Metadata();
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
private final String label = StringUtils.getRandomString(5);
private final Author author = getAuthor();
private final ClientHelper clientHelper = new ClientHelperImpl(db,
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
metadataEncoder, cryptoComponent, authorFactory);
public ClientHelperImplTest() {
clientHelper =
new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
}
@Test
public void testAddLocalMessage() throws Exception {
boolean shared = new Random().nextBoolean();
boolean shared = true;
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
@@ -186,7 +180,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn);
}});
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@@ -323,7 +318,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
try {
clientHelper.verifySignature(label, rawMessage, publicKey, list);
clientHelper
.verifySignature(label, rawMessage, publicKey, list);
fail();
} catch (GeneralSecurityException e) {
// expected
@@ -331,166 +327,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}
}
@Test
public void testParsesAndEncodesAuthor() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(author.getFormatVersion(),
author.getName(), author.getPublicKey());
will(returnValue(author));
}});
BdfList authorList = clientHelper.toList(author);
assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
}
@Test
public void testAcceptsValidAuthor() throws Exception {
BdfList authorList = BdfList.of(
author.getFormatVersion(),
author.getName(),
author.getPublicKey()
);
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(author.getFormatVersion(),
author.getName(), author.getPublicKey());
will(returnValue(author));
}});
assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortAuthor() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongAuthor() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
author.getPublicKey(),
"foo"
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNullFormatVersion() throws Exception {
BdfList invalidAuthor = BdfList.of(
null,
author.getName(),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNonIntegerFormatVersion()
throws Exception {
BdfList invalidAuthor = BdfList.of(
"foo",
author.getName(),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithUnknownFormatVersion() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion() + 1,
author.getName(),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooShortName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
"",
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooLongName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNullName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
null,
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNonStringName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
getRandomBytes(5),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooShortPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
new byte[0]
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooLongPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1)
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNullPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
null
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNonRawPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
"foo"
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
private byte[] expectToByteArray(BdfList list) throws Exception {
BdfWriter bdfWriter = context.mock(BdfWriter.class);
@@ -516,4 +352,5 @@ public class ClientHelperImplTest extends BrambleTestCase {
will(returnValue(eof));
}});
}
}

View File

@@ -18,9 +18,8 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Random;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals;
@@ -33,7 +32,9 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final KeyManager keyManager = context.mock(KeyManager.class);
private final ContactManager contactManager;
private final ContactId contactId = new ContactId(42);
private final Author remote = getAuthor();
private final Author remote =
new Author(new AuthorId(getRandomId()), "remote",
getRandomBytes(42));
private final AuthorId local = new AuthorId(getRandomId());
private final boolean verified = false, active = true;
private final Contact contact =
@@ -46,8 +47,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test
public void testAddContact() throws Exception {
SecretKey master = getSecretKey();
long timestamp = System.currentTimeMillis();
boolean alice = new Random().nextBoolean();
long timestamp = 42;
boolean alice = true;
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{

View File

@@ -3,76 +3,75 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import org.spongycastle.crypto.digests.Blake2bDigest;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
public class Blake2bDigestTest extends BrambleTestCase {
public class Blake2sDigestTest extends BrambleTestCase {
// Vectors from BLAKE2 web site: https://blake2.net/Blake2b-test.txt
private static final String[][] KEYED_TEST_VECTORS = {
// Vectors from BLAKE2 web site: https://blake2.net/blake2s-test.txt
private static final String[][] keyedTestVectors = {
// input/message, key, hash
{
"",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49",
},
{
"00",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1",
},
{
"0001",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"172ffc67153d12e0ca76a8b6cd5d4731885b39ce0cac93a8972a18006c8b8baf",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"4f8ce1e51d2fe7f24043a904d898ebfc91975418753413aa099b795ecb35cedb",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd",
},
};
@Test
public void testDigestWithKeyedTestVectors() {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
keyedTestVectors[0][1]));
for (String[] keyedTestVector : keyedTestVectors) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
byte[] expected = StringUtils.fromHexString(keyedTestVector[2]);
digest.reset();
Blake2bDigest digest = new Blake2bDigest(key);
digest.update(input, 0, input.length);
byte[] hash = new byte[64];
byte[] hash = new byte[32];
digest.doFinal(hash, 0);
assertArrayEquals(expected, hash);
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
hash);
}
}
@Test
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
keyedTestVectors[0][1]));
Random random = new Random();
for (int i = 0; i < 100; i++) {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
for (String[] keyedTestVector : keyedTestVectors) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
if (input.length == 0) continue;
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
byte[] expected = StringUtils.fromHexString(keyedTestVector[2]);
Blake2bDigest digest = new Blake2bDigest(key);
if (input.length < 3) continue;
digest.reset();
int pos = random.nextInt(input.length);
if (pos > 0)
@@ -81,10 +80,11 @@ public class Blake2bDigestTest extends BrambleTestCase {
if (pos < (input.length - 1))
digest.update(input, pos + 1, input.length - (pos + 1));
byte[] hash = new byte[64];
byte[] hash = new byte[32];
digest.doFinal(hash, 0);
assertArrayEquals(expected, hash);
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
hash);
}
}
}
@@ -98,12 +98,12 @@ public class Blake2bDigestTest extends BrambleTestCase {
byte[] input = new byte[key.length + 1];
for (byte i = 0; i < input.length; i++) input[i] = i;
// Hash the input
Blake2bDigest digest = new Blake2bDigest(key);
Blake2sDigest digest = new Blake2sDigest(key);
digest.update(input, 0, input.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
// Create a second instance, hash the input without calling doFinal()
Blake2bDigest digest1 = new Blake2bDigest(key);
Blake2sDigest digest1 = new Blake2sDigest(key);
digest1.update(input, 0, input.length);
// Reset the second instance and hash the input again
digest1.reset();
@@ -116,10 +116,9 @@ public class Blake2bDigestTest extends BrambleTestCase {
// Self-test routine from https://tools.ietf.org/html/rfc7693#appendix-E
private static final String SELF_TEST_RESULT =
"C23A7800D98123BD10F506C61E29DA5603D763B8BBAD2E737F5E765A7BCCD475";
private static final int[] SELF_TEST_DIGEST_LEN = {20, 32, 48, 64};
private static final int[] SELF_TEST_INPUT_LEN =
{0, 3, 128, 129, 255, 1024};
"6A411F08CE25ADCDFB02ABA641451CEC53C598B24F4FC787FBDC88797F4C1DFE";
private static final int[] SELF_TEST_DIGEST_LEN = {16, 20, 28, 32};
private static final int[] SELF_TEST_INPUT_LEN = {0, 3, 64, 65, 255, 1024};
private static byte[] selfTestSequence(int len, int seed) {
int a = 0xDEAD4BAD * seed;
@@ -139,8 +138,8 @@ public class Blake2bDigestTest extends BrambleTestCase {
@Test
public void runSelfTest() {
Blake2bDigest testDigest = new Blake2bDigest(256);
byte[] md = new byte[64];
Blake2sDigest testDigest = new Blake2sDigest();
byte[] md = new byte[32];
for (int i = 0; i < 4; i++) {
int outlen = SELF_TEST_DIGEST_LEN[i];
@@ -149,7 +148,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
// unkeyed hash
byte[] in = selfTestSequence(inlen, inlen);
Blake2bDigest unkeyedDigest = new Blake2bDigest(outlen * 8);
Blake2sDigest unkeyedDigest = new Blake2sDigest(outlen * 8);
unkeyedDigest.update(in, 0, inlen);
unkeyedDigest.doFinal(md, 0);
// hash the hash
@@ -157,7 +156,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
// keyed hash
byte[] key = selfTestSequence(outlen, outlen);
Blake2bDigest keyedDigest = new Blake2bDigest(key, outlen, null,
Blake2sDigest keyedDigest = new Blake2sDigest(key, outlen, null,
null);
keyedDigest.update(in, 0, inlen);
keyedDigest.doFinal(md, 0);

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import java.security.GeneralSecurityException;
public class EcdsaSignatureTest extends SignatureTest {
@Override
protected KeyPair generateKeyPair() {
return crypto.generateSignatureKeyPair();
}
@Override
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
return crypto.sign(label, toSign, privateKey);
}
@Override
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
return crypto.verify(label, signedData, publicKey, signature);
}
}

View File

@@ -1,169 +1,25 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class EdSignatureTest extends SignatureTest {
// Test vectors from RFC 8032: secret key, public key, message, signature
// https://tools.ietf.org/html/rfc8032#section-7.1
private static final String[][] TEST_VECTORS = {{
"9d61b19deffd5a60ba844af492ec2cc4" +
"4449c5697b326919703bac031cae7f60",
"d75a980182b10ab7d54bfed3c964073a" +
"0ee172f3daa62325af021a68f707511a",
"",
"e5564300c360ac729086e2cc806e828a" +
"84877f1eb8e5d974d873e06522490155" +
"5fb8821590a33bacc61e39701cf9b46b" +
"d25bf5f0595bbe24655141438e7a100b"
}, {
"4ccd089b28ff96da9db6c346ec114e0f" +
"5b8a319f35aba624da8cf6ed4fb8a6fb",
"3d4017c3e843895a92b70aa74d1b7ebc" +
"9c982ccf2ec4968cc0cd55f12af4660c",
"72",
"92a009a9f0d4cab8720e820b5f642540" +
"a2b27b5416503f8fb3762223ebdb69da" +
"085ac1e43e15996e458f3613d0f11d8c" +
"387b2eaeb4302aeeb00d291612bb0c00"
}, {
"c5aa8df43f9f837bedb7442f31dcb7b1" +
"66d38535076f094b85ce3a2e0b4458f7",
"fc51cd8e6218a1a38da47ed00230f058" +
"0816ed13ba3303ac5deb911548908025",
"af82",
"6291d657deec24024827e69c3abe01a3" +
"0ce548a284743a445e3680d7db5ac3ac" +
"18ff9b538d16f290ae67f760984dc659" +
"4a7c15e9716ed28dc027beceea1ec40a"
}, {
"f5e5767cf153319517630f226876b86c" +
"8160cc583bc013744c6bf255f5cc0ee5",
"278117fc144c72340f67d0f2316e8386" +
"ceffbf2b2428c9c51fef7c597f1d426e",
"08b8b2b733424243760fe426a4b54908" +
"632110a66c2f6591eabd3345e3e4eb98" +
"fa6e264bf09efe12ee50f8f54e9f77b1" +
"e355f6c50544e23fb1433ddf73be84d8" +
"79de7c0046dc4996d9e773f4bc9efe57" +
"38829adb26c81b37c93a1b270b20329d" +
"658675fc6ea534e0810a4432826bf58c" +
"941efb65d57a338bbd2e26640f89ffbc" +
"1a858efcb8550ee3a5e1998bd177e93a" +
"7363c344fe6b199ee5d02e82d522c4fe" +
"ba15452f80288a821a579116ec6dad2b" +
"3b310da903401aa62100ab5d1a36553e" +
"06203b33890cc9b832f79ef80560ccb9" +
"a39ce767967ed628c6ad573cb116dbef" +
"efd75499da96bd68a8a97b928a8bbc10" +
"3b6621fcde2beca1231d206be6cd9ec7" +
"aff6f6c94fcd7204ed3455c68c83f4a4" +
"1da4af2b74ef5c53f1d8ac70bdcb7ed1" +
"85ce81bd84359d44254d95629e9855a9" +
"4a7c1958d1f8ada5d0532ed8a5aa3fb2" +
"d17ba70eb6248e594e1a2297acbbb39d" +
"502f1a8c6eb6f1ce22b3de1a1f40cc24" +
"554119a831a9aad6079cad88425de6bd" +
"e1a9187ebb6092cf67bf2b13fd65f270" +
"88d78b7e883c8759d2c4f5c65adb7553" +
"878ad575f9fad878e80a0c9ba63bcbcc" +
"2732e69485bbc9c90bfbd62481d9089b" +
"eccf80cfe2df16a2cf65bd92dd597b07" +
"07e0917af48bbb75fed413d238f5555a" +
"7a569d80c3414a8d0859dc65a46128ba" +
"b27af87a71314f318c782b23ebfe808b" +
"82b0ce26401d2e22f04d83d1255dc51a" +
"ddd3b75a2b1ae0784504df543af8969b" +
"e3ea7082ff7fc9888c144da2af58429e" +
"c96031dbcad3dad9af0dcbaaaf268cb8" +
"fcffead94f3c7ca495e056a9b47acdb7" +
"51fb73e666c6c655ade8297297d07ad1" +
"ba5e43f1bca32301651339e22904cc8c" +
"42f58c30c04aafdb038dda0847dd988d" +
"cda6f3bfd15c4b4c4525004aa06eeff8" +
"ca61783aacec57fb3d1f92b0fe2fd1a8" +
"5f6724517b65e614ad6808d6f6ee34df" +
"f7310fdc82aebfd904b01e1dc54b2927" +
"094b2db68d6f903b68401adebf5a7e08" +
"d78ff4ef5d63653a65040cf9bfd4aca7" +
"984a74d37145986780fc0b16ac451649" +
"de6188a7dbdf191f64b5fc5e2ab47b57" +
"f7f7276cd419c17a3ca8e1b939ae49e4" +
"88acba6b965610b5480109c8b17b80e1" +
"b7b750dfc7598d5d5011fd2dcc5600a3" +
"2ef5b52a1ecc820e308aa342721aac09" +
"43bf6686b64b2579376504ccc493d97e" +
"6aed3fb0f9cd71a43dd497f01f17c0e2" +
"cb3797aa2a2f256656168e6c496afc5f" +
"b93246f6b1116398a346f1a641f3b041" +
"e989f7914f90cc2c7fff357876e506b5" +
"0d334ba77c225bc307ba537152f3f161" +
"0e4eafe595f6d9d90d11faa933a15ef1" +
"369546868a7f3a45a96768d40fd9d034" +
"12c091c6315cf4fde7cb68606937380d" +
"b2eaaa707b4c4185c32eddcdd306705e" +
"4dc1ffc872eeee475a64dfac86aba41c" +
"0618983f8741c5ef68d3a101e8a3b8ca" +
"c60c905c15fc910840b94c00a0b9d0",
"0aab4c900501b3e24d7cdf4663326a3a" +
"87df5e4843b2cbdb67cbf6e460fec350" +
"aa5371b1508f9f4528ecea23c436d94b" +
"5e8fcd4f681e30a6ac00a9704a188a03"
}, {
"833fe62409237b9d62ec77587520911e" +
"9a759cec1d19755b7da901b96dca3d42",
"ec172b93ad5e563bf4932c70e1245034" +
"c35467ef2efd4d64ebf819683467e2bf",
"ddaf35a193617abacc417349ae204131" +
"12e6fa4e89a97ea20a9eeee64b55d39a" +
"2192992a274fc1a836ba3c23a3feebbd" +
"454d4423643ce80e2a9ac94fa54ca49f",
"dc2a4459e7369633a52b1bf277839a00" +
"201009a3efbf3ecb69bea2186c26b589" +
"09351fc9ac90b3ecfdfbc7c66431e030" +
"3dca179c138ac17ad9bef1177331a704"
}};
@Override
protected KeyPair generateKeyPair() {
return crypto.generateSignatureKeyPair();
return crypto.generateEdKeyPair();
}
@Override
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
return crypto.sign(label, toSign, privateKey);
return crypto.signEd(label, toSign, privateKey);
}
@Override
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
return crypto.verify(label, signedData, publicKey, signature);
}
@Test
public void testRfc8032TestVectors() throws Exception {
for (String[] vector : TEST_VECTORS) {
byte[] privateKeyBytes = fromHexString(vector[0]);
byte[] publicKeyBytes = fromHexString(vector[1]);
byte[] messageBytes = fromHexString(vector[2]);
byte[] signatureBytes = fromHexString(vector[3]);
EdSignature signature = new EdSignature();
signature.initSign(new EdPrivateKey(privateKeyBytes));
signature.update(messageBytes);
assertArrayEquals(signatureBytes, signature.sign());
signature.initVerify(new EdPublicKey(publicKeyBytes));
signature.update(messageBytes);
assertTrue(signature.verify(signatureBytes));
}
return crypto.verifyEd(label, signedData, publicKey, signature);
}
}

View File

@@ -13,11 +13,11 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.junit.Assert.assertEquals;
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
@@ -31,11 +31,15 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
ECPoint defaultG = defaultX9Parameters.getG();
BigInteger defaultN = defaultX9Parameters.getN();
BigInteger defaultH = defaultX9Parameters.getH();
// Check that the default parameters are equal to our parameters
assertEquals(PARAMETERS.getCurve(), defaultCurve);
assertEquals(PARAMETERS.getG(), defaultG);
assertEquals(PARAMETERS.getN(), defaultN);
assertEquals(PARAMETERS.getH(), defaultH);
// ECDomainParameters doesn't have an equals() method, but it's just a
// container for the parameters
ECDomainParameters defaultParameters = new ECDomainParameters(
defaultCurve, defaultG, defaultN, defaultH);
// Instantiate an implementation using the Montgomery ladder multiplier
ECDomainParameters montgomeryParameters =
constantTime(defaultParameters);
// Generate two key pairs with each set of parameters, using the same
// deterministic PRNG for both sets of parameters
byte[] seed = new byte[32];
@@ -43,7 +47,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
// Montgomery ladder multiplier
SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(montgomeryParameters, random);
new ECKeyGenerationParameters(PARAMETERS, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
montgomeryGenerator.init(montgomeryGeneratorParams);
AsymmetricCipherKeyPair montgomeryKeyPair1 =
@@ -103,13 +107,4 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultDefault);
}
private static ECDomainParameters constantTime(ECDomainParameters in) {
ECCurve curve = in.getCurve().configure().setMultiplier(
new MontgomeryLadderMultiplier()).create();
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(x, y);
return new ECDomainParameters(curve, g, in.getN(), in.getH());
}
}

View File

@@ -11,7 +11,6 @@ import org.spongycastle.crypto.BasicAgreement;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
@@ -38,6 +37,7 @@ import java.util.Collections;
import java.util.List;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
// Not a JUnit test
public class EllipticCurvePerformanceTest {
@@ -64,6 +64,7 @@ public class EllipticCurvePerformanceTest {
runTest(name + " default", params);
runTest(name + " constant", constantTime(params));
}
runTest("ours", PARAMETERS);
runCurve25519Test();
runEd25519Test();
}
@@ -82,7 +83,7 @@ public class EllipticCurvePerformanceTest {
List<Long> samples = new ArrayList<>();
List<byte[]> signatures = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
@@ -97,7 +98,7 @@ public class EllipticCurvePerformanceTest {
// Time some signature verifications
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);

View File

@@ -22,7 +22,7 @@ public class HashTest extends BrambleTestCase {
private final byte[] inputBytes2 = new byte[0];
public HashTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
@Test

View File

@@ -2,80 +2,33 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test;
import org.whispersystems.curve25519.Curve25519;
import java.security.GeneralSecurityException;
import java.util.Random;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.junit.Assert.assertArrayEquals;
public class KeyAgreementTest extends BrambleTestCase {
// Test vector from RFC 7748: Alice's private and public keys, Bob's
// private and public keys, and the shared secret
// https://tools.ietf.org/html/rfc7748#section-6.1
private static final String ALICE_PRIVATE =
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a";
private static final String ALICE_PUBLIC =
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a";
private static final String BOB_PRIVATE =
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb";
private static final String BOB_PUBLIC =
"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f";
private static final String SHARED_SECRET =
"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742";
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final byte[][] inputs;
public KeyAgreementTest() {
Random random = new Random();
inputs = new byte[random.nextInt(10) + 1][];
for (int i = 0; i < inputs.length; i++)
inputs[i] = getRandomBytes(random.nextInt(256));
}
@Test
public void testDerivesSharedSecret() throws Exception {
public void testDeriveSharedSecret() throws Exception {
CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider());
KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair();
Random random = new Random();
byte[][] inputs = new byte[random.nextInt(10) + 1][];
for (int i = 0; i < inputs.length; i++)
inputs[i] = getRandomBytes(random.nextInt(256));
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
bPair.getPublic(), aPair, inputs);
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
aPair.getPublic(), bPair, inputs);
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
}
@Test(expected = GeneralSecurityException.class)
public void testRejectsInvalidPublicKey() throws Exception {
KeyPair keyPair = crypto.generateAgreementKeyPair();
PublicKey invalid = new Curve25519PublicKey(new byte[32]);
crypto.deriveSharedSecret(SHARED_SECRET_LABEL, invalid, keyPair,
inputs);
}
@Test
public void testRfc7748TestVector() throws Exception {
// Private keys need to be clamped because curve25519-java does the
// clamping at key generation time, not multiplication time
byte[] aPriv = Curve25519KeyParser.clamp(fromHexString(ALICE_PRIVATE));
byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = Curve25519KeyParser.clamp(fromHexString(BOB_PRIVATE));
byte[] bPub = fromHexString(BOB_PUBLIC);
byte[] sharedSecret = fromHexString(SHARED_SECRET);
Curve25519 curve25519 = Curve25519.getInstance("java");
assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(aPub, bPriv));
assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(bPub, aPriv));
}
}

View File

@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
public class KeyDerivationTest extends BrambleTestCase {
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
new CryptoComponentImpl(new TestSecureRandomProvider());
private final TransportCrypto transportCrypto =
new TransportCryptoImpl(crypto);
private final TransportId transportId = new TransportId("id");

View File

@@ -6,30 +6,29 @@ import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class KeyEncodingAndParsingTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
new CryptoComponentImpl(new TestSecureRandomProvider());
@Test
public void testAgreementPublicKeyLength() throws Exception {
// Generate 10 agreement key pairs
for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateAgreementKeyPair();
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES);
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@@ -46,8 +45,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
aPub = parser.parsePublicKey(aPub.getEncoded());
aPub = parser.parsePublicKey(aPub.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 =
crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
assertArrayEquals(secret, secret1);
}
@@ -64,8 +62,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 =
crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
assertArrayEquals(secret, secret1);
}
@@ -79,12 +76,12 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
// Parse some random byte arrays - expect GeneralSecurityException
for (int i = 0; i < 1000; i++) {
try {
parser.parsePublicKey(getRandomBytes(pubLength));
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
} catch (GeneralSecurityException expected) {
// Expected
}
try {
parser.parsePrivateKey(getRandomBytes(privLength));
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
} catch (GeneralSecurityException expected) {
// Expected
}
@@ -98,7 +95,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_SIGNATURE_PUBLIC_KEY_BYTES);
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@@ -109,53 +106,44 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] key = keyPair.getPrivate().getEncoded();
// Sign some random data and check the length of the signature
byte[] toBeSigned = getRandomBytes(1234);
byte[] toBeSigned = TestUtils.getRandomBytes(1234);
byte[] signature = crypto.sign("label", toBeSigned, key);
assertTrue(signature.length <= MAX_SIGNATURE_BYTES);
assertTrue(signature.length <= MAX_SIGNATURE_LENGTH);
}
}
@Test
public void testSignaturePublicKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate a key pair and sign some data
KeyPair keyPair = crypto.generateSignatureKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
byte[] message = getRandomBytes(123);
byte[] signature = crypto.sign("test", message,
privateKey.getEncoded());
// Verify the signature
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature));
// Generate two key pairs
KeyPair aPair = crypto.generateSignatureKeyPair();
KeyPair bPair = crypto.generateSignatureKeyPair();
// Derive the shared secret
PublicKey aPub = aPair.getPublic();
byte[] secret = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
// Encode and parse the public key - no exceptions should be thrown
publicKey = parser.parsePublicKey(publicKey.getEncoded());
// Verify the signature again
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature));
aPub = parser.parsePublicKey(aPub.getEncoded());
aPub = parser.parsePublicKey(aPub.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
assertArrayEquals(secret, secret1);
}
@Test
public void testSignaturePrivateKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate a key pair and sign some data
KeyPair keyPair = crypto.generateSignatureKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
byte[] message = getRandomBytes(123);
byte[] signature = crypto.sign("test", message,
privateKey.getEncoded());
// Verify the signature
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature));
// Generate two key pairs
KeyPair aPair = crypto.generateSignatureKeyPair();
KeyPair bPair = crypto.generateSignatureKeyPair();
// Derive the shared secret
PrivateKey bPriv = bPair.getPrivate();
byte[] secret = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
// Encode and parse the private key - no exceptions should be thrown
privateKey = parser.parsePrivateKey(privateKey.getEncoded());
// Sign the data again - the signatures should be the same
byte[] signature1 = crypto.sign("test", message,
privateKey.getEncoded());
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature1));
assertArrayEquals(signature, signature1);
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
assertArrayEquals(secret, secret1);
}
@Test
@@ -168,12 +156,12 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
// Parse some random byte arrays - expect GeneralSecurityException
for (int i = 0; i < 1000; i++) {
try {
parser.parsePublicKey(getRandomBytes(pubLength));
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
} catch (GeneralSecurityException expected) {
// Expected
}
try {
parser.parsePrivateKey(getRandomBytes(privLength));
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
} catch (GeneralSecurityException expected) {
// Expected
}

View File

@@ -17,7 +17,7 @@ import static org.junit.Assert.assertFalse;
public class MacTest extends BrambleTestCase {
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
new CryptoComponentImpl(new TestSecureRandomProvider());
private final SecretKey key1 = getSecretKey(), key2 = getSecretKey();
private final String label1 = getRandomString(123);

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
@@ -9,13 +8,14 @@ import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class PasswordBasedEncryptionTest extends BrambleTestCase {
public class PasswordBasedKdfTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(),
new ScryptKdf(new SystemClock()));
new CryptoComponentImpl(new TestSecureRandomProvider());
@Test
public void testEncryptionAndDecryption() {
@@ -37,4 +37,17 @@ public class PasswordBasedEncryptionTest extends BrambleTestCase {
byte[] output = crypto.decryptWithPassword(ciphertext, password);
assertNull(output);
}
@Test
public void testCalibration() {
// If the target time is unachievable, one iteration should be used
int iterations = crypto.chooseIterationCount(0);
assertEquals(1, iterations);
// If the target time is long, more than one iteration should be used
iterations = crypto.chooseIterationCount(10 * 1000);
assertTrue(iterations > 1);
// If the target time is very long, max iterations should be used
iterations = crypto.chooseIterationCount(Integer.MAX_VALUE);
assertEquals(Integer.MAX_VALUE, iterations);
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
@@ -18,7 +17,7 @@ class PseudoRandom {
PseudoRandom(byte[] seed) {
// Hash the seed to produce a 32-byte key
byte[] key = new byte[32];
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
digest.update(seed, 0, seed.length);
digest.doFinal(key, 0);
// Initialise the stream cipher with an all-zero nonce

View File

@@ -1,97 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
public class ScryptKdfTest extends BrambleTestCase {
@Test
public void testPasswordAffectsKey() throws Exception {
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
byte[] salt = getRandomBytes(32);
Set<Bytes> keys = new HashSet<>();
for (int i = 0; i < 100; i++) {
String password = getRandomString(16);
SecretKey key = kdf.deriveKey(password, salt, 256);
assertTrue(keys.add(new Bytes(key.getBytes())));
}
}
@Test
public void testSaltAffectsKey() throws Exception {
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
String password = getRandomString(16);
Set<Bytes> keys = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] salt = getRandomBytes(32);
SecretKey key = kdf.deriveKey(password, salt, 256);
assertTrue(keys.add(new Bytes(key.getBytes())));
}
}
@Test
public void testCostParameterAffectsKey() throws Exception {
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
String password = getRandomString(16);
byte[] salt = getRandomBytes(32);
Set<Bytes> keys = new HashSet<>();
for (int cost = 2; cost <= 256; cost *= 2) {
SecretKey key = kdf.deriveKey(password, salt, cost);
assertTrue(keys.add(new Bytes(key.getBytes())));
}
}
@Test
public void testCalibration() throws Exception {
Clock clock = new ArrayClock(
0, 50, // Duration for cost 256
0, 100, // Duration for cost 512
0, 200, // Duration for cost 1024
0, 400, // Duration for cost 2048
0, 800 // Duration for cost 4096
);
PasswordBasedKdf kdf = new ScryptKdf(clock);
assertEquals(4096, kdf.chooseCostParameter());
}
@Test
public void testCalibrationChoosesMinCost() throws Exception {
Clock clock = new ArrayClock(
0, 2000 // Duration for cost 256 is already too high
);
PasswordBasedKdf kdf = new ScryptKdf(clock);
assertEquals(256, kdf.chooseCostParameter());
}
private static class ArrayClock implements Clock {
private final long[] times;
private int index = 0;
private ArrayClock(long... times) {
this.times = times;
}
@Override
public long currentTimeMillis() {
return times[index++];
}
@Override
public void sleep(long milliseconds) throws InterruptedException {
Thread.sleep(milliseconds);
}
}
}

View File

@@ -32,7 +32,7 @@ public abstract class SignatureTest extends BrambleTestCase {
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
SignatureTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
KeyPair k = generateKeyPair();
publicKey = k.getPublic().getEncoded();
privateKey = k.getPrivate().getEncoded();

View File

@@ -1,6 +0,0 @@
package org.briarproject.bramble.db;
interface BenchmarkTask<T> {
void run(T context) throws Exception;
}

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.db.NoSuchTransportException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.identity.event.LocalAuthorAddedEvent;
import org.briarproject.bramble.api.identity.event.LocalAuthorRemovedEvent;
@@ -47,20 +48,18 @@ import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -69,9 +68,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -89,7 +85,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final ClientId clientId;
private final GroupId groupId;
private final Group group;
private final AuthorId authorId;
private final Author author;
private final AuthorId localAuthorId;
private final LocalAuthor localAuthor;
private final MessageId messageId, messageId1;
private final int size;
@@ -102,15 +100,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final Contact contact;
public DatabaseComponentImplTest() {
clientId = new ClientId(getRandomString(123));
clientId = new ClientId(StringUtils.getRandomString(5));
groupId = new GroupId(TestUtils.getRandomId());
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
group = new Group(groupId, clientId, descriptor);
author = getAuthor();
localAuthor = getLocalAuthor();
authorId = new AuthorId(TestUtils.getRandomId());
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
localAuthorId = new AuthorId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
localAuthor = new LocalAuthor(localAuthorId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
messageId = new MessageId(TestUtils.getRandomId());
messageId1 = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
size = 1234;
raw = new byte[size];
message = new Message(messageId, groupId, timestamp, raw);
@@ -119,8 +120,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transportId = new TransportId("id");
maxLatency = Integer.MAX_VALUE;
contactId = new ContactId(234);
contact = new Contact(contactId, author, localAuthor.getId(),
true, true);
contact = new Contact(contactId, author, localAuthorId, true, true);
}
private DatabaseComponent createDatabaseComponent(Database<Object> database,
@@ -134,7 +134,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
int shutdownHandle = 12345;
context.checking(new Expectations() {{
// open()
oneOf(database).open(null);
oneOf(database).open();
will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle));
@@ -142,27 +142,25 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).startTransaction();
will(returnValue(txn));
// registerLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
// addContact()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(false));
oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(false));
oneOf(database).addContact(txn, author, localAuthor.getId(),
true, true);
oneOf(database).addContact(txn, author, localAuthorId, true, true);
will(returnValue(contactId));
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(
ContactStatusChangedEvent.class)));
// getContacts()
oneOf(database).getContacts(txn);
will(returnValue(singletonList(contact)));
will(returnValue(Collections.singletonList(contact)));
// addGroup()
oneOf(database).containsGroup(txn, groupId);
will(returnValue(false));
@@ -173,12 +171,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
// getGroups()
oneOf(database).getGroups(txn, clientId);
will(returnValue(singletonList(group)));
will(returnValue(Collections.singletonList(group)));
// removeGroup()
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(emptyMap()));
will(returnValue(Collections.emptyList()));
oneOf(database).removeGroup(txn, groupId);
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
oneOf(eventBus).broadcast(with(any(
@@ -189,9 +187,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).removeContact(txn, contactId);
oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
// removeLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).removeLocalAuthor(txn, localAuthor.getId());
oneOf(database).removeLocalAuthor(txn, localAuthorId);
oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
// endTransaction()
oneOf(database).commitTransaction(txn);
@@ -201,21 +199,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
assertFalse(db.open(null));
assertFalse(db.open());
Transaction transaction = db.startTransaction(false);
try {
db.addLocalAuthor(transaction, localAuthor);
assertEquals(contactId, db.addContact(transaction, author,
localAuthor.getId(), true, true));
assertEquals(singletonList(contact),
assertEquals(contactId,
db.addContact(transaction, author, localAuthorId, true,
true));
assertEquals(Collections.singletonList(contact),
db.getContacts(transaction));
db.addGroup(transaction, group); // First time - listeners called
db.addGroup(transaction, group); // Second time - not called
assertEquals(singletonList(group),
assertEquals(Collections.singletonList(group),
db.getGroups(transaction, clientId));
db.removeGroup(transaction, group);
db.removeContact(transaction, contactId);
db.removeLocalAuthor(transaction, localAuthor.getId());
db.removeLocalAuthor(transaction, localAuthorId);
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
@@ -256,8 +255,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).addMessage(txn, message, DELIVERED, true);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
oneOf(database).commitTransaction(txn);
// The message was added, so the listeners should be called
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
@@ -393,7 +397,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
Ack a = new Ack(singletonList(messageId));
Ack a = new Ack(Collections.singletonList(messageId));
db.receiveAck(transaction, contactId, a);
fail();
} catch (NoSuchContactException expected) {
@@ -414,7 +418,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
Offer o = new Offer(singletonList(messageId));
Offer o = new Offer(Collections.singletonList(messageId));
db.receiveOffer(transaction, contactId, o);
fail();
} catch (NoSuchContactException expected) {
@@ -425,7 +429,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
Request r = new Request(singletonList(messageId));
Request r = new Request(Collections.singletonList(messageId));
db.receiveRequest(transaction, contactId, r);
fail();
} catch (NoSuchContactException expected) {
@@ -483,8 +487,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Check whether the pseudonym is in the DB (which it's not)
exactly(3).of(database).startTransaction();
will(returnValue(txn));
exactly(3).of(database).containsLocalAuthor(txn,
localAuthor.getId());
exactly(3).of(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
exactly(3).of(database).abortTransaction(txn);
}});
@@ -493,7 +496,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addContact(transaction, author, localAuthor.getId(), true, true);
db.addContact(transaction, author, localAuthorId, true, true);
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
@@ -503,7 +506,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
db.getLocalAuthor(transaction, localAuthor.getId());
db.getLocalAuthor(transaction, localAuthorId);
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
@@ -513,7 +516,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
db.removeLocalAuthor(transaction, localAuthor.getId());
db.removeLocalAuthor(transaction, localAuthorId);
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
@@ -756,20 +759,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).startTransaction();
will(returnValue(txn));
// registerLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
// addContact()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(false));
oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(false));
oneOf(database).addContact(txn, author, localAuthor.getId(),
true, true);
oneOf(database).addContact(txn, author, localAuthorId, true, true);
will(returnValue(contactId));
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(
@@ -791,8 +792,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addLocalAuthor(transaction, localAuthor);
assertEquals(contactId, db.addContact(transaction, author,
localAuthor.getId(), true, true));
assertEquals(contactId,
db.addContact(transaction, author, localAuthorId, true,
true));
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
@@ -1020,7 +1022,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
Ack a = new Ack(singletonList(messageId));
Ack a = new Ack(Collections.singletonList(messageId));
db.receiveAck(transaction, contactId, a);
db.commitTransaction(transaction);
} finally {
@@ -1040,7 +1042,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(VISIBLE));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
oneOf(database).addMessage(txn, message, UNKNOWN, false);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, true, true);
// Second time
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -1190,7 +1197,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
Request r = new Request(singletonList(messageId));
Request r = new Request(Collections.singletonList(messageId));
db.receiveRequest(transaction, contactId, r);
db.commitTransaction(transaction);
} finally {
@@ -1199,11 +1206,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
@Test
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
throws Exception {
AtomicReference<GroupVisibilityUpdatedEvent> event =
new AtomicReference<>();
public void testChangingVisibilityCallsListeners() throws Exception {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
@@ -1212,13 +1215,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(INVISIBLE));
will(returnValue(INVISIBLE)); // Not yet visible
oneOf(database).addGroupVisibility(txn, contactId, groupId, false);
oneOf(database).getMessageIds(txn, groupId);
will(returnValue(Collections.singletonList(messageId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(
GroupVisibilityUpdatedEvent.class)));
will(new CaptureArgumentAction<>(event,
GroupVisibilityUpdatedEvent.class, 0));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
@@ -1230,48 +1236,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally {
db.endTransaction(transaction);
}
GroupVisibilityUpdatedEvent e = event.get();
assertNotNull(e);
assertEquals(singletonList(contactId), e.getAffectedContacts());
}
@Test
public void testChangingVisibilityFromVisibleToInvisibleCallsListeners()
throws Exception {
AtomicReference<GroupVisibilityUpdatedEvent> event =
new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(VISIBLE));
oneOf(database).removeGroupVisibility(txn, contactId, groupId);
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(
GroupVisibilityUpdatedEvent.class)));
will(new CaptureArgumentAction<>(event,
GroupVisibilityUpdatedEvent.class, 0));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
Transaction transaction = db.startTransaction(false);
try {
db.setGroupVisibility(transaction, contactId, groupId, INVISIBLE);
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
}
GroupVisibilityUpdatedEvent e = event.get();
assertNotNull(e);
assertEquals(singletonList(contactId), e.getAffectedContacts());
}
@Test
@@ -1303,8 +1267,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test
public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys();
Map<ContactId, TransportKeys> keys =
singletonMap(contactId, transportKeys);
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
contactId, transportKeys);
context.checking(new Expectations() {{
// startTransaction()
oneOf(database).startTransaction();
@@ -1442,10 +1406,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
// Contact is a local identity
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(true));
oneOf(database).abortTransaction(txn);
}});
@@ -1455,7 +1419,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addContact(transaction, author, localAuthor.getId(), true, true);
db.addContact(transaction, author, localAuthorId, true, true);
fail();
} catch (ContactExistsException expected) {
// Expected
@@ -1469,13 +1433,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(false));
// Contact already exists for this local identity
oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(true));
oneOf(database).abortTransaction(txn);
}});
@@ -1485,7 +1448,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addContact(transaction, author, localAuthor.getId(), true, true);
db.addContact(transaction, author, localAuthorId, true, true);
fail();
} catch (ContactExistsException expected) {
// Expected
@@ -1501,7 +1464,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
context.checking(new Expectations() {{
// open()
oneOf(database).open(null);
oneOf(database).open();
will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle));
@@ -1513,8 +1476,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).addMessage(txn, message, DELIVERED, true);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
// addMessageDependencies()
oneOf(database).containsMessage(txn, messageId);
will(returnValue(true));
@@ -1543,7 +1511,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
assertFalse(db.open(null));
assertFalse(db.open());
Transaction transaction = db.startTransaction(false);
try {
db.addLocalMessage(transaction, message, metadata, true);

View File

@@ -1,233 +0,0 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestDatabaseConfig;
import org.briarproject.bramble.test.TestUtils;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.sql.Connection;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.JdbcDatabase.CODE_SCHEMA_VERSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@NotNullByDefault
public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
private final File testDir = TestUtils.getTestDirectory();
@SuppressWarnings("unchecked")
private final Migration<Connection> migration =
context.mock(Migration.class, "migration");
@SuppressWarnings("unchecked")
private final Migration<Connection> migration1 =
context.mock(Migration.class, "migration1");
protected final DatabaseConfig config =
new TestDatabaseConfig(testDir, 1024 * 1024);
protected final Clock clock = new SystemClock();
abstract Database<Connection> createDatabase(
List<Migration<Connection>> migrations) throws Exception;
@Before
public void setUp() {
assertTrue(testDir.mkdirs());
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
@Test
public void testDoesNotRunMigrationsWhenCreatingDatabase()
throws Exception {
Database<Connection> db = createDatabase(singletonList(migration));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close();
}
@Test(expected = DbException.class)
public void testThrowsExceptionIfDataSchemaVersionIsMissing()
throws Exception {
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version
setDataSchemaVersion(db, -1);
db.close();
// Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1));
db.open(null);
}
@Test
public void testDoesNotRunMigrationsIfSchemaVersionsMatch()
throws Exception {
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close();
// Reopen the DB - migrations should not be run
db = createDatabase(asList(migration, migration1));
assertTrue(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close();
}
@Test(expected = DataTooNewException.class)
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
db.close();
// Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1));
db.open(null);
}
@Test(expected = DataTooOldException.class)
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoMigrations()
throws Exception {
// Open the DB for the first time
Database<Connection> db = createDatabase(emptyList());
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
db.close();
// Reopen the DB - an exception should be thrown
db = createDatabase(emptyList());
db.open(null);
}
@Test(expected = DataTooOldException.class)
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoSuitableMigration()
throws Exception {
context.checking(new Expectations() {{
oneOf(migration).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 2));
oneOf(migration).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION));
}});
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
db.close();
// Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1));
db.open(null);
}
@Test
public void testRunsMigrationIfCodeIsNewerThanDataAndSuitableMigration()
throws Exception {
context.checking(new Expectations() {{
// First migration should be run, increasing schema version by 2
oneOf(migration).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 2));
oneOf(migration).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION));
oneOf(migration).migrate(with(any(Connection.class)));
// Second migration is not suitable and should be skipped
oneOf(migration1).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION));
}});
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close();
// Reopen the DB - the first migration should be run
db = createDatabase(asList(migration, migration1));
assertTrue(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close();
}
@Test
public void testRunsMigrationsIfCodeIsNewerThanDataAndSuitableMigrations()
throws Exception {
context.checking(new Expectations() {{
// First migration should be run, incrementing schema version
oneOf(migration).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 2));
oneOf(migration).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration).migrate(with(any(Connection.class)));
// Second migration should be run, incrementing schema version again
oneOf(migration1).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION));
oneOf(migration1).migrate(with(any(Connection.class)));
}});
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close();
// Reopen the DB - both migrations should be run
db = createDatabase(asList(migration, migration1));
assertTrue(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close();
}
private int getDataSchemaVersion(Database<Connection> db)
throws Exception {
Connection txn = db.startTransaction();
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
db.commitTransaction(txn);
return s.getInt(SCHEMA_VERSION_KEY, -1);
}
private void setDataSchemaVersion(Database<Connection> db, int version)
throws Exception {
Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, version);
Connection txn = db.startTransaction();
db.mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
db.commitTransaction(txn);
}
}

View File

@@ -1,89 +0,0 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.TestDatabaseConfig;
import org.briarproject.bramble.test.UTest;
import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getMean;
import static org.briarproject.bramble.test.TestUtils.getMedian;
import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
import static org.briarproject.bramble.test.UTest.Z_CRITICAL_0_01;
public abstract class DatabasePerformanceComparisonTest
extends DatabasePerformanceTest {
/**
* How many blocks of each condition to compare.
*/
private static final int COMPARISON_BLOCKS = 10;
abstract Database<Connection> createDatabase(boolean conditionA,
DatabaseConfig databaseConfig, Clock clock);
@Override
protected void benchmark(String name,
BenchmarkTask<Database<Connection>> task) throws Exception {
List<Double> aDurations = new ArrayList<>();
List<Double> bDurations = new ArrayList<>();
boolean aFirst = true;
for (int i = 0; i < COMPARISON_BLOCKS; i++) {
// Alternate between running the A and B benchmarks first
if (aFirst) {
aDurations.addAll(benchmark(true, task).durations);
bDurations.addAll(benchmark(false, task).durations);
} else {
bDurations.addAll(benchmark(false, task).durations);
aDurations.addAll(benchmark(true, task).durations);
}
aFirst = !aFirst;
}
// Compare the results using a small P value, which increases our
// chance of getting an inconclusive result, making this a conservative
// test for performance differences
UTest.Result comparison = UTest.test(aDurations, bDurations,
Z_CRITICAL_0_01);
writeResult(name, aDurations, bDurations, comparison);
}
private SteadyStateResult benchmark(boolean conditionA,
BenchmarkTask<Database<Connection>> task) throws Exception {
deleteTestDirectory(testDir);
Database<Connection> db = openDatabase(conditionA);
populateDatabase(db);
db.close();
db = openDatabase(conditionA);
// Measure blocks of iterations until we reach a steady state
SteadyStateResult result = measureSteadyState(db, task);
db.close();
return result;
}
private Database<Connection> openDatabase(boolean conditionA)
throws DbException {
Database<Connection> db = createDatabase(conditionA,
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(null);
return db;
}
private void writeResult(String name, List<Double> aDurations,
List<Double> bDurations, UTest.Result comparison)
throws IOException {
String result = String.format("%s\t%,d\t%,d\t%,d\t%,d\t%,d\t%,d\t%s",
name, (long) getMean(aDurations), (long) getMedian(aDurations),
(long) getStandardDeviation(aDurations),
(long) getMean(bDurations), (long) getMedian(bDurations),
(long) getStandardDeviation(bDurations),
comparison.name());
writeResult(result);
}
}

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