Merged changes from the afsnit repo.

The project is now built as an Android project (via Eclipse or
ant). Tests have been moved to a separate project so they can exist
outside the Android build process. A basic Android app structure has
been created. A Bluetooth plugin for Android has been added, and the
Bluetooth plugin for J2SE has been modified to use the same techniques.
This commit is contained in:
akwizgran
2012-10-30 22:10:38 +00:00
parent a66da73d37
commit 2f7e2e16cf
132 changed files with 1651 additions and 11702 deletions

View File

@@ -1,25 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding=".gitignore|build.xml" kind="src" path="src"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="lib" path="libs/activation.jar"/>
<classpathentry kind="lib" path="libs/bluecove-2.1.0-briar.jar"/>
<classpathentry kind="lib" path="libs/bluecove-gpl-2.1.0.jar"/>
<classpathentry kind="lib" path="libs/commons-io-2.0.1.jar"/>
<classpathentry kind="lib" path="libs/guice-3.0-no_aop.jar"/>
<classpathentry kind="lib" path="libs/h2small-1.3.161.jar"/>
<classpathentry kind="lib" path="libs/javax.inject-1.jar"/>
<classpathentry kind="lib" path="libs/jna.jar"/>
<classpathentry kind="lib" path="libs/jnotify-0.93.jar"/>
<classpathentry kind="lib" path="libs/mail.jar"/>
<classpathentry kind="lib" path="libs/platform.jar"/>
<classpathentry kind="lib" path="libs/sc-light-jdk15on-20120824.jar"/>
<classpathentry kind="lib" path="libs/scprov-jdk15on-20120824.jar"/>
<classpathentry kind="lib" path="libs/silvertunnel.org_netlib.jar"/>
<classpathentry kind="lib" path="libs/test/hamcrest-core-1.1.jar"/>
<classpathentry kind="lib" path="libs/test/hamcrest-library-1.1.jar"/>
<classpathentry kind="lib" path="libs/test/jmock-2.5.1.jar"/>
<classpathentry kind="lib" path="libs/test/junit-4.9b3.jar"/>
<classpathentry kind="output" path="bin"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

6
.gitignore vendored
View File

@@ -1,3 +1,3 @@
/windows-jre
/Briar
/bin
bin
gen
local.properties

View File

@@ -5,13 +5,29 @@
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

24
AndroidManifest.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sf.briar"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application android:label="@string/app_name" >
<activity
android:name=".Briar"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".BriarService" android:exported="false" >
<intent-filter>
<action android:name="net.sf.briar.BriarService" />
</intent-filter>
</service>
</application>
</manifest>

BIN
android.jar Normal file

Binary file not shown.

17
ant.properties Normal file
View File

@@ -0,0 +1,17 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked into Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.

View File

@@ -1,37 +0,0 @@
<project name='build-common'>
<import file='dependencies.xml'/>
<dirname property='build-common.root' file='${ant.file.build-common}'/>
<fileset id='bundled-jars' dir='${build-common.root}/libs'>
<include name='*.jar'/>
</fileset>
<fileset id='test-jars' dir='${build-common.root}/libs/test'>
<include name='*.jar'/>
</fileset>
<path id='classes'>
<pathelement location='${build-common.root}/src/build'/>
</path>
<path id='test-classes'>
<pathelement location='${build-common.root}/test/build'/>
</path>
<target name='clean'>
<delete dir='build'/>
<delete dir='test.tmp'/>
</target>
<target name='compile'>
<mkdir dir='build'/>
<javac srcdir='net/sf/briar' destdir='build' source='1.5'
includeantruntime='false' debug='on'>
<classpath>
<fileset refid='bundled-jars'/>
<fileset refid='test-jars'/>
<path refid='classes'/>
</classpath>
</javac>
</target>
<target name='depend'>
<antcall target='depend.${ant.project.name}'/>
</target>
<target name='depend-clean'>
<antcall target='depend-clean.${ant.project.name}'/>
</target>
</project>

104
build.xml
View File

@@ -1,14 +1,92 @@
<project name='all' default='depend'>
<import file='build-common.xml'/>
<target name='doc'>
<delete dir='javadoc'/>
<javadoc destdir='javadoc' windowtitle='Briar'>
<classpath>
<fileset refid='bundled-jars'/>
</classpath>
<fileset dir='.' defaultexcludes='yes'>
<include name='src/net/sf/briar/**'/>
</fileset>
</javadoc>
</target>
<?xml version="1.0" encoding="UTF-8"?>
<project name="Briar" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@@ -1,21 +0,0 @@
<project name='dependencies'>
<dirname property='depend.root' file='${ant.file.dependencies}'/>
<target name='depend.all' depends='depend.src'/>
<target name='depend.src'>
<ant dir='${depend.root}/src' target='compile'
inheritAll='false'/>
</target>
<target name='depend.test' depends='depend.src'>
<ant dir='${depend.root}/test' target='compile'
inheritAll='false'/>
</target>
<target name='depend-clean.all' depends='depend-clean.src'/>
<target name='depend-clean.src'>
<ant dir='${depend.root}/src' target='clean'
inheritAll='false'/>
</target>
<target name='depend-clean.test' depends='depend-clean.src'>
<ant dir='${depend.root}/test' target='clean'
inheritAll='false'/>
</target>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

20
proguard-project.txt Normal file
View File

@@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

14
project.properties Normal file
View File

@@ -0,0 +1,14 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-16

4
res/values/strings.xml Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Briar</string>
</resources>

2
src/.gitignore vendored
View File

@@ -1 +1 @@
/build
build

View File

@@ -1,3 +1,25 @@
<project name='api' default='depend'>
<import file='../build-common.xml'/>
<project name='prototype' default='compile'>
<fileset id='prototype-jars' dir='../libs'>
<include name='*.jar'/>
</fileset>
<path id='android-jar'>
<pathelement location='../android.jar'/>
</path>
<path id='prototype-classes'>
<pathelement location='../build'/>
</path>
<target name='clean'>
<delete dir='../build'/>
</target>
<target name='compile'>
<mkdir dir='../build'/>
<javac srcdir='net/sf/briar' destdir='../build' source='1.5'
includeantruntime='false' debug='off'>
<classpath>
<fileset refid='prototype-jars'/>
<path refid='android-jar'/>
<path refid='prototype-classes'/>
</classpath>
</javac>
</target>
</project>

View File

@@ -0,0 +1,19 @@
package net.sf.briar;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
public class HelloWorldActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView text = new TextView(this);
text.setText("Hello world");
setContentView(text);
Intent intent = new Intent("net.sf.briar.AfsnitService");
startService(intent);
}
}

View File

@@ -0,0 +1,59 @@
package net.sf.briar;
import static android.content.Context.MODE_PRIVATE;
import java.io.File;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.ui.UiCallback;
import android.content.Context;
import com.google.inject.AbstractModule;
public class HelloWorldModule extends AbstractModule {
private final DatabaseConfig config;
private final UiCallback callback;
public HelloWorldModule(final Context appContext) {
final Password password = new Password() {
public char[] getPassword() {
return "foo bar".toCharArray();
}
};
config = new DatabaseConfig() {
public File getDataDirectory() {
return appContext.getDir("db", MODE_PRIVATE);
}
public Password getPassword() {
return password;
}
public long getMaxSize() {
return Long.MAX_VALUE;
}
};
callback = new UiCallback() {
public int showChoice(String[] options, String... message) {
return -1;
}
public boolean showConfirmationMessage(String... message) {
return false;
}
public void showMessage(String... message) {}
};
}
@Override
protected void configure() {
bind(DatabaseConfig.class).toInstance(config);
bind(UiCallback.class).toInstance(callback);
}
}

View File

@@ -0,0 +1,95 @@
package net.sf.briar;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.briar.android.AndroidModule;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.clock.ClockModule;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.db.DatabaseModule;
import net.sf.briar.lifecycle.LifecycleModule;
import net.sf.briar.plugins.PluginsModule;
import net.sf.briar.protocol.ProtocolModule;
import net.sf.briar.protocol.duplex.DuplexProtocolModule;
import net.sf.briar.protocol.simplex.SimplexProtocolModule;
import net.sf.briar.serial.SerialModule;
import net.sf.briar.transport.TransportModule;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class HelloWorldService extends Service implements Runnable {
private static final Logger LOG =
Logger.getLogger(HelloWorldService.class.getName());
private DatabaseComponent db = null;
private KeyManager keyManager = null;
private PluginManager pluginManager = null;
@Override
public void onCreate() {
Thread t = new Thread(this);
t.setDaemon(false);
t.start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return 0;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void run() {
Injector i = Guice.createInjector(
new HelloWorldModule(getApplicationContext()),
new AndroidModule(), new ClockModule(), new CryptoModule(),
new DatabaseModule(), new LifecycleModule(),
new PluginsModule(), new ProtocolModule(),
new DuplexProtocolModule(), new SimplexProtocolModule(),
new SerialModule(), new TransportModule());
db = i.getInstance(DatabaseComponent.class);
keyManager = i.getInstance(KeyManager.class);
pluginManager = i.getInstance(PluginManager.class);
try {
// Start...
if(LOG.isLoggable(Level.INFO)) LOG.info("Starting");
db.open(false);
if(LOG.isLoggable(Level.INFO)) LOG.info("Database opened");
keyManager.start();
if(LOG.isLoggable(Level.INFO)) LOG.info("Key manager started");
int pluginsStarted = pluginManager.start(this);
if(LOG.isLoggable(Level.INFO))
LOG.info(pluginsStarted + " plugins started");
// ...sleep...
try {
Thread.sleep(1000);
} catch(InterruptedException ignored) {}
// ...and stop
if(LOG.isLoggable(Level.INFO)) LOG.info("Shutting down");
int pluginsStopped = pluginManager.stop();
if(LOG.isLoggable(Level.INFO))
LOG.info(pluginsStopped + " plugins stopped");
keyManager.stop();
if(LOG.isLoggable(Level.INFO)) LOG.info("Key manager stopped");
db.close();
if(LOG.isLoggable(Level.INFO)) LOG.info("Database closed");
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
}

View File

@@ -0,0 +1,89 @@
package net.sf.briar.android;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import net.sf.briar.api.android.AndroidExecutor;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.google.inject.Inject;
class AndroidExecutorImpl implements AndroidExecutor {
private static final int SHUTDOWN = 0, RUNNABLE = 1, CALLABLE = 2;
private final Runnable loop;
private final AtomicBoolean started = new AtomicBoolean(false);
private final CountDownLatch startLatch = new CountDownLatch(1);
private volatile Handler handler = null;
@Inject
AndroidExecutorImpl() {
loop = new Runnable() {
public void run() {
Looper.prepare();
handler = new FutureTaskHandler();
startLatch.countDown();
Looper.loop();
}
};
}
private void startIfNecessary() {
if(started.getAndSet(true)) return;
new Thread(loop).start();
try {
startLatch.await();
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public Future<Void> submit(Runnable r) {
startIfNecessary();
Future<Void> f = new FutureTask<Void>(r, null);
Message m = Message.obtain(handler, RUNNABLE, f);
handler.sendMessage(m);
return f;
}
public <V> Future<V> submit(Callable<V> c) {
startIfNecessary();
Future<V> f = new FutureTask<V>(c);
Message m = Message.obtain(handler, RUNNABLE, f);
handler.sendMessage(m);
return f;
}
public void shutdown() {
if(handler != null) {
Message m = Message.obtain(handler, SHUTDOWN);
handler.sendMessage(m);
}
}
private static class FutureTaskHandler extends Handler {
@Override
public void handleMessage(Message m) {
switch(m.what) {
case SHUTDOWN:
Looper.myLooper().quit();
break;
case RUNNABLE:
case CALLABLE:
((FutureTask<?>) m.obj).run();
break;
default:
throw new IllegalArgumentException();
}
}
}
}

View File

@@ -0,0 +1,13 @@
package net.sf.briar.android;
import net.sf.briar.api.android.AndroidExecutor;
import com.google.inject.AbstractModule;
public class AndroidModule extends AbstractModule {
@Override
protected void configure() {
bind(AndroidExecutor.class).to(AndroidExecutorImpl.class);
}
}

View File

@@ -0,0 +1,17 @@
package net.sf.briar.api.android;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
/**
* Enables background threads to make Android API calls that must be made from
* a thread with a message queue.
*/
public interface AndroidExecutor {
Future<Void> submit(Runnable r);
<V> Future<V> submit(Callable<V> c);
void shutdown();
}

View File

@@ -1,9 +1,9 @@
package net.sf.briar.api.crypto;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.db.ContactTransport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ContactTransport;
public interface KeyManager {

View File

@@ -23,6 +23,8 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.api.transport.ContactTransport;
import net.sf.briar.api.transport.TemporarySecret;
/**
* Encapsulates the database implementation and exposes high-level operations
@@ -152,6 +154,19 @@ public interface DatabaseComponent {
long incrementConnectionCounter(ContactId c, TransportId t, long period)
throws DbException;
/**
* Merges the given configuration with existing configuration for the
* given transport.
*/
void mergeConfig(TransportId t, TransportConfig c) throws DbException;
/**
* Merges the given properties with the existing local properties for the
* given transport.
*/
void mergeLocalProperties(TransportId t, TransportProperties p)
throws DbException;
/** Processes an acknowledgement from the given contact. */
void receiveAck(ContactId c, Ack a) throws DbException;
@@ -179,12 +194,6 @@ public interface DatabaseComponent {
/** Removes a contact (and all associated state) from the database. */
void removeContact(ContactId c) throws DbException;
/**
* Sets the configuration for the given transport, replacing any existing
* configuration for that transport.
*/
void setConfig(TransportId t, TransportConfig c) throws DbException;
/**
* Sets the connection reordering window for the given contact transport
* in the given rotation period.
@@ -192,13 +201,6 @@ public interface DatabaseComponent {
void setConnectionWindow(ContactId c, TransportId t, long period,
long centre, byte[] bitmap) throws DbException;
/**
* Sets the local transport properties for the given transport, replacing
* any existing properties for that transport.
*/
void setLocalProperties(TransportId t, TransportProperties p)
throws DbException;
/** Records the user's rating for the given author. */
void setRating(AuthorId a, Rating r) throws DbException;

View File

@@ -0,0 +1,14 @@
package net.sf.briar.api.db;
import java.io.File;
import net.sf.briar.api.crypto.Password;
public interface DatabaseConfig {
File getDataDirectory();
Password getPassword();
long getMaxSize();
}

View File

@@ -1,15 +0,0 @@
package net.sf.briar.api.db;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
/** Annotation for injecting the directory where the database is stored. */
@BindingAnnotation
@Target({ PARAMETER })
@Retention(RUNTIME)
public @interface DatabaseDirectory {}

View File

@@ -1,15 +0,0 @@
package net.sf.briar.api.db;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
/** Annotation for injecting the maximum size in bytes of the database. */
@BindingAnnotation
@Target({ PARAMETER })
@Retention(RUNTIME)
public @interface DatabaseMaxSize {}

View File

@@ -1,18 +0,0 @@
package net.sf.briar.api.db;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
/**
* Annotation for injecting the password from which the database encryption
* key is derived.
*/
@BindingAnnotation
@Target({ PARAMETER })
@Retention(RUNTIME)
public @interface DatabasePassword {}

View File

@@ -0,0 +1,7 @@
package net.sf.briar.api.db;
/** Thrown when a database operation is attempted and the database is closed. */
public class DbClosedException extends DbException {
private static final long serialVersionUID = -3679248177625310653L;
}

View File

@@ -21,11 +21,13 @@ public interface PluginCallback {
/** Returns the plugin's remote transport properties. */
Map<ContactId, TransportProperties> getRemoteProperties();
/** Stores the plugin's configuration. */
void setConfig(TransportConfig c);
/** Merges the given configuration with the plugin's configuration. */
void mergeConfig(TransportConfig c);
/** Stores the plugin's local transport properties. */
void setLocalProperties(TransportProperties p);
/**
* Merges the given properties with the plugin's local transport properties.
*/
void mergeLocalProperties(TransportProperties p);
/**
* Presents the user with a choice among two or more named options and

View File

@@ -3,6 +3,7 @@ package net.sf.briar.api.plugins;
import java.util.Collection;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import android.content.Context;
public interface PluginManager {
@@ -11,7 +12,7 @@ public interface PluginManager {
* started. This method must not be called until the database has been
* opened.
*/
int start();
int start(Context context);
/**
* Stops the plugins and returns the number of plugins successfully stopped.

View File

@@ -2,8 +2,13 @@ package net.sf.briar.api.plugins.duplex;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import android.content.Context;
public interface DuplexPluginFactory {
DuplexPlugin createPlugin(Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
DuplexPluginCallback callback);
}

View File

@@ -2,8 +2,13 @@ package net.sf.briar.api.plugins.simplex;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import android.content.Context;
public interface SimplexPluginFactory {
SimplexPlugin createPlugin(Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
SimplexPluginCallback callback);
}

View File

@@ -2,7 +2,6 @@ package net.sf.briar.api.transport;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.protocol.TransportId;
/**

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.db;
package net.sf.briar.api.transport;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.protocol.TransportId;

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.db;
package net.sf.briar.api.transport;
import static net.sf.briar.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE;
import net.sf.briar.api.ContactId;

View File

@@ -8,11 +8,8 @@ import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.ContactTransport;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.Status;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
@@ -21,6 +18,8 @@ import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ContactTransport;
import net.sf.briar.api.transport.TemporarySecret;
/**
* A low-level interface to the database (DatabaseComponent provides a
@@ -475,6 +474,24 @@ interface Database<T> {
long incrementConnectionCounter(T txn, ContactId c, TransportId t,
long period) throws DbException;
/**
* Merges the given configuration with the existing configuration for the
* given transport.
* <p>
* Locking: transport write.
*/
void mergeConfig(T txn, TransportId t, TransportConfig config)
throws DbException;
/**
* Merges the given properties with the existing local properties for the
* given transport.
* <p>
* Locking: transport write.
*/
void mergeLocalProperties(T txn, TransportId t, TransportProperties p)
throws DbException;
/**
* Removes an outstanding batch that has been acknowledged. Any messages in
* the batch that are still considered outstanding (Status.SENT) with
@@ -544,15 +561,6 @@ interface Database<T> {
*/
void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
/**
* Sets the configuration for the given transport, replacing any existing
* configuration for that transport.
* <p>
* Locking: transport write.
*/
void setConfig(T txn, TransportId t, TransportConfig config)
throws DbException;
/**
* Sets the connection reordering window for the given contact transport in
* the given rotation period.
@@ -569,15 +577,6 @@ interface Database<T> {
*/
void setExpiryTime(T txn, ContactId c, long expiry) throws DbException;
/**
* Sets the local transport properties for the given transport, replacing
* any existing properties for that transport.
* <p>
* Locking: transport write.
*/
void setLocalProperties(T txn, TransportId t, TransportProperties p)
throws DbException;
/**
* Sets the user's rating for the given author.
* <p>

View File

@@ -5,6 +5,7 @@ import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.briar.api.db.DbClosedException;
import net.sf.briar.api.db.DbException;
class DatabaseCleanerImpl extends TimerTask implements DatabaseCleaner {
@@ -32,6 +33,8 @@ class DatabaseCleanerImpl extends TimerTask implements DatabaseCleaner {
if(callback.shouldCheckFreeSpace()) {
callback.checkFreeSpaceAndClean();
}
} catch(DbClosedException e) {
if(LOG.isLoggable(Level.INFO)) LOG.info("Database closed, exiting");
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
throw new Error(e); // Kill the application

View File

@@ -27,14 +27,11 @@ import net.sf.briar.api.Rating;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.ContactTransport;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.NoSuchContactTransportException;
import net.sf.briar.api.db.Status;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.db.event.BatchReceivedEvent;
import net.sf.briar.api.db.event.ContactAddedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent;
@@ -62,6 +59,8 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.api.transport.ContactTransport;
import net.sf.briar.api.transport.TemporarySecret;
import com.google.inject.Inject;
@@ -1006,6 +1005,47 @@ DatabaseCleaner.Callback {
}
}
public void mergeConfig(TransportId t, TransportConfig c)
throws DbException {
transportLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.mergeConfig(txn, t, c);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
}
public void mergeLocalProperties(TransportId t, TransportProperties p)
throws DbException {
boolean changed = false;
transportLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!p.equals(db.getLocalProperties(txn, t))) {
db.mergeLocalProperties(txn, t, p);
db.setTransportsModified(txn, clock.currentTimeMillis());
changed = true;
}
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
// Call the listeners outside the lock
if(changed) callListeners(new LocalTransportsUpdatedEvent());
}
public void receiveAck(ContactId c, Ack a) throws DbException {
contactLock.readLock().lock();
try {
@@ -1263,23 +1303,6 @@ DatabaseCleaner.Callback {
callListeners(new ContactRemovedEvent(c));
}
public void setConfig(TransportId t, TransportConfig c)
throws DbException {
transportLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.setConfig(txn, t, c);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
}
public void setConnectionWindow(ContactId c, TransportId t, long period,
long centre, byte[] bitmap) throws DbException {
contactLock.readLock().lock();
@@ -1304,30 +1327,6 @@ DatabaseCleaner.Callback {
}
}
public void setLocalProperties(TransportId t, TransportProperties p)
throws DbException {
boolean changed = false;
transportLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!p.equals(db.getLocalProperties(txn, t))) {
db.setLocalProperties(txn, t, p);
db.setTransportsModified(txn, clock.currentTimeMillis());
changed = true;
}
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
// Call the listeners outside the lock
if(changed) callListeners(new LocalTransportsUpdatedEvent());
}
public void setRating(AuthorId a, Rating r) throws DbException {
boolean changed;
messageLock.writeLock().lock();

View File

@@ -1,16 +1,12 @@
package net.sf.briar.db;
import java.io.File;
import java.sql.Connection;
import java.util.concurrent.Executor;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseDirectory;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DatabaseMaxSize;
import net.sf.briar.api.db.DatabasePassword;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.PacketFactory;
@@ -46,10 +42,9 @@ public class DatabaseModule extends AbstractModule {
}
@Provides
Database<Connection> getDatabase(@DatabaseDirectory File dir,
@DatabasePassword Password password, @DatabaseMaxSize long maxSize,
Database<Connection> getDatabase(DatabaseConfig config,
GroupFactory groupFactory, Clock clock) {
return new H2Database(dir, password, maxSize, groupFactory, clock);
return new H2Database(config, groupFactory, clock);
}
@Provides @Singleton

View File

@@ -10,13 +10,10 @@ import java.util.Properties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseDirectory;
import net.sf.briar.api.db.DatabaseMaxSize;
import net.sf.briar.api.db.DatabasePassword;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.protocol.GroupFactory;
import org.apache.commons.io.FileSystemUtils;
import net.sf.briar.util.FileUtils;
import com.google.inject.Inject;
@@ -29,22 +26,19 @@ class H2Database extends JdbcDatabase {
private static final String SECRET_TYPE = "BINARY(32)";
private final File home;
private final Password password;
private final String url;
private final Password password;
private final long maxSize;
@Inject
H2Database(@DatabaseDirectory File dir,
@DatabasePassword Password password,
@DatabaseMaxSize long maxSize,
GroupFactory groupFactory, Clock clock) {
H2Database(DatabaseConfig config, GroupFactory groupFactory, Clock clock) {
super(groupFactory, clock, HASH_TYPE, BINARY_TYPE, COUNTER_TYPE,
SECRET_TYPE);
home = new File(dir, "db");
this.password = password;
home = new File(config.getDataDirectory(), "db");
url = "jdbc:h2:split:" + home.getPath()
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
this.maxSize = maxSize;
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
password = config.getPassword();
maxSize = config.getMaxSize();
}
public void open(boolean resume) throws DbException, IOException {
@@ -63,8 +57,7 @@ class H2Database extends JdbcDatabase {
public long getFreeSpace() throws DbException {
try {
File dir = home.getParentFile();
String path = dir.getAbsolutePath();
long free = FileSystemUtils.freeSpaceKb(path) * 1024L;
long free = FileUtils.getFreeSpace(dir);
long used = getDiskSpace(dir);
long quota = maxSize - used;
long min = Math.min(free, quota);

View File

@@ -28,11 +28,9 @@ import net.sf.briar.api.Rating;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.ContactTransport;
import net.sf.briar.api.db.DbClosedException;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.Status;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
@@ -42,6 +40,8 @@ import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ContactTransport;
import net.sf.briar.api.transport.TemporarySecret;
import net.sf.briar.util.FileUtils;
/**
@@ -376,7 +376,7 @@ abstract class JdbcDatabase implements Database<Connection> {
public Connection startTransaction() throws DbException {
Connection txn = null;
synchronized(connections) {
if(closed) throw new DbException();
if(closed) throw new DbClosedException();
txn = connections.poll();
}
try {
@@ -1207,6 +1207,7 @@ abstract class JdbcDatabase implements Database<Connection> {
transports.add(t);
}
t.put(rs.getString(2), rs.getString(3));
lastId = id;
}
rs.close();
ps.close();
@@ -2340,28 +2341,52 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setConfig(Connection txn, TransportId t, TransportConfig c)
public void mergeConfig(Connection txn, TransportId t, TransportConfig c)
throws DbException {
mergeStringMap(txn, t, c, "transportConfigs");
}
public void mergeLocalProperties(Connection txn, TransportId t,
TransportProperties p) throws DbException {
mergeStringMap(txn, t, p, "transportProperties");
}
private void mergeStringMap(Connection txn, TransportId t,
Map<String, String> m, String tableName) throws DbException {
PreparedStatement ps = null;
try {
// Delete any existing config for the given transport
String sql = "DELETE FROM transportConfigs WHERE transportId = ?";
// Update any properties that already exist
String sql = "UPDATE " + tableName + " SET value = ?"
+ " WHERE transportId = ? AND key = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
ps.executeUpdate();
ps.close();
// Store the new config
sql = "INSERT INTO transportConfigs (transportId, key, value)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
for(Entry<String, String> e : c.entrySet()) {
ps.setString(2, e.getKey());
ps.setString(3, e.getValue());
ps.setBytes(2, t.getBytes());
for(Entry<String, String> e : m.entrySet()) {
ps.setString(1, e.getValue());
ps.setString(3, e.getKey());
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != c.size()) throw new DbStateException();
if(batchAffected.length != m.size()) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] > 1) throw new DbStateException();
}
// Insert any properties that don't already exist
sql = "INSERT INTO " + tableName + " (transportId, key, value)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
int updateIndex = 0, inserted = 0;
for(Entry<String, String> e : m.entrySet()) {
if(batchAffected[updateIndex] == 0) {
ps.setString(2, e.getKey());
ps.setString(3, e.getValue());
ps.addBatch();
inserted++;
}
updateIndex++;
}
batchAffected = ps.executeBatch();
if(batchAffected.length != inserted) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
@@ -2411,39 +2436,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setLocalProperties(Connection txn, TransportId t,
TransportProperties p) throws DbException {
PreparedStatement ps = null;
try {
// Delete any existing properties for the given transport
String sql = "DELETE FROM transportProperties"
+ " WHERE transportId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
ps.executeUpdate();
ps.close();
// Store the new properties
sql = "INSERT INTO transportProperties (transportId, key, value)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
for(Entry<String, String> e : p.entrySet()) {
ps.setString(2, e.getKey());
ps.setString(3, e.getValue());
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != p.size()) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public Rating setRating(Connection txn, AuthorId a, Rating r)
throws DbException {
PreparedStatement ps = null;

View File

@@ -1,7 +1,7 @@
package net.sf.briar.api.db;
package net.sf.briar.db;
/** The status of a message with respect to a particular contact. */
public enum Status {
enum Status {
/** The message has not been sent, received, or acked. */
NEW,
/** The message has been sent, but not received or acked. */

View File

@@ -1,8 +1,5 @@
package net.sf.briar.plugins;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -18,6 +15,7 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.plugins.Plugin;
@@ -36,6 +34,8 @@ import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionDispatcher;
import net.sf.briar.api.ui.UiCallback;
import net.sf.briar.util.OsUtils;
import android.content.Context;
import com.google.inject.Inject;
@@ -44,17 +44,25 @@ class PluginManagerImpl implements PluginManager {
private static final Logger LOG =
Logger.getLogger(PluginManagerImpl.class.getName());
private static final String[] SIMPLEX_PLUGIN_FACTORIES = new String[] {
private static final String[] ANDROID_SIMPLEX_FACTORIES = new String[0];
private static final String[] ANDROID_DUPLEX_FACTORIES = new String[] {
"net.sf.briar.plugins.droidtooth.DroidtoothPluginFactory",
"net.sf.briar.plugins.socket.SimpleSocketPluginFactory"
};
private static final String[] J2SE_SIMPLEX_FACTORIES = new String[] {
"net.sf.briar.plugins.file.RemovableDrivePluginFactory"
};
private static final String[] DUPLEX_PLUGIN_FACTORIES = new String[] {
private static final String[] J2SE_DUPLEX_FACTORIES = new String[] {
"net.sf.briar.plugins.bluetooth.BluetoothPluginFactory",
"net.sf.briar.plugins.socket.SimpleSocketPluginFactory",
"net.sf.briar.plugins.tor.TorPluginFactory"
};
private final ExecutorService pluginExecutor;
private final AndroidExecutor androidExecutor;
private final DatabaseComponent db;
private final Poller poller;
private final ConnectionDispatcher dispatcher;
@@ -64,9 +72,11 @@ class PluginManagerImpl implements PluginManager {
@Inject
PluginManagerImpl(@PluginExecutor ExecutorService pluginExecutor,
DatabaseComponent db, Poller poller,
ConnectionDispatcher dispatcher, UiCallback uiCallback) {
AndroidExecutor androidExecutor, DatabaseComponent db,
Poller poller, ConnectionDispatcher dispatcher,
UiCallback uiCallback) {
this.pluginExecutor = pluginExecutor;
this.androidExecutor = androidExecutor;
this.db = db;
this.poller = poller;
this.dispatcher = dispatcher;
@@ -75,17 +85,17 @@ class PluginManagerImpl implements PluginManager {
duplexPlugins = new ArrayList<DuplexPlugin>();
}
public synchronized int start() {
public synchronized int start(Context appContext) {
Set<TransportId> ids = new HashSet<TransportId>();
// Instantiate and start the simplex plugins
for(String s : SIMPLEX_PLUGIN_FACTORIES) {
for(String s : getSimplexPluginFactoryNames()) {
try {
Class<?> c = Class.forName(s);
SimplexPluginFactory factory =
(SimplexPluginFactory) c.newInstance();
SimplexCallback callback = new SimplexCallback();
SimplexPlugin plugin = factory.createPlugin(pluginExecutor,
callback);
androidExecutor, appContext, callback);
if(plugin == null) {
if(LOG.isLoggable(Level.INFO)) {
LOG.info(factory.getClass().getSimpleName()
@@ -111,14 +121,14 @@ class PluginManagerImpl implements PluginManager {
}
}
// Instantiate and start the duplex plugins
for(String s : DUPLEX_PLUGIN_FACTORIES) {
for(String s : getDuplexPluginFactoryNames()) {
try {
Class<?> c = Class.forName(s);
DuplexPluginFactory factory =
(DuplexPluginFactory) c.newInstance();
DuplexCallback callback = new DuplexCallback();
DuplexPlugin plugin = factory.createPlugin(pluginExecutor,
callback);
androidExecutor, appContext, callback);
if(plugin == null) {
if(LOG.isLoggable(Level.INFO)) {
LOG.info(factory.getClass().getSimpleName()
@@ -152,8 +162,20 @@ class PluginManagerImpl implements PluginManager {
return simplexPlugins.size() + duplexPlugins.size();
}
private String[] getSimplexPluginFactoryNames() {
if(OsUtils.isAndroid()) return ANDROID_SIMPLEX_FACTORIES;
return J2SE_SIMPLEX_FACTORIES;
}
private String[] getDuplexPluginFactoryNames() {
if(OsUtils.isAndroid()) return ANDROID_DUPLEX_FACTORIES;
return J2SE_DUPLEX_FACTORIES;
}
public synchronized int stop() {
int stopped = 0;
// Stop the poller
poller.stop();
// Stop the simplex plugins
for(SimplexPlugin plugin : simplexPlugins) {
try {
@@ -163,7 +185,6 @@ class PluginManagerImpl implements PluginManager {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
simplexPlugins.clear();
// Stop the duplex plugins
for(DuplexPlugin plugin : duplexPlugins) {
try {
@@ -173,11 +194,9 @@ class PluginManagerImpl implements PluginManager {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
duplexPlugins.clear();
// Stop the poller
poller.stop();
// Shut down the executor service
// Shut down the executors
pluginExecutor.shutdown();
androidExecutor.shutdown();
// Return the number of plugins successfully stopped
return stopped;
}
@@ -232,38 +251,19 @@ class PluginManagerImpl implements PluginManager {
}
}
public void setConfig(TransportConfig c) {
public void mergeConfig(TransportConfig c) {
assert id != null;
try {
db.setConfig(id, c);
db.mergeConfig(id, c);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
public void setLocalProperties(TransportProperties p) {
public void mergeLocalProperties(TransportProperties p) {
assert id != null;
if(p.size() > MAX_PROPERTIES_PER_TRANSPORT) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Plugin " + id + " set too many properties");
return;
}
for(String s : p.keySet()) {
if(s.length() > MAX_PROPERTY_LENGTH) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Plugin " + id + " set long key: " + s);
return;
}
}
for(String s : p.values()) {
if(s.length() > MAX_PROPERTY_LENGTH) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Plugin " + id + " set long value: " + s);
return;
}
}
try {
db.setLocalProperties(id, p);
db.mergeLocalProperties(id, p);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}

View File

@@ -1,54 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import javax.bluetooth.DataElement;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.UUID;
abstract class AbstractListener implements DiscoveryListener {
protected final DiscoveryAgent discoveryAgent;
protected final AtomicInteger searches = new AtomicInteger(1);
protected final CountDownLatch finished = new CountDownLatch(1);
protected AbstractListener(DiscoveryAgent discoveryAgent) {
this.discoveryAgent = discoveryAgent;
}
public void inquiryCompleted(int discoveryType) {
if(searches.decrementAndGet() == 0) finished.countDown();
}
public void serviceSearchCompleted(int transaction, int response) {
if(searches.decrementAndGet() == 0) finished.countDown();
}
protected Object getDataElementValue(Object o) {
if(o instanceof DataElement) {
// Bluecove throws an exception if the type is unknown
try {
return ((DataElement) o).getValue();
} catch(ClassCastException e) {
return null;
}
}
return null;
}
protected void findNestedClassIds(Object o, Collection<String> ids) {
o = getDataElementValue(o);
if(o instanceof Enumeration<?>) {
for(Object o1 : Collections.list((Enumeration<?>) o)) {
findNestedClassIds(o1, ids);
}
} else if(o instanceof UUID) {
ids.add(o.toString());
}
}
}

View File

@@ -1,19 +1,17 @@
package net.sf.briar.plugins.bluetooth;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static javax.bluetooth.DiscoveryAgent.GIAC;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -38,28 +36,29 @@ import net.sf.briar.util.StringUtils;
class BluetoothPlugin implements DuplexPlugin {
// Share an ID with the Android Bluetooth plugin
public static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
+ "00a539fd260f08a13a0d8a900cde5e49"
+ "1b4df2ffd42e40c408f2db7868f518aa");
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
+ "00a539fd260f08a13a0d8a900cde5e49"
+ "1b4df2ffd42e40c408f2db7868f518aa");
private static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
Logger.getLogger(BluetoothPlugin.class.getName());
private final Executor pluginExecutor;
private final Clock clock;
private final DuplexPluginCallback callback;
private final long pollingInterval;
private final Object discoveryLock = new Object();
private final Object localPropertiesLock = new Object();
private final ScheduledExecutorService scheduler;
private final Collection<StreamConnectionNotifier> sockets; // Locking: this
private boolean running = false; // Locking: this
private LocalDevice localDevice = null; // Locking: this
private StreamConnectionNotifier socket = null; // Locking: this
// Non-null if running has ever been true
private volatile LocalDevice localDevice = null;
BluetoothPlugin(@PluginExecutor Executor pluginExecutor, Clock clock,
DuplexPluginCallback callback, long pollingInterval) {
this.pluginExecutor = pluginExecutor;
@@ -67,7 +66,6 @@ class BluetoothPlugin implements DuplexPlugin {
this.callback = callback;
this.pollingInterval = pollingInterval;
scheduler = Executors.newScheduledThreadPool(0);
sockets = new ArrayList<StreamConnectionNotifier>();
}
public TransportId getId() {
@@ -76,7 +74,6 @@ class BluetoothPlugin implements DuplexPlugin {
public void start() throws IOException {
// Initialise the Bluetooth stack
LocalDevice localDevice;
try {
localDevice = LocalDevice.getLocalDevice();
} catch(UnsatisfiedLinkError e) {
@@ -86,10 +83,9 @@ class BluetoothPlugin implements DuplexPlugin {
throw new IOException(e.toString());
}
if(LOG.isLoggable(Level.INFO))
LOG.info("Address " + localDevice.getBluetoothAddress());
LOG.info("Local address " + localDevice.getBluetoothAddress());
synchronized(this) {
running = true;
this.localDevice = localDevice;
}
pluginExecutor.execute(new Runnable() {
public void run() {
@@ -102,8 +98,11 @@ class BluetoothPlugin implements DuplexPlugin {
synchronized(this) {
if(!running) return;
}
makeDeviceDiscoverable();
String url = "btspp://localhost:" + getUuid() + ";name=RFCOMM";
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put("address", localDevice.getBluetoothAddress());
callback.mergeLocalProperties(p);
String url = makeUrl("localhost", getUuid());
StreamConnectionNotifier scn;
try {
scn = (StreamConnectionNotifier) Connector.open(url);
@@ -121,44 +120,13 @@ class BluetoothPlugin implements DuplexPlugin {
acceptContactConnections(scn);
}
private String getUuid() {
// FIXME: Avoid making alien calls with a lock held
synchronized(localPropertiesLock) {
TransportProperties p = callback.getLocalProperties();
String uuid = p.get("uuid");
if(uuid == null) {
// Generate a (weakly) random UUID and store it
byte[] b = new byte[16];
new Random().nextBytes(b);
uuid = generateUuid(b);
p.put("uuid", uuid);
callback.setLocalProperties(p);
}
return uuid;
}
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
private void makeDeviceDiscoverable() {
// Try to make the device discoverable (requires root on Linux)
LocalDevice localDevice;
synchronized(this) {
if(!running) return;
localDevice = this.localDevice;
}
try {
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
} catch(BluetoothStateException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
// Advertise the address to contacts if the device is discoverable
if(localDevice.getDiscoverable() != DiscoveryAgent.NOT_DISCOVERABLE) {
// FIXME: Avoid making alien calls with a lock held
synchronized(localPropertiesLock) {
TransportProperties p = callback.getLocalProperties();
p.put("address", localDevice.getBluetoothAddress());
callback.setLocalProperties(p);
}
}
// FIXME: Get the UUID from the local transport properties
private String getUuid() {
return UUID.nameUUIDFromBytes(new byte[0]).toString();
}
private void tryToClose(StreamConnectionNotifier scn) {
@@ -181,7 +149,7 @@ class BluetoothPlugin implements DuplexPlugin {
return;
}
BluetoothTransportConnection conn =
new BluetoothTransportConnection(s);
new BluetoothTransportConnection(s);
callback.incomingConnectionCreated(conn);
synchronized(this) {
if(!running) return;
@@ -192,9 +160,6 @@ class BluetoothPlugin implements DuplexPlugin {
public void stop() {
synchronized(this) {
running = false;
localDevice = null;
for(StreamConnectionNotifier scn : sockets) tryToClose(scn);
sockets.clear();
if(socket != null) {
tryToClose(socket);
socket = null;
@@ -215,75 +180,31 @@ class BluetoothPlugin implements DuplexPlugin {
synchronized(this) {
if(!running) return;
}
pluginExecutor.execute(new Runnable() {
public void run() {
connectAndCallBack(connected);
}
});
}
private void connectAndCallBack(Collection<ContactId> connected) {
synchronized(this) {
if(!running) return;
}
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
Map<ContactId, String> discovered = discoverContactUrls(remote);
for(Entry<ContactId, String> e : discovered.entrySet()) {
ContactId c = e.getKey();
// Don't create redundant connections
if(connected.contains(c)) continue;
String url = e.getValue();
DuplexTransportConnection d = connect(c, url);
if(d != null) callback.outgoingConnectionCreated(c, d);
}
}
private Map<ContactId, String> discoverContactUrls(
Map<ContactId, TransportProperties> remote) {
LocalDevice localDevice;
synchronized(this) {
if(!running) return Collections.emptyMap();
localDevice = this.localDevice;
}
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
Map<String, ContactId> addresses = new HashMap<String, ContactId>();
Map<ContactId, String> uuids = new HashMap<ContactId, String>();
callback.getRemoteProperties();
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey();
TransportProperties p = e.getValue();
String address = p.get("address");
String uuid = p.get("uuid");
final ContactId c = e.getKey();
if(connected.contains(c)) continue;
final String address = e.getValue().get("address");
final String uuid = e.getValue().get("uuid");
if(address != null && uuid != null) {
addresses.put(address, c);
uuids.put(c, uuid);
}
}
if(addresses.isEmpty()) return Collections.emptyMap();
ContactListener listener = new ContactListener(discoveryAgent,
Collections.unmodifiableMap(addresses),
Collections.unmodifiableMap(uuids));
// FIXME: Avoid making alien calls with a lock held
synchronized(discoveryLock) {
try {
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
return listener.waitForUrls();
} catch(BluetoothStateException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
return Collections.emptyMap();
} catch(InterruptedException e) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Interrupted while waiting for URLs");
Thread.currentThread().interrupt();
return Collections.emptyMap();
pluginExecutor.execute(new Runnable() {
public void run() {
synchronized(BluetoothPlugin.this) {
if(!running) return;
}
String url = makeUrl(address, uuid);
DuplexTransportConnection conn = connect(url);
if(conn != null)
callback.outgoingConnectionCreated(c, conn);
}
});
}
}
}
private DuplexTransportConnection connect(ContactId c, String url) {
synchronized(this) {
if(!running) return null;
}
private DuplexTransportConnection connect(String url) {
try {
StreamConnection s = (StreamConnection) Connector.open(url);
return new BluetoothTransportConnection(s);
@@ -297,12 +218,13 @@ class BluetoothPlugin implements DuplexPlugin {
synchronized(this) {
if(!running) return null;
}
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
if(!remote.containsKey(c)) return null;
remote = Collections.singletonMap(c, remote.get(c));
String url = discoverContactUrls(remote).get(c);
return url == null ? null : connect(c, url);
TransportProperties p = callback.getRemoteProperties().get(c);
if(p == null) return null;
String address = p.get("address");
String uuid = p.get("uuid");
if(address == null || uuid == null) return null;
String url = makeUrl(address, uuid);
return connect(url);
}
public boolean supportsInvitations() {
@@ -311,43 +233,40 @@ class BluetoothPlugin implements DuplexPlugin {
public DuplexTransportConnection sendInvitation(PseudoRandom r,
long timeout) {
return createInvitationConnection(r, timeout);
}
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
long timeout) {
return createInvitationConnection(r, timeout);
}
private DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout) {
synchronized(this) {
if(!running) return null;
}
// Use the invitation code to generate the UUID
String uuid = generateUuid(r.nextBytes(16));
// The invitee's device may not be discoverable, so both parties must
// try to initiate connections
final ConnectionCallback c = new ConnectionCallback(uuid, timeout);
pluginExecutor.execute(new Runnable() {
public void run() {
createInvitationConnection(c);
// Discover nearby devices and connect to any with the right UUID
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
long end = clock.currentTimeMillis() + timeout;
String url = null;
while(url == null && clock.currentTimeMillis() < end) {
InvitationListener listener =
new InvitationListener(discoveryAgent, uuid);
// FIXME: Avoid making alien calls with a lock held
synchronized(discoveryLock) {
try {
discoveryAgent.startInquiry(GIAC, listener);
url = listener.waitForUrl();
} catch(BluetoothStateException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.toString());
return null;
} catch(InterruptedException e) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Interrupted while waiting for URL");
Thread.currentThread().interrupt();
return null;
}
}
});
pluginExecutor.execute(new Runnable() {
public void run() {
bindInvitationSocket(c);
synchronized(this) {
if(!running) return null;
}
});
try {
StreamConnection s = c.waitForConnection();
return s == null ? null : new BluetoothTransportConnection(s);
} catch(InterruptedException e) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Interrupted while waiting for connection");
Thread.currentThread().interrupt();
return null;
}
if(url == null) return null;
return connect(url);
}
private String generateUuid(byte[] b) {
@@ -355,95 +274,59 @@ class BluetoothPlugin implements DuplexPlugin {
return uuid.toString().replaceAll("-", "");
}
private void createInvitationConnection(ConnectionCallback c) {
LocalDevice localDevice;
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
long timeout) {
synchronized(this) {
if(!running) return;
localDevice = this.localDevice;
}
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
// Try to discover the other party until the invitation times out
long end = clock.currentTimeMillis() + c.getTimeout();
String url = null;
while(url == null && clock.currentTimeMillis() < end) {
InvitationListener listener = new InvitationListener(discoveryAgent,
c.getUuid());
// FIXME: Avoid making alien calls with a lock held
synchronized(discoveryLock) {
try {
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
url = listener.waitForUrl();
} catch(BluetoothStateException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.toString());
return;
} catch(InterruptedException e) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Interrupted while waiting for URL");
Thread.currentThread().interrupt();
return;
}
}
synchronized(this) {
if(!running) return;
}
}
if(url == null) return;
// Try to connect to the other party
try {
StreamConnection s = (StreamConnection) Connector.open(url);
c.addConnection(s);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
private void bindInvitationSocket(final ConnectionCallback c) {
synchronized(this) {
if(!running) return;
if(!running) return null;
}
// Use the invitation code to generate the UUID
String uuid = generateUuid(r.nextBytes(16));
String url = makeUrl("localhost", uuid);
// Make the device discoverable if possible
makeDeviceDiscoverable();
String url = "btspp://localhost:" + c.getUuid() + ";name=RFCOMM";
// Bind a socket for accepting the invitation connection
final StreamConnectionNotifier scn;
try {
scn = (StreamConnectionNotifier) Connector.open(url);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
return;
return null;
}
synchronized(this) {
if(!running) {
tryToClose(scn);
return;
return null;
}
sockets.add(scn);
}
// Close the socket when the invitation times out
Runnable close = new Runnable() {
public void run() {
synchronized(this) {
sockets.remove(scn);
}
tryToClose(scn);
}
};
ScheduledFuture<?> future = scheduler.schedule(close,
c.getTimeout(), TimeUnit.MILLISECONDS);
// Try to accept a connection
ScheduledFuture<?> f = scheduler.schedule(close, timeout, MILLISECONDS);
// Try to accept a connection and close the socket
try {
StreamConnection s = scn.acceptAndOpen();
// Close the socket and return the connection
if(future.cancel(false)) {
synchronized(this) {
sockets.remove(scn);
}
tryToClose(scn);
}
c.addConnection(s);
return new BluetoothTransportConnection(s);
} catch(IOException e) {
// This is expected when the socket is closed
if(LOG.isLoggable(Level.INFO)) LOG.info(e.toString());
tryToClose(scn);
return null;
} finally {
if(f.cancel(false)) tryToClose(scn);
}
}
private void makeDeviceDiscoverable() {
// Try to make the device discoverable (requires root on Linux)
synchronized(this) {
if(!running) return;
}
try {
localDevice.setDiscoverable(GIAC);
} catch(BluetoothStateException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
}

View File

@@ -2,17 +2,20 @@ package net.sf.briar.plugins.bluetooth;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
import android.content.Context;
public class BluetoothPluginFactory implements DuplexPluginFactory {
private static final long POLLING_INTERVAL = 3L * 60L * 1000L; // 3 mins
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
DuplexPluginCallback callback) {
return new BluetoothPlugin(pluginExecutor, new SystemClock(), callback,
POLLING_INTERVAL);

View File

@@ -1,57 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.microedition.io.StreamConnection;
class ConnectionCallback {
private static final Logger LOG =
Logger.getLogger(ConnectionCallback.class.getName());
private final String uuid;
private final long timeout;
private final long end;
private StreamConnection connection = null; // Locking: this
ConnectionCallback(String uuid, long timeout) {
this.uuid = uuid;
this.timeout = timeout;
end = System.currentTimeMillis() + timeout;
}
String getUuid() {
return uuid;
}
long getTimeout() {
return timeout;
}
synchronized StreamConnection waitForConnection()
throws InterruptedException {
long now = System.currentTimeMillis();
while(connection == null && now < end) {
wait(end - now);
now = System.currentTimeMillis();
}
return connection;
}
synchronized void addConnection(StreamConnection s) {
if(connection == null) {
connection = s;
notifyAll();
} else {
// Redundant connection
try {
s.close();
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
}
}

View File

@@ -1,83 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import net.sf.briar.api.ContactId;
class ContactListener extends AbstractListener {
private static final Logger LOG =
Logger.getLogger(ContactListener.class.getName());
private final Map<String, ContactId> addresses;
private final Map<ContactId, String> uuids;
private final Map<ContactId, String> urls;
ContactListener(DiscoveryAgent discoveryAgent,
Map<String, ContactId> addresses, Map<ContactId, String> uuids) {
super(discoveryAgent);
this.addresses = addresses;
this.uuids = uuids;
urls = Collections.synchronizedMap(new HashMap<ContactId, String>());
}
Map<ContactId, String> waitForUrls() throws InterruptedException {
finished.await();
return urls;
}
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
// Do we recognise the address?
ContactId contactId = addresses.get(device.getBluetoothAddress());
if(contactId == null) return;
// Do we have a UUID for this contact?
String uuid = uuids.get(contactId);
if(uuid == null) return;
UUID[] uuids = new UUID[] { new UUID(uuid, false) };
// Try to discover the services associated with the UUID
try {
discoveryAgent.searchServices(null, uuids, device, this);
} catch(BluetoothStateException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
searches.incrementAndGet();
}
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
for(ServiceRecord record : services) {
// Do we recognise the address?
RemoteDevice device = record.getHostDevice();
ContactId c = addresses.get(device.getBluetoothAddress());
if(c == null) continue;
// Do we have a UUID for this contact?
String uuid = uuids.get(c);
if(uuid == null) return;
// Does this service have a URL?
String serviceUrl = record.getConnectionURL(
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
if(serviceUrl == null) continue;
// Does this service have the UUID we're looking for?
Collection<String> uuids = new TreeSet<String>();
findNestedClassIds(record.getAttributeValue(0x1), uuids);
for(String u : uuids) {
if(uuid.equalsIgnoreCase(u.toString())) {
// The UUID matches - store the URL
urls.put(c, serviceUrl);
}
}
}
}
}

View File

@@ -1,28 +1,37 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
class InvitationListener extends AbstractListener {
class InvitationListener implements DiscoveryListener {
private static final Logger LOG =
Logger.getLogger(InvitationListener.class.getName());
Logger.getLogger(InvitationListener.class.getName());
private final AtomicInteger searches = new AtomicInteger(1);
private final CountDownLatch finished = new CountDownLatch(1);
private final DiscoveryAgent discoveryAgent;
private final String uuid;
private volatile String url = null;
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
super(discoveryAgent);
this.discoveryAgent = discoveryAgent;
this.uuid = uuid;
}
@@ -61,4 +70,35 @@ class InvitationListener extends AbstractListener {
}
}
}
public void inquiryCompleted(int discoveryType) {
if(searches.decrementAndGet() == 0) finished.countDown();
}
public void serviceSearchCompleted(int transaction, int response) {
if(searches.decrementAndGet() == 0) finished.countDown();
}
// UUIDs are sometimes buried in nested data elements
private void findNestedClassIds(Object o, Collection<String> ids) {
o = getDataElementValue(o);
if(o instanceof Enumeration<?>) {
for(Object o1 : Collections.list((Enumeration<?>) o))
findNestedClassIds(o1, ids);
} else if(o instanceof UUID) {
ids.add(o.toString());
}
}
private Object getDataElementValue(Object o) {
if(o instanceof DataElement) {
// Bluecove throws an exception if the type is unknown
try {
return ((DataElement) o).getValue();
} catch(ClassCastException e) {
return null;
}
}
return null;
}
}

View File

@@ -0,0 +1,431 @@
package net.sf.briar.plugins.droidtooth;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.io.IOException;
import java.net.SocketTimeoutException;
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.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.util.StringUtils;
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 android.os.Build;
class DroidtoothPlugin implements DuplexPlugin {
// Share an ID with the J2SE Bluetooth plugin
public static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
+ "00a539fd260f08a13a0d8a900cde5e49"
+ "1b4df2ffd42e40c408f2db7868f518aa");
private static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(DroidtoothPlugin.class.getName());
private static final String FOUND = "android.bluetooth.device.action.FOUND";
private static final String DISCOVERY_FINISHED =
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
private final Executor pluginExecutor;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final DuplexPluginCallback callback;
private final long pollingInterval;
private boolean running = false; // Locking: this
private BluetoothServerSocket socket = null; // Locking: this
// Non-null if running has ever been true
private volatile BluetoothAdapter adapter = null;
DroidtoothPlugin(@PluginExecutor Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
DuplexPluginCallback callback, long pollingInterval) {
this.pluginExecutor = pluginExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.callback = callback;
this.pollingInterval = pollingInterval;
}
public TransportId getId() {
return ID;
}
public void start() throws IOException {
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
Callable<BluetoothAdapter> c = new Callable<BluetoothAdapter>() {
public BluetoothAdapter call() throws Exception {
return BluetoothAdapter.getDefaultAdapter();
}
};
Future<BluetoothAdapter> f = androidExecutor.submit(c);
try {
adapter = f.get();
} catch(InterruptedException e) {
throw new IOException(e.toString());
} catch(ExecutionException e) {
throw new IOException(e.toString());
}
if(adapter == null) throw new IOException(); // Bluetooth not supported
synchronized(this) {
running = true;
}
pluginExecutor.execute(new Runnable() {
public void run() {
bind();
}
});
}
private void bind() {
synchronized(this) {
if(!running) return;
}
if(!enableBluetooth()) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Could not enable Bluetooth");
return;
}
makeDeviceDiscoverable();
if(LOG.isLoggable(Level.INFO))
LOG.info("Local address " + adapter.getAddress());
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put("address", adapter.getAddress());
callback.mergeLocalProperties(p);
// Bind a server socket to accept connections from contacts
BluetoothServerSocket ss;
try {
ss = InsecureBluetooth.listen(adapter, "RFCOMM", getUuid(), false);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
return;
}
synchronized(this) {
if(!running) {
tryToClose(ss);
return;
}
socket = ss;
}
acceptContactConnections(ss);
}
private boolean enableBluetooth() {
synchronized(this) {
if(!running) return false;
}
if(adapter.isEnabled()) return true;
// Try to enable the adapter and wait for the result
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
BluetoothStateReceiver receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
try {
if(!adapter.enable()) return false;
return receiver.waitForStateChange();
} catch(InterruptedException e) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Interrupted while enabling Bluetooth");
Thread.currentThread().interrupt();
return false;
}
}
private void makeDeviceDiscoverable() {
synchronized(this) {
if(!running) return;
}
if(adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) return;
// Indefinite discoverability can only be set on API Level 8 or higher
if(Build.VERSION.SDK_INT < 8) return;
Intent intent = new Intent(ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(EXTRA_DISCOVERABLE_DURATION, 0);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(intent);
}
// FIXME: Get the UUID from the local transport properties
private UUID getUuid() {
return UUID.nameUUIDFromBytes(new byte[0]);
}
private void tryToClose(BluetoothServerSocket ss) {
try {
ss.close();
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
}
}
private void acceptContactConnections(BluetoothServerSocket ss) {
while(true) {
BluetoothSocket s;
try {
s = ss.accept();
} catch(IOException e) {
// This is expected when the socket is closed
if(LOG.isLoggable(Level.INFO)) LOG.info(e.toString());
tryToClose(ss);
return;
}
DroidtoothTransportConnection conn =
new DroidtoothTransportConnection(s);
callback.incomingConnectionCreated(conn);
synchronized(this) {
if(!running) return;
}
}
}
public void stop() throws IOException {
synchronized(this) {
running = false;
if(socket != null) {
tryToClose(socket);
socket = null;
}
}
}
public boolean shouldPoll() {
return true;
}
public long getPollingInterval() {
return pollingInterval;
}
public void poll(Collection<ContactId> connected) {
synchronized(this) {
if(!running) return;
}
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if(connected.contains(c)) continue;
final String address = e.getValue().get("address");
final String uuid = e.getValue().get("uuid");
if(address != null && uuid != null) {
pluginExecutor.execute(new Runnable() {
public void run() {
synchronized(DroidtoothPlugin.this) {
if(!running) return;
}
DuplexTransportConnection conn = connect(address, uuid);
if(conn != null)
callback.outgoingConnectionCreated(c, conn);
}
});
}
}
}
private DuplexTransportConnection connect(String address, String uuid) {
// Validate the address
if(!BluetoothAdapter.checkBluetoothAddress(address)) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Invalid address " + address);
return null;
}
BluetoothDevice d = adapter.getRemoteDevice(address);
// Validate the UUID
UUID u;
try {
u = UUID.fromString(uuid);
} catch(IllegalArgumentException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Invalid UUID " + uuid);
return null;
}
// Try to connect
try {
BluetoothSocket s = InsecureBluetooth.createSocket(d, u, false);
return new DroidtoothTransportConnection(s);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
return null;
}
}
public DuplexTransportConnection createConnection(ContactId c) {
synchronized(this) {
if(!running) return null;
}
TransportProperties p = callback.getRemoteProperties().get(c);
if(p == null) return null;
String address = p.get("address");
String uuid = p.get("uuid");
if(address == null || uuid == null) return null;
return connect(address, uuid);
}
public boolean supportsInvitations() {
return true;
}
public DuplexTransportConnection sendInvitation(PseudoRandom r,
long timeout) {
synchronized(this) {
if(!running) return null;
}
// Use the same pseudo-random UUID as the contact
String uuid = UUID.nameUUIDFromBytes(r.nextBytes(16)).toString();
// Register to receive Bluetooth discovery intents
IntentFilter filter = new IntentFilter();
filter.addAction(FOUND);
filter.addAction(DISCOVERY_FINISHED);
// Discover nearby devices and connect to any with the right UUID
DiscoveryReceiver receiver = new DiscoveryReceiver(uuid);
appContext.registerReceiver(receiver, filter);
adapter.startDiscovery();
try {
return receiver.waitForConnection(timeout);
} catch(InterruptedException e) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Interrupted while sending invitation");
Thread.currentThread().interrupt();
return null;
}
}
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
long timeout) {
synchronized(this) {
if(!running) return null;
}
// Use the same pseudo-random UUID as the contact
UUID uuid = UUID.nameUUIDFromBytes(r.nextBytes(16));
// Bind a new server socket to accept the invitation connection
final BluetoothServerSocket ss;
try {
ss = InsecureBluetooth.listen(adapter, "RFCOMM", uuid, false);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
return null;
}
// Return the first connection received by the socket, if any
try {
return new DroidtoothTransportConnection(ss.accept((int) timeout));
} catch(SocketTimeoutException e) {
if(LOG.isLoggable(Level.INFO)) LOG.info("Invitation timed out");
return null;
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
return null;
} finally {
tryToClose(ss);
}
}
private static class BluetoothStateReceiver extends BroadcastReceiver {
private final CountDownLatch finished = new CountDownLatch(1);
private volatile boolean enabled = false;
@Override
public void onReceive(Context ctx, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if(state == STATE_ON) {
enabled = true;
finish(ctx);
} else if(state == STATE_OFF) {
finish(ctx);
}
}
private void finish(Context ctx) {
ctx.getApplicationContext().unregisterReceiver(this);
finished.countDown();
}
boolean waitForStateChange() throws InterruptedException {
finished.await();
return enabled;
}
}
private class DiscoveryReceiver extends BroadcastReceiver {
private final CountDownLatch finished = new CountDownLatch(1);
private final String uuid;
private volatile DuplexTransportConnection connection = null;
private DiscoveryReceiver(String uuid) {
this.uuid = uuid;
}
@Override
public void onReceive(final Context ctx, Intent intent) {
String action = intent.getAction();
if(action.equals(DISCOVERY_FINISHED)) {
finish(ctx);
} else if(action.equals(FOUND)) {
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
final String address = d.getAddress();
pluginExecutor.execute(new Runnable() {
public void run() {
synchronized(DroidtoothPlugin.this) {
if(!running) return;
}
DuplexTransportConnection conn = connect(address, uuid);
if(conn != null) {
connection = conn;
finish(ctx);
}
}
});
}
}
private void finish(Context ctx) {
ctx.getApplicationContext().unregisterReceiver(this);
finished.countDown();
}
private DuplexTransportConnection waitForConnection(long timeout)
throws InterruptedException {
finished.await(timeout, MILLISECONDS);
return connection;
}
}
}

View File

@@ -0,0 +1,22 @@
package net.sf.briar.plugins.droidtooth;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
import android.content.Context;
public class DroidtoothPluginFactory implements DuplexPluginFactory {
private static final long POLLING_INTERVAL = 3L * 60L * 1000L; // 3 mins
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
DuplexPluginCallback callback) {
return new DroidtoothPlugin(pluginExecutor, androidExecutor, appContext,
callback, POLLING_INTERVAL);
}
}

View File

@@ -0,0 +1,34 @@
package net.sf.briar.plugins.droidtooth;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import android.bluetooth.BluetoothSocket;
class DroidtoothTransportConnection implements DuplexTransportConnection {
private final BluetoothSocket socket;
DroidtoothTransportConnection(BluetoothSocket socket) {
this.socket = socket;
}
public InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
}
public boolean shouldFlush() {
return true;
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
socket.close();
}
}

View File

@@ -0,0 +1,207 @@
package net.sf.briar.plugins.droidtooth;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelUuid;
// Based on http://stanford.edu/~tpurtell/InsecureBluetooth.java by T.J. Purtell
class InsecureBluetooth {
static BluetoothServerSocket listen(BluetoothAdapter adapter, String name,
UUID uuid, boolean encrypt) throws IOException {
try {
String className = BluetoothAdapter.class.getName()
+ ".RfcommChannelPicker";
Class<?> channelPickerClass = null;
Class<?>[] children = BluetoothAdapter.class.getDeclaredClasses();
for(Class<?> c : children) {
if(c.getCanonicalName().equals(className)) {
channelPickerClass = c;
break;
}
}
if(channelPickerClass == null)
throw new IOException("Can't find channel picker class");
Constructor<?> constructor =
channelPickerClass.getDeclaredConstructor(UUID.class);
if(constructor == null)
throw new IOException("Can't find channel picker constructor");
Object channelPicker = constructor.newInstance(uuid);
Method nextChannel = channelPickerClass.getDeclaredMethod(
"nextChannel", new Class[0]);
nextChannel.setAccessible(true);
BluetoothServerSocket socket = null;
int channel;
while(true) {
channel = (Integer) nextChannel.invoke(channelPicker,
new Object[0]);
if(channel == -1)
throw new IOException("No available channels");
try {
socket = listen(channel, encrypt);
break;
} catch(InUseException e) {
continue;
}
}
Field f = adapter.getClass().getDeclaredField("mService");
f.setAccessible(true);
Object mService = f.get(adapter);
Method addRfcommServiceRecord =
mService.getClass().getDeclaredMethod(
"addRfcommServiceRecord", String.class,
ParcelUuid.class, int.class, IBinder.class);
addRfcommServiceRecord.setAccessible(true);
int handle = (Integer) addRfcommServiceRecord.invoke(mService, name,
new ParcelUuid(uuid), channel, new Binder());
if(handle == -1) {
try {
socket.close();
} catch(IOException ignored) {}
throw new IOException("Can't register SDP record for " + name);
}
Field f1 = adapter.getClass().getDeclaredField("mHandler");
f1.setAccessible(true);
Object mHandler = f1.get(adapter);
Method setCloseHandler = socket.getClass().getDeclaredMethod(
"setCloseHandler", Handler.class, int.class);
setCloseHandler.setAccessible(true);
setCloseHandler.invoke(socket, mHandler, handle);
return socket;
} catch(NoSuchMethodException e) {
throw new IOException(e.toString());
} catch(NoSuchFieldException e) {
throw new IOException(e.toString());
} catch(IllegalAccessException e) {
throw new IOException(e.toString());
} catch(InstantiationException e) {
throw new IOException(e.toString());
} catch(InvocationTargetException e) {
if(e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
throw new IOException(e.toString());
}
}
}
private static BluetoothServerSocket listen(int port, boolean encrypt,
boolean reuse) throws IOException, InUseException {
BluetoothServerSocket socket = null;
try {
Constructor<BluetoothServerSocket> constructor =
BluetoothServerSocket.class.getDeclaredConstructor(
int.class, boolean.class, boolean.class, int.class);
if(constructor == null)
throw new IOException("Can't find server socket constructor");
constructor.setAccessible(true);
Field f = BluetoothSocket.class.getDeclaredField("TYPE_RFCOMM");
f.setAccessible(true);
int rfcommType = (Integer) f.get(null);
Field f1 = BluetoothSocket.class.getDeclaredField("EADDRINUSE");
f1.setAccessible(true);
int eAddrInUse = (Integer) f1.get(null);
socket = constructor.newInstance(rfcommType, false, encrypt, port);
Field f2 = socket.getClass().getDeclaredField("mSocket");
f2.setAccessible(true);
Object mSocket = f2.get(socket);
Method bindListen = mSocket.getClass().getDeclaredMethod(
"bindListen", new Class[0]);
bindListen.setAccessible(true);
Object result = bindListen.invoke(mSocket, new Object[0]);
int errno = (Integer) result;
if(reuse && errno == eAddrInUse) {
throw new InUseException();
} else if(errno != 0) {
try {
socket.close();
} catch(IOException ignored) {}
Method throwErrnoNative = mSocket.getClass().getMethod(
"throwErrnoNative", int.class);
throwErrnoNative.invoke(mSocket, errno);
}
return socket;
} catch(NoSuchMethodException e) {
throw new IOException(e.toString());
} catch(NoSuchFieldException e) {
throw new IOException(e.toString());
} catch(IllegalAccessException e) {
throw new IOException(e.toString());
} catch(InstantiationException e) {
throw new IOException(e.toString());
} catch(InvocationTargetException e) {
if(e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
throw new IOException(e.toString());
}
}
}
static BluetoothServerSocket listen(int port, boolean encrypt)
throws IOException {
return listen(port, encrypt, false);
}
private static BluetoothSocket createSocket(BluetoothDevice device,
int port, UUID uuid, boolean encrypt) throws IOException {
try {
BluetoothSocket socket = null;
Constructor<BluetoothSocket> constructor =
BluetoothSocket.class.getDeclaredConstructor(int.class,
int.class, boolean.class, boolean.class,
BluetoothDevice.class, int.class, ParcelUuid.class);
if(constructor == null)
throw new IOException("Can't find socket constructor");
constructor.setAccessible(true);
Field f = BluetoothSocket.class.getDeclaredField("TYPE_RFCOMM");
f.setAccessible(true);
int typeRfcomm = (Integer) f.get(null);
socket = constructor.newInstance(typeRfcomm, -1, false, true,
device, port, uuid != null ? new ParcelUuid(uuid) : null);
return socket;
} catch(NoSuchMethodException e) {
throw new IOException(e.toString());
} catch(NoSuchFieldException e) {
throw new IOException(e.toString());
} catch(IllegalAccessException e) {
throw new IOException(e.toString());
} catch(InstantiationException e) {
throw new IOException(e.toString());
} catch(InvocationTargetException e) {
if(e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
throw new IOException(e.toString());
}
}
}
static BluetoothSocket createSocket(BluetoothDevice device, UUID uuid,
boolean encrypt) throws IOException {
return createSocket(device, -1, uuid, encrypt);
}
static BluetoothSocket createSocket(BluetoothDevice device, int port,
boolean encrypt) throws IOException {
return createSocket(device, port, null, encrypt);
}
private static class InUseException extends RuntimeException {
private static final long serialVersionUID = -5983642322821496023L;
}
}

View File

@@ -2,13 +2,16 @@ package net.sf.briar.plugins.email;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
import android.content.Context;
public class GmailPluginFactory implements SimplexPluginFactory {
public SimplexPlugin createPlugin(Executor pluginExecutor,
AndroidExecutor androidExecutor, Context context,
SimplexPluginCallback callback) {
return new GmailPlugin(pluginExecutor, callback);
}

View File

@@ -2,17 +2,20 @@ package net.sf.briar.plugins.file;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
import net.sf.briar.util.OsUtils;
import android.content.Context;
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
private static final long POLLING_INTERVAL = 10L * 1000L; // 10 seconds
public SimplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
SimplexPluginCallback callback) {
RemovableDriveFinder finder;
RemovableDriveMonitor monitor;

View File

@@ -128,12 +128,12 @@ class SimpleSocketPlugin extends SocketPlugin {
throw new IllegalArgumentException();
InetSocketAddress i = (InetSocketAddress) s;
InetAddress addr = i.getAddress();
TransportProperties p = callback.getLocalProperties();
TransportProperties p = new TransportProperties();
if(addr.isLinkLocalAddress() || addr.isSiteLocalAddress())
p.put("internal", addr.getHostAddress());
else p.put("external", addr.getHostAddress());
p.put("port", String.valueOf(i.getPort()));
callback.setLocalProperties(p);
callback.mergeLocalProperties(p);
}
public boolean supportsInvitations() {

View File

@@ -2,16 +2,19 @@ package net.sf.briar.plugins.socket;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
import android.content.Context;
public class SimpleSocketPluginFactory implements DuplexPluginFactory {
private static final long POLLING_INTERVAL = 5L * 60L * 1000L; // 5 mins
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
DuplexPluginCallback callback) {
return new SimpleSocketPlugin(pluginExecutor, callback,
POLLING_INTERVAL);

View File

@@ -95,9 +95,9 @@ class TorPlugin implements DuplexPlugin {
if(c.containsKey("noHiddenService")) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Not creating hidden service");
TransportProperties p = callback.getLocalProperties();
p.remove("onion");
callback.setLocalProperties(p);
TransportProperties p = new TransportProperties();
p.put("onion", null);
callback.mergeLocalProperties(p);
return;
}
// Retrieve the hidden service address, or create one if necessary
@@ -107,7 +107,7 @@ class TorPlugin implements DuplexPlugin {
if(privateKey == null) {
if(LOG.isLoggable(Level.INFO))
LOG.info("Creating hidden service address");
addr = createHiddenServiceAddress(util, c);
addr = createHiddenServiceAddress(util);
} else {
if(LOG.isLoggable(Level.INFO))
LOG.info("Parsing hidden service address");
@@ -116,7 +116,7 @@ class TorPlugin implements DuplexPlugin {
privateKey, "", false);
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
addr = createHiddenServiceAddress(util, c);
addr = createHiddenServiceAddress(util);
}
}
TorHiddenServicePortPrivateNetAddress addrPort =
@@ -141,18 +141,19 @@ class TorPlugin implements DuplexPlugin {
if(LOG.isLoggable(Level.INFO)) LOG.info("Listening on " + onion);
TransportProperties p = callback.getLocalProperties();
p.put("onion", onion);
callback.setLocalProperties(p);
callback.mergeLocalProperties(p);
acceptContactConnections(ss);
}
private TorHiddenServicePrivateNetAddress createHiddenServiceAddress(
TorNetLayerUtil util, TransportConfig c) {
TorNetLayerUtil util) {
TorHiddenServicePrivateNetAddress addr =
util.createNewTorHiddenServicePrivateNetAddress();
RSAKeyPair keyPair = addr.getKeyPair();
String privateKey = Encryption.getPEMStringFromRSAKeyPair(keyPair);
TransportConfig c = new TransportConfig();
c.put("privateKey", privateKey);
callback.setConfig(c);
callback.mergeConfig(c);
return addr;
}

View File

@@ -2,16 +2,19 @@ package net.sf.briar.plugins.tor;
import java.util.concurrent.Executor;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
import android.content.Context;
public class TorPluginFactory implements DuplexPluginFactory {
private static final long POLLING_INTERVAL = 15L * 60L * 1000L; // 15 mins
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
AndroidExecutor androidExecutor, Context appContext,
DuplexPluginCallback callback) {
return new TorPlugin(pluginExecutor, callback, POLLING_INTERVAL);
}

View File

@@ -7,10 +7,10 @@ import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.TemporarySecret;
import com.google.inject.Inject;

View File

@@ -14,16 +14,16 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.ContactTransport;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.ContactTransport;
import net.sf.briar.api.transport.TemporarySecret;
import net.sf.briar.util.ByteUtils;
import com.google.inject.Inject;

View File

@@ -15,9 +15,9 @@ import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.TemporarySecret;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.TemporarySecret;
import net.sf.briar.util.ByteUtils;
/** A connection recogniser for a specific transport. */

View File

@@ -1,12 +1,13 @@
package net.sf.briar.util;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -26,8 +27,8 @@ public class BoundedExecutor implements Executor {
public BoundedExecutor(int maxQueued, int minThreads, int maxThreads) {
semaphore = new Semaphore(maxQueued + maxThreads);
queue = new LinkedBlockingQueue<Runnable>();
executor = new ThreadPoolExecutor(minThreads, maxThreads, 60,
TimeUnit.SECONDS, queue);
executor = new ThreadPoolExecutor(minThreads, maxThreads, 60, SECONDS,
queue);
}
public void execute(final Runnable r) {

View File

@@ -7,6 +7,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.security.CodeSource;
import org.apache.commons.io.FileSystemUtils;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.StatFs;
public class FileUtils {
/**
@@ -62,7 +68,7 @@ public class FileUtils {
* callback is not null it's called once for each file created.
*/
public static void copyRecursively(File src, File dest, Callback callback)
throws IOException {
throws IOException {
assert dest.exists();
assert dest.isDirectory();
dest = new File(dest, src.getName());
@@ -82,6 +88,20 @@ public class FileUtils {
f.delete();
}
@SuppressLint("NewApi")
public static long getFreeSpace(File f) throws IOException {
if(OsUtils.isAndroid()) {
if(Build.VERSION.SDK_INT >= 9) {
return f.getUsableSpace();
} else {
StatFs s = new StatFs(f.getAbsolutePath());
return (long) s.getAvailableBlocks() * s.getBlockSize();
}
} else {
return FileSystemUtils.freeSpaceKb(f.getAbsolutePath()) * 1024L;
}
}
public interface Callback {
void processingFile(File f);

View File

@@ -4,13 +4,14 @@ public class OsUtils {
private static final String os = System.getProperty("os.name");
private static final String version = System.getProperty("os.version");
private static final String vendor = System.getProperty("java.vendor");
public static boolean isWindows() {
return os.indexOf("Windows") != -1;
return os != null && os.indexOf("Windows") != -1;
}
public static boolean isMac() {
return os.indexOf("Mac OS") != -1;
return os != null && os.indexOf("Mac OS") != -1;
}
public static boolean isMacLeopardOrNewer() {
@@ -27,6 +28,10 @@ public class OsUtils {
}
public static boolean isLinux() {
return os.indexOf("Linux") != -1;
return os != null && os.indexOf("Linux") != -1 && !isAndroid();
}
public static boolean isAndroid() {
return vendor != null && vendor.indexOf("Android") != -1;
}
}

View File

@@ -1,75 +0,0 @@
<project name='test' default='test'>
<import file='../build-common.xml'/>
<target name='test' depends='depend'>
<junit printsummary='on' fork='yes' forkmode='once'>
<assertions>
<enable/>
</assertions>
<classpath>
<fileset refid='bundled-jars'/>
<fileset refid='test-jars'/>
<path refid='classes'/>
<path refid='test-classes'/>
</classpath>
<jvmarg value='-Djava.library.path=../libs'/>
<test name='net.sf.briar.LockFairnessTest'/>
<test name='net.sf.briar.ProtocolIntegrationTest'/>
<test name='net.sf.briar.crypto.CounterModeTest'/>
<test name='net.sf.briar.crypto.ErasableKeyTest'/>
<test name='net.sf.briar.crypto.KeyAgreementTest'/>
<test name='net.sf.briar.crypto.KeyDerivationTest'/>
<test name='net.sf.briar.db.BasicH2Test'/>
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
<test name='net.sf.briar.db.DatabaseComponentImplTest'/>
<test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/>
<test name='net.sf.briar.lifecycle.WindowsShutdownManagerImplTest'/>
<test name='net.sf.briar.plugins.PluginManagerImplTest'/>
<test name='net.sf.briar.plugins.file.LinuxRemovableDriveFinderTest'/>
<test name='net.sf.briar.plugins.file.MacRemovableDriveFinderTest'/>
<test name='net.sf.briar.plugins.file.PollingRemovableDriveMonitorTest'/>
<test name='net.sf.briar.plugins.file.RemovableDrivePluginTest'/>
<test name='net.sf.briar.plugins.file.UnixRemovableDriveMonitorTest'/>
<test name='net.sf.briar.plugins.socket.SimpleSocketPluginTest'/>
<test name='net.sf.briar.protocol.AckReaderTest'/>
<test name='net.sf.briar.protocol.BatchReaderTest'/>
<test name='net.sf.briar.protocol.ConstantsTest'/>
<test name='net.sf.briar.protocol.ConsumersTest'/>
<test name='net.sf.briar.protocol.OfferReaderTest'/>
<test name='net.sf.briar.protocol.ProtocolIntegrationTest'/>
<test name='net.sf.briar.protocol.ProtocolWriterImplTest'/>
<test name='net.sf.briar.protocol.RequestReaderTest'/>
<test name='net.sf.briar.protocol.UnverifiedBatchImplTest'/>
<test name='net.sf.briar.protocol.simplex.OutgoingSimplexConnectionTest'/>
<test name='net.sf.briar.protocol.simplex.SimplexProtocolIntegrationTest'/>
<test name='net.sf.briar.serial.ReaderImplTest'/>
<test name='net.sf.briar.serial.WriterImplTest'/>
<test name='net.sf.briar.transport.ConnectionReaderImplTest'/>
<test name='net.sf.briar.transport.ConnectionRegistryImplTest'/>
<test name='net.sf.briar.transport.ConnectionWindowTest'/>
<test name='net.sf.briar.transport.ConnectionWriterImplTest'/>
<test name='net.sf.briar.transport.IncomingEncryptionLayerTest'/>
<test name='net.sf.briar.transport.OutgoingEncryptionLayerTest'/>
<test name='net.sf.briar.transport.TransportIntegrationTest'/>
<test name='net.sf.briar.util.ByteUtilsTest'/>
<test name='net.sf.briar.util.FileUtilsTest'/>
<test name='net.sf.briar.util.StringUtilsTest'/>
<test name='net.sf.briar.util.ZipUtilsTest'/>
</junit>
</target>
<target name='test-slow' depends='depend'>
<junit printsummary='withOutAndErr' fork='yes' forkmode='once'>
<assertions>
<enable/>
</assertions>
<classpath>
<fileset refid='bundled-jars'/>
<fileset refid='test-jars'/>
<path refid='classes'/>
<path refid='test-classes'/>
</classpath>
<jvmarg value='-Djava.library.path=../libs'/>
<test name='net.sf.briar.db.H2DatabaseTest'/>
<test name='net.sf.briar.plugins.tor.TorPluginTest'/>
</junit>
</target>
</project>

View File

@@ -1,19 +0,0 @@
package net.sf.briar;
import java.lang.Thread.UncaughtExceptionHandler;
import junit.framework.TestCase;
public abstract class BriarTestCase extends TestCase {
public BriarTestCase() {
super();
// Ensure exceptions thrown on worker threads cause tests to fail
UncaughtExceptionHandler fail = new UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable throwable) {
fail();
}
};
Thread.setDefaultUncaughtExceptionHandler(fail);
}
}

View File

@@ -1,161 +0,0 @@
package net.sf.briar;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.junit.Test;
public class LockFairnessTest extends BriarTestCase {
@Test
public void testReadersCanShareTheLock() throws Exception {
// Use a fair lock
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
final CountDownLatch firstReaderHasLock = new CountDownLatch(1);
final CountDownLatch firstReaderHasFinished = new CountDownLatch(1);
final CountDownLatch secondReaderHasLock = new CountDownLatch(1);
final CountDownLatch secondReaderHasFinished = new CountDownLatch(1);
// First reader
Thread first = new Thread() {
@Override
public void run() {
try {
// Acquire the lock
lock.readLock().lock();
try {
// Allow the second reader to acquire the lock
firstReaderHasLock.countDown();
// Wait for the second reader to acquire the lock
assertTrue(secondReaderHasLock.await(10,
TimeUnit.SECONDS));
} finally {
// Release the lock
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
firstReaderHasFinished.countDown();
}
};
first.start();
// Second reader
Thread second = new Thread() {
@Override
public void run() {
try {
// Wait for the first reader to acquire the lock
assertTrue(firstReaderHasLock.await(10, TimeUnit.SECONDS));
// Acquire the lock
lock.readLock().lock();
try {
// Allow the first reader to release the lock
secondReaderHasLock.countDown();
} finally {
// Release the lock
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
secondReaderHasFinished.countDown();
}
};
second.start();
// Wait for both readers to finish
assertTrue(firstReaderHasFinished.await(10, TimeUnit.SECONDS));
assertTrue(secondReaderHasFinished.await(10, TimeUnit.SECONDS));
}
@Test
public void testWritersDoNotStarve() throws Exception {
// Use a fair lock
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
final CountDownLatch firstReaderHasLock = new CountDownLatch(1);
final CountDownLatch firstReaderHasFinished = new CountDownLatch(1);
final CountDownLatch secondReaderHasFinished = new CountDownLatch(1);
final CountDownLatch writerHasFinished = new CountDownLatch(1);
final AtomicBoolean secondReaderHasHeldLock = new AtomicBoolean(false);
final AtomicBoolean writerHasHeldLock = new AtomicBoolean(false);
// First reader
Thread first = new Thread() {
@Override
public void run() {
try {
// Acquire the lock
lock.readLock().lock();
try {
// Allow the other threads to acquire the lock
firstReaderHasLock.countDown();
// Wait for both other threads to wait for the lock
while(lock.getQueueLength() < 2) Thread.sleep(10);
// No other thread should have acquired the lock
assertFalse(secondReaderHasHeldLock.get());
assertFalse(writerHasHeldLock.get());
} finally {
// Release the lock
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
firstReaderHasFinished.countDown();
}
};
first.start();
// Writer
Thread writer = new Thread() {
@Override
public void run() {
try {
// Wait for the first reader to acquire the lock
assertTrue(firstReaderHasLock.await(10, TimeUnit.SECONDS));
// Acquire the lock
lock.writeLock().lock();
try {
writerHasHeldLock.set(true);
// The second reader should not overtake the writer
assertFalse(secondReaderHasHeldLock.get());
} finally {
lock.writeLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
writerHasFinished.countDown();
}
};
writer.start();
// Second reader
Thread second = new Thread() {
@Override
public void run() {
try {
// Wait for the first reader to acquire the lock
assertTrue(firstReaderHasLock.await(10, TimeUnit.SECONDS));
// Wait for the writer to wait for the lock
while(lock.getQueueLength() < 1) Thread.sleep(10);
// Acquire the lock
lock.readLock().lock();
try {
secondReaderHasHeldLock.set(true);
// The second reader should not overtake the writer
assertTrue(writerHasHeldLock.get());
} finally {
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
secondReaderHasFinished.countDown();
}
};
second.start();
// Wait for all the threads to finish
assertTrue(firstReaderHasFinished.await(10, TimeUnit.SECONDS));
assertTrue(secondReaderHasFinished.await(10, TimeUnit.SECONDS));
assertTrue(writerHasFinished.await(10, TimeUnit.SECONDS));
}
}

View File

@@ -1,264 +0,0 @@
package net.sf.briar;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.Author;
import net.sf.briar.api.protocol.AuthorFactory;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageFactory;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.Offer;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.ProtocolReader;
import net.sf.briar.api.protocol.ProtocolReaderFactory;
import net.sf.briar.api.protocol.ProtocolWriter;
import net.sf.briar.api.protocol.ProtocolWriterFactory;
import net.sf.briar.api.protocol.RawBatch;
import net.sf.briar.api.protocol.Request;
import net.sf.briar.api.protocol.SubscriptionUpdate;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.clock.ClockModule;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.db.DatabaseModule;
import net.sf.briar.lifecycle.LifecycleModule;
import net.sf.briar.protocol.ProtocolModule;
import net.sf.briar.protocol.duplex.DuplexProtocolModule;
import net.sf.briar.protocol.simplex.SimplexProtocolModule;
import net.sf.briar.serial.SerialModule;
import net.sf.briar.transport.TransportModule;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class ProtocolIntegrationTest extends BriarTestCase {
private final BatchId ack = new BatchId(TestUtils.getRandomId());
private final long timestamp = System.currentTimeMillis();
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final ProtocolReaderFactory protocolReaderFactory;
private final ProtocolWriterFactory protocolWriterFactory;
private final PacketFactory packetFactory;
private final CryptoComponent crypto;
private final ContactId contactId;
private final TransportId transportId;
private final byte[] secret;
private final Author author;
private final Group group, group1;
private final Message message, message1, message2, message3;
private final String authorName = "Alice";
private final String subject = "Hello";
private final String messageBody = "Hello world";
private final Collection<Transport> transports;
public ProtocolIntegrationTest() throws Exception {
super();
Injector i = Guice.createInjector(new ClockModule(), new CryptoModule(),
new DatabaseModule(), new LifecycleModule(),
new ProtocolModule(), new SerialModule(),
new TestDatabaseModule(), new SimplexProtocolModule(),
new TransportModule(), new DuplexProtocolModule());
connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class);
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
protocolReaderFactory = i.getInstance(ProtocolReaderFactory.class);
protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class);
packetFactory = i.getInstance(PacketFactory.class);
crypto = i.getInstance(CryptoComponent.class);
contactId = new ContactId(234);
transportId = new TransportId(TestUtils.getRandomId());
// Create a shared secret
Random r = new Random();
secret = new byte[32];
r.nextBytes(secret);
// Create two groups: one restricted, one unrestricted
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
group = groupFactory.createGroup("Unrestricted group", null);
KeyPair groupKeyPair = crypto.generateSignatureKeyPair();
group1 = groupFactory.createGroup("Restricted group",
groupKeyPair.getPublic().getEncoded());
// Create an author
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
author = authorFactory.createAuthor(authorName,
authorKeyPair.getPublic().getEncoded());
// Create two messages to each group: one anonymous, one pseudonymous
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
message = messageFactory.createMessage(null, group, subject,
messageBody.getBytes("UTF-8"));
message1 = messageFactory.createMessage(null, group1,
groupKeyPair.getPrivate(), subject,
messageBody.getBytes("UTF-8"));
message2 = messageFactory.createMessage(null, group, author,
authorKeyPair.getPrivate(), subject,
messageBody.getBytes("UTF-8"));
message3 = messageFactory.createMessage(null, group1,
groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
subject, messageBody.getBytes("UTF-8"));
// Create some transports
TransportId transportId = new TransportId(TestUtils.getRandomId());
Transport transport = new Transport(transportId,
Collections.singletonMap("bar", "baz"));
transports = Collections.singletonList(transport);
}
@Test
public void testWriteAndRead() throws Exception {
read(write());
}
private byte[] write() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret.clone(), 0L, true);
ConnectionWriter conn = connectionWriterFactory.createConnectionWriter(
out, Long.MAX_VALUE, ctx, false, true);
OutputStream out1 = conn.getOutputStream();
ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out1,
false);
Ack a = packetFactory.createAck(Collections.singletonList(ack));
writer.writeAck(a);
Collection<byte[]> batch = Arrays.asList(message.getSerialised(),
message1.getSerialised(), message2.getSerialised(),
message3.getSerialised());
RawBatch b = packetFactory.createBatch(batch);
writer.writeBatch(b);
Collection<MessageId> offer = Arrays.asList(message.getId(),
message1.getId(), message2.getId(), message3.getId());
Offer o = packetFactory.createOffer(offer);
writer.writeOffer(o);
BitSet requested = new BitSet(4);
requested.set(1);
requested.set(3);
Request r = packetFactory.createRequest(requested, 4);
writer.writeRequest(r);
// Use a LinkedHashMap for predictable iteration order
Map<Group, Long> subs = new LinkedHashMap<Group, Long>();
subs.put(group, 0L);
subs.put(group1, 0L);
SubscriptionUpdate s = packetFactory.createSubscriptionUpdate(
Collections.<GroupId, GroupId>emptyMap(), subs, 0L, timestamp);
writer.writeSubscriptionUpdate(s);
TransportUpdate t = packetFactory.createTransportUpdate(transports,
timestamp);
writer.writeTransportUpdate(t);
writer.flush();
return out.toByteArray();
}
private void read(byte[] connectionData) throws Exception {
InputStream in = new ByteArrayInputStream(connectionData);
byte[] tag = new byte[TAG_LENGTH];
assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH));
// FIXME: Check that the expected tag was received
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret.clone(), 0L, false);
ConnectionReader conn = connectionReaderFactory.createConnectionReader(
in, ctx, true, true);
InputStream in1 = conn.getInputStream();
ProtocolReader reader = protocolReaderFactory.createProtocolReader(in1);
// Read the ack
assertTrue(reader.hasAck());
Ack a = reader.readAck();
assertEquals(Collections.singletonList(ack), a.getBatchIds());
// Read and verify the batch
assertTrue(reader.hasBatch());
Batch b = reader.readBatch().verify();
Collection<Message> messages = b.getMessages();
assertEquals(4, messages.size());
Iterator<Message> it = messages.iterator();
checkMessageEquality(message, it.next());
checkMessageEquality(message1, it.next());
checkMessageEquality(message2, it.next());
checkMessageEquality(message3, it.next());
// Read the offer
assertTrue(reader.hasOffer());
Offer o = reader.readOffer();
Collection<MessageId> offered = o.getMessageIds();
assertEquals(4, offered.size());
Iterator<MessageId> it1 = offered.iterator();
assertEquals(message.getId(), it1.next());
assertEquals(message1.getId(), it1.next());
assertEquals(message2.getId(), it1.next());
assertEquals(message3.getId(), it1.next());
// Read the request
assertTrue(reader.hasRequest());
Request req = reader.readRequest();
BitSet requested = req.getBitmap();
assertFalse(requested.get(0));
assertTrue(requested.get(1));
assertFalse(requested.get(2));
assertTrue(requested.get(3));
// If there are any padding bits, they should all be zero
assertEquals(2, requested.cardinality());
// Read the subscription update
assertTrue(reader.hasSubscriptionUpdate());
SubscriptionUpdate s = reader.readSubscriptionUpdate();
Map<Group, Long> subs = s.getSubscriptions();
assertEquals(2, subs.size());
assertEquals(Long.valueOf(0L), subs.get(group));
assertEquals(Long.valueOf(0L), subs.get(group1));
assertTrue(s.getTimestamp() == timestamp);
// Read the transport update
assertTrue(reader.hasTransportUpdate());
TransportUpdate t = reader.readTransportUpdate();
assertEquals(transports, t.getTransports());
assertTrue(t.getTimestamp() == timestamp);
in.close();
}
private void checkMessageEquality(Message m1, Message m2) {
assertEquals(m1.getId(), m2.getId());
assertEquals(m1.getParent(), m2.getParent());
assertEquals(m1.getGroup(), m2.getGroup());
assertEquals(m1.getAuthor(), m2.getAuthor());
assertEquals(m1.getTimestamp(), m2.getTimestamp());
assertArrayEquals(m1.getSerialised(), m2.getSerialised());
}
}

View File

@@ -1,38 +0,0 @@
package net.sf.briar;
import java.io.File;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseDirectory;
import net.sf.briar.api.db.DatabaseMaxSize;
import net.sf.briar.api.db.DatabasePassword;
import com.google.inject.AbstractModule;
public class TestDatabaseModule extends AbstractModule {
private final File dir;
private final Password password;
public TestDatabaseModule() {
this(new File("."));
}
public TestDatabaseModule(File dir) {
this.dir = dir;
this.password = new Password() {
public char[] getPassword() {
return "foo bar".toCharArray();
}
};
}
@Override
protected void configure() {
bind(File.class).annotatedWith(DatabaseDirectory.class).toInstance(dir);
bind(Password.class).annotatedWith(
DatabasePassword.class).toInstance(password);
bind(long.class).annotatedWith(
DatabaseMaxSize.class).toInstance(Long.MAX_VALUE);
}
}

View File

@@ -1,75 +0,0 @@
package net.sf.briar;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;
import net.sf.briar.api.protocol.UniqueId;
public class TestUtils {
private static final AtomicInteger nextTestDir =
new AtomicInteger((int) (Math.random() * 1000 * 1000));
private static final Random random = new Random();
public static void delete(File f) {
if(f.isDirectory()) for(File child : f.listFiles()) delete(child);
f.delete();
}
public static void createFile(File f, String s) throws IOException {
f.getParentFile().mkdirs();
PrintStream out = new PrintStream(new FileOutputStream(f));
out.print(s);
out.flush();
out.close();
}
public static File getTestDirectory() {
int name = nextTestDir.getAndIncrement();
File testDir = new File("test.tmp/" + name);
return testDir;
}
public static void deleteTestDirectory(File testDir) {
delete(testDir);
testDir.getParentFile().delete(); // Delete if empty
}
public static File getBuildDirectory() {
File build = new File("build"); // Ant
if(build.exists() && build.isDirectory()) return build;
File bin = new File("bin"); // Eclipse
if(bin.exists() && bin.isDirectory()) return bin;
throw new RuntimeException("Could not find build directory");
}
public static File getFontDirectory() {
File f = new File("i18n");
if(f.exists() && f.isDirectory()) return f;
f = new File("../i18n");
if(f.exists() && f.isDirectory()) return f;
throw new RuntimeException("Could not find font directory");
}
public static byte[] getRandomId() {
byte[] b = new byte[UniqueId.LENGTH];
random.nextBytes(b);
return b;
}
public static void readFully(InputStream in, byte[] b) throws IOException {
int offset = 0;
while(offset < b.length) {
int read = in.read(b, offset, b.length - offset);
if(read == -1) break;
offset += read;
}
TestCase.assertEquals(b.length, offset);
}
}

View File

@@ -1,156 +0,0 @@
package net.sf.briar.crypto;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.Bytes;
import org.junit.Test;
import org.spongycastle.jce.provider.BouncyCastleProvider;
public class CounterModeTest extends BriarTestCase {
private static final String CIPHER_ALGO = "AES";
private static final String CIPHER_MODE = "AES/CTR/NoPadding";
private static final String PROVIDER = "SC";
private static final int KEY_SIZE_BYTES = 32; // AES-256
private static final int BLOCK_SIZE_BYTES = 16;
private final SecureRandom random;
private final byte[] keyBytes;
private final SecretKeySpec key;
public CounterModeTest() {
super();
Security.addProvider(new BouncyCastleProvider());
random = new SecureRandom();
keyBytes = new byte[KEY_SIZE_BYTES];
random.nextBytes(keyBytes);
key = new SecretKeySpec(keyBytes, CIPHER_ALGO);
}
@Test
public void testEveryBitOfIvIsSignificant()
throws GeneralSecurityException {
// Set each bit of the IV in turn, encrypt the same plaintext and check
// that all the resulting ciphertexts are distinct
byte[] plaintext = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(plaintext);
Set<Bytes> ciphertexts = new HashSet<Bytes>();
for(int i = 0; i < BLOCK_SIZE_BYTES * 8; i++) {
// Set the i^th bit of the IV
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
ivBytes[i / 8] |= (byte) (128 >> i % 8);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// Encrypt the plaintext
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext =
new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
ciphertexts.add(new Bytes(ciphertext));
}
// All the ciphertexts should be distinct using Arrays.equals()
assertEquals(BLOCK_SIZE_BYTES * 8, ciphertexts.size());
}
@Test
public void testRepeatedIvsProduceRepeatedCiphertexts()
throws GeneralSecurityException {
// This is the inverse of the previous test, to check that the
// distinct ciphertexts were due to using distinct IVs
byte[] plaintext = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(plaintext);
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Set<Bytes> ciphertexts = new HashSet<Bytes>();
for(int i = 0; i < BLOCK_SIZE_BYTES * 8; i++) {
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext =
new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
ciphertexts.add(new Bytes(ciphertext));
}
assertEquals(1, ciphertexts.size());
}
@Test
public void testLeastSignificantBitsUsedAsCounter()
throws GeneralSecurityException {
// Initialise the least significant 16 bits of the IV to zero and
// encrypt ten blocks of zeroes
byte[] plaintext = new byte[BLOCK_SIZE_BYTES * 10];
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(ivBytes);
ivBytes[BLOCK_SIZE_BYTES - 2] = 0;
ivBytes[BLOCK_SIZE_BYTES - 1] = 0;
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
// Make sure the IV array hasn't been modified
assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 2]);
assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 1]);
// Initialise the least significant 16 bits of the IV to one and
// encrypt another ten blocks of zeroes
ivBytes[BLOCK_SIZE_BYTES - 1] = 1;
iv = new IvParameterSpec(ivBytes);
cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1);
// The last nine blocks of the first ciphertext should be identical to
// the first nine blocks of the second ciphertext
for(int i = 0; i < BLOCK_SIZE_BYTES * 9; i++) {
assertEquals(ciphertext[i + BLOCK_SIZE_BYTES], ciphertext1[i]);
}
}
@Test
public void testCounterUsesMoreThan16Bits()
throws GeneralSecurityException {
// Initialise the least significant bits of the IV to 2^16-1 and
// encrypt ten blocks of zeroes
byte[] plaintext = new byte[BLOCK_SIZE_BYTES * 10];
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(ivBytes);
ivBytes[BLOCK_SIZE_BYTES - 3] = 0;
ivBytes[BLOCK_SIZE_BYTES - 2] = (byte) 255;
ivBytes[BLOCK_SIZE_BYTES - 1] = (byte) 255;
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
// Make sure the IV array hasn't been modified
assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 3]);
assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 2]);
assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 1]);
// Initialise the least significant bits of the IV to 2^16 and
// encrypt another ten blocks of zeroes
ivBytes[BLOCK_SIZE_BYTES - 3] = 1;
ivBytes[BLOCK_SIZE_BYTES - 2] = 0;
ivBytes[BLOCK_SIZE_BYTES - 1] = 0;
iv = new IvParameterSpec(ivBytes);
cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1);
// The last nine blocks of the first ciphertext should be identical to
// the first nine blocks of the second ciphertext
for(int i = 0; i < BLOCK_SIZE_BYTES * 9; i++) {
assertEquals(ciphertext[i + BLOCK_SIZE_BYTES], ciphertext1[i]);
}
}
}

View File

@@ -1,79 +0,0 @@
package net.sf.briar.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.ErasableKey;
import org.junit.Test;
public class ErasableKeyTest extends BriarTestCase {
private static final String CIPHER = "AES";
private static final String CIPHER_MODE = "AES/CTR/NoPadding";
private static final int IV_BYTES = 16; // 128 bits
private static final int KEY_BYTES = 32; // 256 bits
private static final String MAC = "HMacSHA384";
private final Random random = new Random();
@Test
public void testCopiesAreErased() {
byte[] master = new byte[KEY_BYTES];
random.nextBytes(master);
ErasableKey k = new ErasableKeyImpl(master, CIPHER);
byte[] copy = k.getEncoded();
assertArrayEquals(master, copy);
k.erase();
byte[] blank = new byte[KEY_BYTES];
assertArrayEquals(blank, master);
assertArrayEquals(blank, copy);
}
@Test
public void testErasureDoesNotAffectCipher() throws Exception {
byte[] key = new byte[KEY_BYTES];
random.nextBytes(key);
ErasableKey k = new ErasableKeyImpl(key, CIPHER);
Cipher c = Cipher.getInstance(CIPHER_MODE);
IvParameterSpec iv = new IvParameterSpec(new byte[IV_BYTES]);
c.init(Cipher.ENCRYPT_MODE, k, iv);
// Encrypt a blank plaintext
byte[] plaintext = new byte[123];
byte[] ciphertext = c.doFinal(plaintext);
// Erase the key and encrypt again - erase() was called after doFinal()
k.erase();
byte[] ciphertext1 = c.doFinal(plaintext);
// Encrypt again - this time erase() was called before doFinal()
byte[] ciphertext2 = c.doFinal(plaintext);
// The ciphertexts should match
assertArrayEquals(ciphertext, ciphertext1);
assertArrayEquals(ciphertext, ciphertext2);
}
@Test
public void testErasureDoesNotAffectMac() throws Exception {
byte[] key = new byte[KEY_BYTES];
random.nextBytes(key);
ErasableKey k = new ErasableKeyImpl(key, CIPHER);
Mac m = Mac.getInstance(MAC);
m.init(k);
// Authenticate a blank plaintext
byte[] plaintext = new byte[123];
byte[] mac = m.doFinal(plaintext);
// Erase the key and authenticate again
k.erase();
byte[] mac1 = m.doFinal(plaintext);
// Authenticate again
byte[] mac2 = m.doFinal(plaintext);
// The MACs should match
assertArrayEquals(mac, mac1);
assertArrayEquals(mac, mac2);
}
}

View File

@@ -1,28 +0,0 @@
package net.sf.briar.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.security.KeyPair;
import java.security.PrivateKey;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import org.junit.Test;
public class KeyAgreementTest extends BriarTestCase {
@Test
public void testKeyAgreement() {
CryptoComponent crypto = new CryptoComponentImpl();
KeyPair a = crypto.generateAgreementKeyPair();
byte[] aPub = a.getPublic().getEncoded();
PrivateKey aPriv = a.getPrivate();
KeyPair b = crypto.generateAgreementKeyPair();
byte[] bPub = b.getPublic().getEncoded();
PrivateKey bPriv = b.getPrivate();
byte[] aSecret = crypto.deriveInitialSecret(aPub, bPub, aPriv, true);
byte[] bSecret = crypto.deriveInitialSecret(bPub, aPub, bPriv, false);
assertArrayEquals(aSecret, bSecret);
}
}

View File

@@ -1,76 +0,0 @@
package net.sf.briar.crypto;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import org.junit.Test;
public class KeyDerivationTest extends BriarTestCase {
private final CryptoComponent crypto;
private final byte[] secret;
public KeyDerivationTest() {
super();
crypto = new CryptoComponentImpl();
secret = new byte[32];
new Random().nextBytes(secret);
}
@Test
public void testKeysAreDistinct() {
List<ErasableKey> keys = new ArrayList<ErasableKey>();
keys.add(crypto.deriveFrameKey(secret, 0, false, false));
keys.add(crypto.deriveFrameKey(secret, 0, false, true));
keys.add(crypto.deriveFrameKey(secret, 0, true, false));
keys.add(crypto.deriveFrameKey(secret, 0, true, true));
keys.add(crypto.deriveTagKey(secret, true));
keys.add(crypto.deriveTagKey(secret, false));
for(int i = 0; i < 4; i++) {
byte[] keyI = keys.get(i).getEncoded();
for(int j = 0; j < 4; j++) {
byte[] keyJ = keys.get(j).getEncoded();
assertEquals(i == j, Arrays.equals(keyI, keyJ));
}
}
}
@Test
public void testSecretAffectsDerivation() {
Random r = new Random();
List<byte[]> secrets = new ArrayList<byte[]>();
for(int i = 0; i < 20; i++) {
byte[] b = new byte[32];
r.nextBytes(b);
secrets.add(crypto.deriveNextSecret(b, 0));
}
for(int i = 0; i < 20; i++) {
byte[] secretI = secrets.get(i);
for(int j = 0; j < 20; j++) {
byte[] secretJ = secrets.get(j);
assertEquals(i == j, Arrays.equals(secretI, secretJ));
}
}
}
@Test
public void testConnectionNumberAffectsDerivation() {
List<byte[]> secrets = new ArrayList<byte[]>();
for(int i = 0; i < 20; i++) {
secrets.add(crypto.deriveNextSecret(secret.clone(), i));
}
for(int i = 0; i < 20; i++) {
byte[] secretI = secrets.get(i);
for(int j = 0; j < 20; j++) {
byte[] secretJ = secrets.get(j);
assertEquals(i == j, Arrays.equals(secretI, secretJ));
}
}
}
}

View File

@@ -1,192 +0,0 @@
package net.sf.briar.db;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class BasicH2Test extends BriarTestCase {
private static final String CREATE_TABLE =
"CREATE TABLE foo"
+ " (uniqueId BINARY(32),"
+ " name VARCHAR NOT NULL)";
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
private final String url = "jdbc:h2:" + db.getPath();
private Connection connection = null;
@Before
public void setUp() throws Exception {
testDir.mkdirs();
Class.forName("org.h2.Driver");
connection = DriverManager.getConnection(url);
}
@Test
public void testCreateTableAndAddRow() throws Exception {
// Create the table
createTable(connection);
// Generate an ID
byte[] id = new byte[32];
new Random().nextBytes(id);
// Insert the ID and name into the table
addRow(id, "foo");
}
@Test
public void testCreateTableAddAndRetrieveRow() throws Exception {
// Create the table
createTable(connection);
// Generate an ID
byte[] id = new byte[32];
new Random().nextBytes(id);
// Insert the ID and name into the table
addRow(id, "foo");
// Check that the name can be retrieved using the ID
assertEquals("foo", getName(id));
}
@Test
public void testSortOrder() throws Exception {
byte[] first = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, -128
};
byte[] second = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
byte[] third = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 127
};
// Create the table
createTable(connection);
// Insert the rows
addRow(first, "first");
addRow(second, "second");
addRow(third, "third");
addRow(null, "null");
// Check the ordering of the < operator: the null ID is not comparable
assertNull(getPredecessor(first));
assertEquals("first", getPredecessor(second));
assertEquals("second", getPredecessor(third));
assertNull(getPredecessor(null));
// Check the ordering of ORDER BY: nulls come first
List<String> names = getNames();
assertEquals(4, names.size());
assertEquals("null", names.get(0));
assertEquals("first", names.get(1));
assertEquals("second", names.get(2));
assertEquals("third", names.get(3));
}
private void createTable(Connection connection) throws SQLException {
try {
Statement s = connection.createStatement();
s.executeUpdate(CREATE_TABLE);
s.close();
} catch(SQLException e) {
connection.close();
throw e;
}
}
private void addRow(byte[] id, String name) throws SQLException {
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if(id == null) ps.setNull(1, Types.BINARY);
else ps.setBytes(1, id);
ps.setString(2, name);
int rowsAffected = ps.executeUpdate();
ps.close();
assertEquals(1, rowsAffected);
} catch(SQLException e) {
connection.close();
throw e;
}
}
private String getName(byte[] id) throws SQLException {
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if(id != null) ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
String name = rs.getString(1);
assertFalse(rs.next());
rs.close();
ps.close();
return name;
} catch(SQLException e) {
connection.close();
throw e;
}
}
private String getPredecessor(byte[] id) throws SQLException {
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
+ " ORDER BY uniqueId DESC LIMIT ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ps.setInt(2, 1);
ResultSet rs = ps.executeQuery();
String name = rs.next() ? rs.getString(1) : null;
assertFalse(rs.next());
rs.close();
ps.close();
return name;
} catch(SQLException e) {
connection.close();
throw e;
}
}
private List<String> getNames() throws SQLException {
String sql = "SELECT name FROM foo ORDER BY uniqueId";
List<String> names = new ArrayList<String>();
try {
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while(rs.next()) names.add(rs.getString(1));
rs.close();
ps.close();
return names;
} catch(SQLException e) {
connection.close();
throw e;
}
}
@After
public void tearDown() throws Exception {
if(connection != null) connection.close();
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -1,61 +0,0 @@
package net.sf.briar.db;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.db.DbException;
import net.sf.briar.db.DatabaseCleaner.Callback;
import org.junit.Test;
public class DatabaseCleanerImplTest extends BriarTestCase {
@Test
public void testCleanerRunsPeriodically() throws Exception {
final CountDownLatch latch = new CountDownLatch(5);
Callback callback = new Callback() {
public void checkFreeSpaceAndClean() throws DbException {
latch.countDown();
}
public boolean shouldCheckFreeSpace() {
return true;
}
};
DatabaseCleanerImpl cleaner = new DatabaseCleanerImpl();
// Start the cleaner
cleaner.startCleaning(callback, 10L);
// The database should be cleaned five times (allow 5s for system load)
assertTrue(latch.await(5, TimeUnit.SECONDS));
// Stop the cleaner
cleaner.stopCleaning();
}
@Test
public void testStoppingCleanerWakesItUp() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
Callback callback = new Callback() {
public void checkFreeSpaceAndClean() throws DbException {
latch.countDown();
}
public boolean shouldCheckFreeSpace() {
return true;
}
};
DatabaseCleanerImpl cleaner = new DatabaseCleanerImpl();
long start = System.currentTimeMillis();
// Start the cleaner
cleaner.startCleaning(callback, 10L * 1000L);
// The database should be cleaned once at startup
assertTrue(latch.await(5, TimeUnit.SECONDS));
// Stop the cleaner (it should be waiting between sweeps)
cleaner.stopCleaning();
long end = System.currentTimeMillis();
// Check that much less than 10 seconds expired
assertTrue(end - start < 10L * 1000L);
}
}

View File

@@ -1,151 +0,0 @@
package net.sf.briar.db;
import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP;
import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE;
import java.util.Collections;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.db.DatabaseCleaner.Callback;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
/**
* Tests that use the DatabaseCleaner.Callback interface of
* DatabaseComponentImpl.
*/
public class DatabaseComponentImplTest extends DatabaseComponentTest {
@Test
public void testNotCleanedIfEnoughFreeSpace() throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final PacketFactory packetFactory = context.mock(PacketFactory.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testCleanedIfNotEnoughFreeSpace() throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final PacketFactory packetFactory = context.mock(PacketFactory.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1));
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
will(returnValue(Collections.emptyList()));
oneOf(database).commitTransaction(txn);
// As if by magic, some free space has appeared
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testExpiringUnsendableMessageDoesNotTriggerBackwardInclusion()
throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final PacketFactory packetFactory = context.mock(PacketFactory.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1));
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
will(returnValue(Collections.singletonList(messageId)));
oneOf(database).getSendability(txn, messageId);
will(returnValue(0));
oneOf(database).removeMessage(txn, messageId);
oneOf(database).commitTransaction(txn);
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testExpiringSendableMessageTriggersBackwardInclusion()
throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final PacketFactory packetFactory = context.mock(PacketFactory.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1));
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
will(returnValue(Collections.singletonList(messageId)));
oneOf(database).getSendability(txn, messageId);
will(returnValue(1));
oneOf(database).getGroupMessageParent(txn, messageId);
will(returnValue(null));
oneOf(database).removeMessage(txn, messageId);
oneOf(database).commitTransaction(txn);
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Override
protected <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner,
ShutdownManager shutdown, PacketFactory packetFactory) {
return createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
}
private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner,
ShutdownManager shutdown, PacketFactory packetFactory) {
return new DatabaseComponentImpl<T>(database, cleaner, shutdown,
packetFactory, new SystemClock());
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
package net.sf.briar.db;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupId;
class TestGroup implements Group {
private final GroupId id;
private final String name;
private final byte[] publicKey;
public TestGroup(GroupId id, String name, byte[] publicKey) {
this.id = id;
this.name = name;
this.publicKey = publicKey;
}
public GroupId getId() {
return id;
}
public String getName() {
return name;
}
public byte[] getPublicKey() {
return publicKey;
}
}

View File

@@ -1,20 +0,0 @@
package net.sf.briar.db;
import java.io.IOException;
import net.sf.briar.TestUtils;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.GroupId;
class TestGroupFactory implements GroupFactory {
public Group createGroup(String name, byte[] publicKey) throws IOException {
GroupId id = new GroupId(TestUtils.getRandomId());
return new TestGroup(id, name, publicKey);
}
public Group createGroup(GroupId id, String name, byte[] publicKey) {
return new TestGroup(id, name, publicKey);
}
}

View File

@@ -1,89 +0,0 @@
package net.sf.briar.db;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId;
class TestMessage implements Message {
private final MessageId id, parent;
private final GroupId group;
private final AuthorId author;
private final String subject;
private final long timestamp;
private final byte[] raw;
private final int bodyStart, bodyLength;
public TestMessage(MessageId id, MessageId parent, GroupId group,
AuthorId author, String subject, long timestamp, byte[] raw) {
this(id, parent, group, author, subject, timestamp, raw, 0, raw.length);
}
public TestMessage(MessageId id, MessageId parent, GroupId group,
AuthorId author, String subject, long timestamp, byte[] raw,
int bodyStart, int bodyLength) {
this.id = id;
this.parent = parent;
this.group = group;
this.author = author;
this.subject = subject;
this.timestamp = timestamp;
this.raw = raw;
this.bodyStart = bodyStart;
this.bodyLength = bodyLength;
}
public MessageId getId() {
return id;
}
public MessageId getParent() {
return parent;
}
public GroupId getGroup() {
return group;
}
public AuthorId getAuthor() {
return author;
}
public String getSubject() {
return subject;
}
public long getTimestamp() {
return timestamp;
}
public byte[] getSerialised() {
return raw;
}
public int getBodyStart() {
return bodyStart;
}
public int getBodyLength() {
return bodyLength;
}
public InputStream getSerialisedStream() {
return new ByteArrayInputStream(raw);
}
@Override
public boolean equals(Object o) {
return o instanceof Message && id.equals(((Message)o).getId());
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@@ -1,33 +0,0 @@
package net.sf.briar.lifecycle;
import java.util.HashSet;
import java.util.Set;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.lifecycle.ShutdownManager;
import org.junit.Test;
public class ShutdownManagerImplTest extends BriarTestCase {
@Test
public void testAddAndRemove() {
ShutdownManager s = createShutdownManager();
Set<Integer> handles = new HashSet<Integer>();
for(int i = 0; i < 100; i++) {
int handle = s.addShutdownHook(new Runnable() {
public void run() {}
});
// The handles should all be distinct
assertTrue(handles.add(handle));
}
// The hooks should be removable
for(int handle : handles) assertTrue(s.removeShutdownHook(handle));
// The hooks should no longer be removable
for(int handle : handles) assertFalse(s.removeShutdownHook(handle));
}
protected ShutdownManager createShutdownManager() {
return new ShutdownManagerImpl();
}
}

View File

@@ -1,39 +0,0 @@
package net.sf.briar.lifecycle;
import net.sf.briar.api.lifecycle.ShutdownManager;
import org.junit.Test;
public class WindowsShutdownManagerImplTest extends ShutdownManagerImplTest {
@Override
protected ShutdownManager createShutdownManager() {
return new WindowsShutdownManagerImpl();
}
@Test
public void testManagerWaitsForHooksToRun() {
WindowsShutdownManagerImpl s = new WindowsShutdownManagerImpl();
SlowHook[] hooks = new SlowHook[10];
for(int i = 0; i < hooks.length; i++) {
hooks[i] = new SlowHook();
s.addShutdownHook(hooks[i]);
}
s.runShutdownHooks();
for(int i = 0; i < hooks.length; i++) assertTrue(hooks[i].finished);
}
private static class SlowHook implements Runnable {
private volatile boolean finished = false;
public void run() {
try {
Thread.sleep(100);
finished = true;
} catch(InterruptedException e) {
fail();
}
}
}
}

View File

@@ -1,101 +0,0 @@
package net.sf.briar.plugins;
import java.io.IOException;
import java.util.Map;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
public abstract class DuplexClientTest extends DuplexTest {
protected ClientCallback callback = null;
protected void run() throws IOException {
assert plugin != null;
// Start the plugin
System.out.println("Starting plugin");
plugin.start();
// Try to connect to the server
System.out.println("Creating connection");
DuplexTransportConnection d = plugin.createConnection(contactId);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
receiveChallengeSendResponse(d);
}
// Try to send an invitation
System.out.println("Sending invitation");
d = plugin.sendInvitation(getPseudoRandom(123), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
receiveChallengeSendResponse(d);
}
// Try to accept an invitation
System.out.println("Accepting invitation");
d = plugin.acceptInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
sendChallengeReceiveResponse(d);
}
// Stop the plugin
System.out.println("Stopping plugin");
plugin.stop();
}
protected static class ClientCallback implements DuplexPluginCallback {
private TransportConfig config = null;
private TransportProperties local = null;
private Map<ContactId, TransportProperties> remote = null;
public ClientCallback(TransportConfig config, TransportProperties local,
Map<ContactId, TransportProperties> remote) {
this.config = config;
this.local = local;
this.remote = remote;
}
public TransportConfig getConfig() {
return config;
}
public TransportProperties getLocalProperties() {
return local;
}
public Map<ContactId, TransportProperties> getRemoteProperties() {
return remote;
}
public void setConfig(TransportConfig c) {
config = c;
}
public void setLocalProperties(TransportProperties p) {
local = p;
}
public int showChoice(String[] options, String... message) {
return -1;
}
public boolean showConfirmationMessage(String... message) {
return false;
}
public void showMessage(String... message) {}
public void incomingConnectionCreated(DuplexTransportConnection d) {}
public void outgoingConnectionCreated(ContactId contactId,
DuplexTransportConnection d) {}
}
}

View File

@@ -1,103 +0,0 @@
package net.sf.briar.plugins;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
public abstract class DuplexServerTest extends DuplexTest {
protected ServerCallback callback = null;
protected void run() throws Exception {
assert callback != null;
assert plugin != null;
// Start the plugin
System.out.println("Starting plugin");
plugin.start();
// Wait for a connection
System.out.println("Waiting for connection");
callback.latch.await();
// Try to accept an invitation
System.out.println("Accepting invitation");
DuplexTransportConnection d = plugin.acceptInvitation(
getPseudoRandom(123), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
sendChallengeReceiveResponse(d);
}
// Try to send an invitation
System.out.println("Sending invitation");
d = plugin.sendInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
receiveChallengeSendResponse(d);
}
// Stop the plugin
System.out.println("Stopping plugin");
plugin.stop();
}
protected class ServerCallback implements DuplexPluginCallback {
private final CountDownLatch latch = new CountDownLatch(1);
private TransportConfig config;
private TransportProperties local;
private Map<ContactId, TransportProperties> remote;
public ServerCallback(TransportConfig config, TransportProperties local,
Map<ContactId, TransportProperties> remote) {
this.config = config;
this.local = local;
this.remote = remote;
}
public TransportConfig getConfig() {
return config;
}
public TransportProperties getLocalProperties() {
return local;
}
public Map<ContactId, TransportProperties> getRemoteProperties() {
return remote;
}
public void setConfig(TransportConfig c) {
config = c;
}
public void setLocalProperties(TransportProperties p) {
local = p;
}
public int showChoice(String[] options, String... message) {
return -1;
}
public boolean showConfirmationMessage(String... message) {
return false;
}
public void showMessage(String... message) {}
public void incomingConnectionCreated(DuplexTransportConnection d) {
System.out.println("Connection received");
sendChallengeReceiveResponse(d);
latch.countDown();
}
public void outgoingConnectionCreated(ContactId c,
DuplexTransportConnection d) {}
}
}

View File

@@ -1,98 +0,0 @@
package net.sf.briar.plugins;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Random;
import java.util.Scanner;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
abstract class DuplexTest {
protected static final String CHALLENGE = "Carrots!";
protected static final String RESPONSE = "Potatoes!";
protected static final long INVITATION_TIMEOUT = 30 * 1000;
protected final ContactId contactId = new ContactId(234);
protected DuplexPlugin plugin = null;
protected void sendChallengeReceiveResponse(DuplexTransportConnection d) {
assert plugin != null;
try {
PrintStream out = new PrintStream(d.getOutputStream());
out.println(CHALLENGE);
System.out.println("Sent challenge: " + CHALLENGE);
Scanner in = new Scanner(d.getInputStream());
if(in.hasNextLine()) {
String response = in.nextLine();
System.out.println("Received response: " + response);
if(RESPONSE.equals(response)) {
System.out.println("Correct response");
} else {
System.out.println("Incorrect response");
}
} else {
System.out.println("No response");
}
d.dispose(false, true);
} catch(IOException e) {
e.printStackTrace();
try {
d.dispose(true, true);
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
protected void receiveChallengeSendResponse(DuplexTransportConnection d) {
assert plugin != null;
try {
Scanner in = new Scanner(d.getInputStream());
if(in.hasNextLine()) {
String challenge = in.nextLine();
System.out.println("Received challenge: " + challenge);
if(CHALLENGE.equals(challenge)) {
PrintStream out = new PrintStream(d.getOutputStream());
out.println(RESPONSE);
System.out.println("Sent response: " + RESPONSE);
} else {
System.out.println("Incorrect challenge");
}
} else {
System.out.println("No challenge");
}
d.dispose(false, true);
} catch(IOException e) {
e.printStackTrace();
try {
d.dispose(true, true);
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
protected PseudoRandom getPseudoRandom(int seed) {
return new TestPseudoRandom(seed);
}
private static class TestPseudoRandom implements PseudoRandom {
private final Random r;
private TestPseudoRandom(int seed) {
r = new Random(seed);
}
public byte[] nextBytes(int bytes) {
byte[] b = new byte[bytes];
r.nextBytes(b);
return b;
}
}
}

View File

@@ -1,10 +0,0 @@
package net.sf.briar.plugins;
import java.util.concurrent.Executor;
public class ImmediateExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}

View File

@@ -1,54 +0,0 @@
package net.sf.briar.plugins;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionDispatcher;
import net.sf.briar.api.ui.UiCallback;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
public class PluginManagerImplTest extends BriarTestCase {
@SuppressWarnings("unchecked")
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Poller poller = context.mock(Poller.class);
final ConnectionDispatcher dispatcher =
context.mock(ConnectionDispatcher.class);
final UiCallback uiCallback = context.mock(UiCallback.class);
context.checking(new Expectations() {{
oneOf(poller).start(with(any(Collection.class)));
allowing(db).getConfig(with(any(TransportId.class)));
will(returnValue(new TransportConfig()));
allowing(db).getLocalProperties(with(any(TransportId.class)));
will(returnValue(new TransportProperties()));
allowing(db).getRemoteProperties(with(any(TransportId.class)));
will(returnValue(new TransportProperties()));
allowing(db).setLocalProperties(with(any(TransportId.class)),
with(any(TransportProperties.class)));
oneOf(poller).stop();
}});
ExecutorService executor = Executors.newCachedThreadPool();
PluginManagerImpl p = new PluginManagerImpl(executor, db, poller,
dispatcher, uiCallback);
// We expect either 3 or 4 plugins to be started, depending on whether
// the test machine has a Bluetooth device
int started = p.start();
int stopped = p.stop();
assertEquals(started, stopped);
assertTrue(started >= 2);
assertTrue(started <= 3);
context.assertIsSatisfied();
}
}

View File

@@ -1,44 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.plugins.DuplexClientTest;
// This is not a JUnit test - it has to be run manually while the server test
// is running on another machine
public class BluetoothClientTest extends DuplexClientTest {
private BluetoothClientTest(Executor executor, String serverAddress) {
// Store the server's Bluetooth address and UUID
TransportProperties p = new TransportProperties();
p.put("address", serverAddress);
p.put("uuid", BluetoothTest.getUuid());
Map<ContactId, TransportProperties> remote =
Collections.singletonMap(contactId, p);
// Create the plugin
callback = new ClientCallback(new TransportConfig(),
new TransportProperties(), remote);
plugin = new BluetoothPlugin(executor, new SystemClock(), callback, 0L);
}
public static void main(String[] args) throws Exception {
if(args.length != 1) {
System.err.println("Please specify the server's Bluetooth address");
System.exit(1);
}
ExecutorService executor = Executors.newCachedThreadPool();
try {
new BluetoothClientTest(executor, args[0]).run();
} finally {
executor.shutdown();
}
}
}

View File

@@ -1,35 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.plugins.DuplexServerTest;
// This is not a JUnit test - it has to be run manually while the client test
// is running on another machine
public class BluetoothServerTest extends DuplexServerTest {
private BluetoothServerTest(Executor executor) {
// Store the UUID
TransportProperties local = new TransportProperties();
local.put("uuid", BluetoothTest.getUuid());
// Create the plugin
callback = new ServerCallback(new TransportConfig(), local,
Collections.singletonMap(contactId, new TransportProperties()));
plugin = new BluetoothPlugin(executor, new SystemClock(), callback, 0L);
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
try {
new BluetoothServerTest(executor).run();
} finally {
executor.shutdown();
}
}
}

View File

@@ -1,13 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.util.UUID;
class BluetoothTest {
private static final String EMPTY_UUID =
UUID.nameUUIDFromBytes(new byte[0]).toString().replaceAll("-", "");
static String getUuid() {
return EMPTY_UUID;
}
}

View File

@@ -1,123 +0,0 @@
package net.sf.briar.plugins.email;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
import org.junit.Before;
import org.junit.Test;
/*
* Uses environment variables USER_GMAIL_ADDRESS, GMAIL_USERNAME, GMAIL_PASSWORD,
* and CONTACT1_EMAIL - (as recipient email address)
*/
public class GmailPluginTest {
SimplexPluginCallback callback;
TransportProperties local, props1;
TransportConfig config;
ContactId test1;
Map<ContactId, TransportProperties> map = new HashMap<ContactId, TransportProperties>();
@Before
public void setup() {
local = new TransportProperties();
local.put("email", System.getenv("USER_GMAIL_ADDRESS"));
config = new TransportConfig();
config.put("username", System.getenv("GMAIL_USERNAME"));
config.put("password", System.getenv("GMAIL_PASSWORD"));
props1 = new TransportProperties();
props1.put("email", System.getenv("CONTACT1_EMAIL"));
test1 = new ContactId(234);
map.put(test1, props1);
assertEquals(1, map.size());
callback = new SimplexPluginCallback() {
public void showMessage(String... message) {}
public boolean showConfirmationMessage(String... message) {
return false;
}
public int showChoice(String[] options, String... message) {
return 0;
}
public void setLocalProperties(TransportProperties p) {
local = p;
}
public void setConfig(TransportConfig c) {
config = c;
}
public Map<ContactId, TransportProperties> getRemoteProperties() {
return map;
}
public TransportProperties getLocalProperties() {
return local;
}
public TransportConfig getConfig() {
return config;
}
public void writerCreated(ContactId c, SimplexTransportWriter w) {}
public void readerCreated(SimplexTransportReader r) {}
};
callback.setLocalProperties(local);
callback.setConfig(config);
}
@Test
public void testGetID() throws IOException {
GmailPlugin pluginTest = new GmailPlugin(
Executors.newSingleThreadExecutor(), callback);
assertArrayEquals(GmailPlugin.TRANSPORT_ID, pluginTest.getId()
.getBytes());
}
@Test
public void testGmailPluginIMAP() throws IOException {
GmailPlugin pluginTest = new GmailPlugin(
Executors.newSingleThreadExecutor(), callback);
try {
pluginTest.start();
} catch (IOException e) {
System.out.println("IO Exception got caught");
fail();
}
finally{
pluginTest.stop();
}
}
@Test
public void testGmailSMTP() throws IOException {
GmailPlugin pluginTest = new GmailPlugin(
Executors.newSingleThreadExecutor(), callback);
assertEquals(true, pluginTest.connectSMTP(test1));
assertEquals(false, pluginTest.connectSMTP(new ContactId(123)));
pluginTest.stop();
}
}

View File

@@ -1,25 +0,0 @@
package net.sf.briar.plugins.file;
import net.sf.briar.BriarTestCase;
import org.junit.Test;
public class LinuxRemovableDriveFinderTest extends BriarTestCase {
@Test
public void testParseMountPoint() {
LinuxRemovableDriveFinder f = new LinuxRemovableDriveFinder();
String line = "/dev/sda3 on / type ext3"
+ " (rw,errors=remount-ro,commit=0)";
assertEquals("/", f.parseMountPoint(line));
line = "gvfs-fuse-daemon on /home/alice/.gvfs"
+ " type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=alice)";
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
line = "fusectl on /sys/fs/fuse/connections type fusectl (rw)";
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
line = "/dev/sdd1 on /media/HAZ SPACE(!) type vfat"
+ " (rw,nosuid,nodev,uhelper=udisks,uid=1000,gid=1000,"
+ "shortname=mixed,dmask=0077,utf8=1,showexec,flush)";
assertEquals("/media/HAZ SPACE(!)", f.parseMountPoint(line));
}
}

View File

@@ -1,23 +0,0 @@
package net.sf.briar.plugins.file;
import net.sf.briar.BriarTestCase;
import org.junit.Test;
public class MacRemovableDriveFinderTest extends BriarTestCase {
@Test
public void testParseMountPoint() {
MacRemovableDriveFinder f = new MacRemovableDriveFinder();
String line = "/dev/disk0s3 on / (local, journaled)";
assertEquals("/", f.parseMountPoint(line));
line = "devfs on /dev (local)";
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
line = "<volfs> on /.vol";
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
line = "automount -nsl [117] on /Network (automounted)";
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
line = "/dev/disk1s1 on /Volumes/HAZ SPACE(!) (local, nodev, nosuid)";
assertEquals("/Volumes/HAZ SPACE(!)", f.parseMountPoint(line));
}
}

View File

@@ -1,94 +0,0 @@
package net.sf.briar.plugins.file;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import net.sf.briar.BriarTestCase;
import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback;
import org.junit.Test;
public class PollingRemovableDriveMonitorTest extends BriarTestCase {
@Test
public void testOneCallbackPerFile() throws Exception {
// Create a finder that returns no files the first time, then two files
final File file1 = new File("foo");
final File file2 = new File("bar");
final RemovableDriveFinder finder = new RemovableDriveFinder() {
private AtomicBoolean firstCall = new AtomicBoolean(true);
public Collection<File> findRemovableDrives() throws IOException {
if(firstCall.getAndSet(false)) return Collections.emptyList();
else return Arrays.asList(file1, file2);
}
};
// Create a callback that waits for two files
final CountDownLatch latch = new CountDownLatch(2);
final List<File> detected = new ArrayList<File>();
Callback callback = new Callback() {
public void driveInserted(File f) {
detected.add(f);
latch.countDown();
}
public void exceptionThrown(IOException e) {
fail();
}
};
// Create the monitor and start it
final RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
Executors.newCachedThreadPool(), finder, 1);
monitor.start(callback);
// Wait for the monitor to detect the files
assertTrue(latch.await(10, TimeUnit.SECONDS));
monitor.stop();
// Check that both files were detected
assertEquals(2, detected.size());
assertTrue(detected.contains(file1));
assertTrue(detected.contains(file2));
}
@Test
public void testExceptionCallback() throws Exception {
// Create a finder that throws an exception the second time it's polled
final RemovableDriveFinder finder = new RemovableDriveFinder() {
private AtomicBoolean firstCall = new AtomicBoolean(true);
public Collection<File> findRemovableDrives() throws IOException {
if(firstCall.getAndSet(false)) return Collections.emptyList();
else throw new IOException();
}
};
// Create a callback that waits for an exception
final CountDownLatch latch = new CountDownLatch(1);
Callback callback = new Callback() {
public void driveInserted(File root) {
fail();
}
public void exceptionThrown(IOException e) {
latch.countDown();
}
};
// Create the monitor and start it
final RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
Executors.newCachedThreadPool(), finder, 1);
monitor.start(callback);
assertTrue(latch.await(10, TimeUnit.SECONDS));
monitor.stop();
}
}

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