mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
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:
27
.classpath
27
.classpath
@@ -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
6
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
/windows-jre
|
||||
/Briar
|
||||
/bin
|
||||
bin
|
||||
gen
|
||||
local.properties
|
||||
|
||||
50
.project
50
.project
@@ -1,17 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>prototype</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>prototype</name>
|
||||
<comment></comment>
|
||||
<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
24
AndroidManifest.xml
Normal 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
BIN
android.jar
Normal file
Binary file not shown.
17
ant.properties
Normal file
17
ant.properties
Normal 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.
|
||||
|
||||
@@ -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
104
build.xml
@@ -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>
|
||||
|
||||
@@ -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
20
proguard-project.txt
Normal 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
14
project.properties
Normal 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
4
res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Briar</string>
|
||||
</resources>
|
||||
2
src/.gitignore
vendored
2
src/.gitignore
vendored
@@ -1 +1 @@
|
||||
/build
|
||||
build
|
||||
|
||||
@@ -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>
|
||||
|
||||
19
src/net/sf/briar/HelloWorldActivity.java
Normal file
19
src/net/sf/briar/HelloWorldActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
59
src/net/sf/briar/HelloWorldModule.java
Normal file
59
src/net/sf/briar/HelloWorldModule.java
Normal 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);
|
||||
}
|
||||
}
|
||||
95
src/net/sf/briar/HelloWorldService.java
Normal file
95
src/net/sf/briar/HelloWorldService.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/net/sf/briar/android/AndroidExecutorImpl.java
Normal file
89
src/net/sf/briar/android/AndroidExecutorImpl.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
src/net/sf/briar/android/AndroidModule.java
Normal file
13
src/net/sf/briar/android/AndroidModule.java
Normal 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);
|
||||
}
|
||||
}
|
||||
17
src/net/sf/briar/api/android/AndroidExecutor.java
Normal file
17
src/net/sf/briar/api/android/AndroidExecutor.java
Normal 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();
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
14
src/net/sf/briar/api/db/DatabaseConfig.java
Normal file
14
src/net/sf/briar/api/db/DatabaseConfig.java
Normal 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();
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
7
src/net/sf/briar/api/db/DbClosedException.java
Normal file
7
src/net/sf/briar/api/db/DbClosedException.java
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
431
src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
Normal file
431
src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
207
src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java
Normal file
207
src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user