diff --git a/.classpath b/.classpath index 18857aff7..a4763d1ee 100644 --- a/.classpath +++ b/.classpath @@ -1,25 +1,8 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/.gitignore b/.gitignore index e57536479..1a6498b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/windows-jre -/Briar -/bin +bin +gen +local.properties diff --git a/.project b/.project index 69fc6e96b..401ff926e 100644 --- a/.project +++ b/.project @@ -1,17 +1,33 @@ - - - prototype - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - + + + prototype + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 000000000..f66bdf555 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/android.jar b/android.jar new file mode 100644 index 000000000..15ee285f0 Binary files /dev/null and b/android.jar differ diff --git a/ant.properties b/ant.properties new file mode 100644 index 000000000..b0971e891 --- /dev/null +++ b/ant.properties @@ -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. + diff --git a/build-common.xml b/build-common.xml deleted file mode 100644 index 07b3ace07..000000000 --- a/build-common.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build.xml b/build.xml index a1b07b6f6..33f165897 100644 --- a/build.xml +++ b/build.xml @@ -1,14 +1,92 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dependencies.xml b/dependencies.xml deleted file mode 100644 index 7eb615f82..000000000 --- a/dependencies.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/libs/test/hamcrest-core-1.1.jar b/libs/test/hamcrest-core-1.1.jar deleted file mode 100644 index 5f1d5ce0c..000000000 Binary files a/libs/test/hamcrest-core-1.1.jar and /dev/null differ diff --git a/libs/test/hamcrest-library-1.1.jar b/libs/test/hamcrest-library-1.1.jar deleted file mode 100644 index 40610c9b4..000000000 Binary files a/libs/test/hamcrest-library-1.1.jar and /dev/null differ diff --git a/libs/test/jmock-2.5.1.jar b/libs/test/jmock-2.5.1.jar deleted file mode 100644 index 4415dfbc9..000000000 Binary files a/libs/test/jmock-2.5.1.jar and /dev/null differ diff --git a/libs/test/junit-4.9b3.jar b/libs/test/junit-4.9b3.jar deleted file mode 100644 index 8c784e581..000000000 Binary files a/libs/test/junit-4.9b3.jar and /dev/null differ diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 000000000..f2fe1559a --- /dev/null +++ b/proguard-project.txt @@ -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 *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 000000000..9b84a6b4b --- /dev/null +++ b/project.properties @@ -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 diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 000000000..eeabdb397 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Briar + diff --git a/src/.gitignore b/src/.gitignore index 796b96d1c..378eac25d 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1 @@ -/build +build diff --git a/src/build.xml b/src/build.xml index 065ca4c88..95938ff1f 100644 --- a/src/build.xml +++ b/src/build.xml @@ -1,3 +1,25 @@ - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/net/sf/briar/HelloWorldActivity.java b/src/net/sf/briar/HelloWorldActivity.java new file mode 100644 index 000000000..17312662d --- /dev/null +++ b/src/net/sf/briar/HelloWorldActivity.java @@ -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); + } +} diff --git a/src/net/sf/briar/HelloWorldModule.java b/src/net/sf/briar/HelloWorldModule.java new file mode 100644 index 000000000..64a3d8fcf --- /dev/null +++ b/src/net/sf/briar/HelloWorldModule.java @@ -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); + } +} diff --git a/src/net/sf/briar/HelloWorldService.java b/src/net/sf/briar/HelloWorldService.java new file mode 100644 index 000000000..203aaf6e1 --- /dev/null +++ b/src/net/sf/briar/HelloWorldService.java @@ -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()); + } + } +} diff --git a/src/net/sf/briar/android/AndroidExecutorImpl.java b/src/net/sf/briar/android/AndroidExecutorImpl.java new file mode 100644 index 000000000..fd3ac27c2 --- /dev/null +++ b/src/net/sf/briar/android/AndroidExecutorImpl.java @@ -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 submit(Runnable r) { + startIfNecessary(); + Future f = new FutureTask(r, null); + Message m = Message.obtain(handler, RUNNABLE, f); + handler.sendMessage(m); + return f; + } + + public Future submit(Callable c) { + startIfNecessary(); + Future f = new FutureTask(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(); + } + } + } +} + diff --git a/src/net/sf/briar/android/AndroidModule.java b/src/net/sf/briar/android/AndroidModule.java new file mode 100644 index 000000000..d32534a96 --- /dev/null +++ b/src/net/sf/briar/android/AndroidModule.java @@ -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); + } +} diff --git a/src/net/sf/briar/api/android/AndroidExecutor.java b/src/net/sf/briar/api/android/AndroidExecutor.java new file mode 100644 index 000000000..3066a7b24 --- /dev/null +++ b/src/net/sf/briar/api/android/AndroidExecutor.java @@ -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 submit(Runnable r); + + Future submit(Callable c); + + void shutdown(); +} diff --git a/src/net/sf/briar/api/crypto/KeyManager.java b/src/net/sf/briar/api/crypto/KeyManager.java index a5a841687..46253592c 100644 --- a/src/net/sf/briar/api/crypto/KeyManager.java +++ b/src/net/sf/briar/api/crypto/KeyManager.java @@ -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 { diff --git a/src/net/sf/briar/api/db/DatabaseComponent.java b/src/net/sf/briar/api/db/DatabaseComponent.java index f3d952b40..cc6ae698f 100644 --- a/src/net/sf/briar/api/db/DatabaseComponent.java +++ b/src/net/sf/briar/api/db/DatabaseComponent.java @@ -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; diff --git a/src/net/sf/briar/api/db/DatabaseConfig.java b/src/net/sf/briar/api/db/DatabaseConfig.java new file mode 100644 index 000000000..74fdb0c76 --- /dev/null +++ b/src/net/sf/briar/api/db/DatabaseConfig.java @@ -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(); +} diff --git a/src/net/sf/briar/api/db/DatabaseDirectory.java b/src/net/sf/briar/api/db/DatabaseDirectory.java deleted file mode 100644 index ed5fcdc0e..000000000 --- a/src/net/sf/briar/api/db/DatabaseDirectory.java +++ /dev/null @@ -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 {} diff --git a/src/net/sf/briar/api/db/DatabaseMaxSize.java b/src/net/sf/briar/api/db/DatabaseMaxSize.java deleted file mode 100644 index ec1a2fbae..000000000 --- a/src/net/sf/briar/api/db/DatabaseMaxSize.java +++ /dev/null @@ -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 {} diff --git a/src/net/sf/briar/api/db/DatabasePassword.java b/src/net/sf/briar/api/db/DatabasePassword.java deleted file mode 100644 index d24a4a30c..000000000 --- a/src/net/sf/briar/api/db/DatabasePassword.java +++ /dev/null @@ -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 {} diff --git a/src/net/sf/briar/api/db/DbClosedException.java b/src/net/sf/briar/api/db/DbClosedException.java new file mode 100644 index 000000000..74f992107 --- /dev/null +++ b/src/net/sf/briar/api/db/DbClosedException.java @@ -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; +} diff --git a/src/net/sf/briar/api/plugins/PluginCallback.java b/src/net/sf/briar/api/plugins/PluginCallback.java index 88473dc51..63435f9f5 100644 --- a/src/net/sf/briar/api/plugins/PluginCallback.java +++ b/src/net/sf/briar/api/plugins/PluginCallback.java @@ -21,11 +21,13 @@ public interface PluginCallback { /** Returns the plugin's remote transport properties. */ Map 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 diff --git a/src/net/sf/briar/api/plugins/PluginManager.java b/src/net/sf/briar/api/plugins/PluginManager.java index da0c5e145..4d1a6897f 100644 --- a/src/net/sf/briar/api/plugins/PluginManager.java +++ b/src/net/sf/briar/api/plugins/PluginManager.java @@ -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. diff --git a/src/net/sf/briar/api/plugins/duplex/DuplexPluginFactory.java b/src/net/sf/briar/api/plugins/duplex/DuplexPluginFactory.java index 5d9609ff9..daf39c155 100644 --- a/src/net/sf/briar/api/plugins/duplex/DuplexPluginFactory.java +++ b/src/net/sf/briar/api/plugins/duplex/DuplexPluginFactory.java @@ -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); } diff --git a/src/net/sf/briar/api/plugins/simplex/SimplexPluginFactory.java b/src/net/sf/briar/api/plugins/simplex/SimplexPluginFactory.java index d114ec376..06e4df03c 100644 --- a/src/net/sf/briar/api/plugins/simplex/SimplexPluginFactory.java +++ b/src/net/sf/briar/api/plugins/simplex/SimplexPluginFactory.java @@ -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); } diff --git a/src/net/sf/briar/api/transport/ConnectionRecogniser.java b/src/net/sf/briar/api/transport/ConnectionRecogniser.java index 83ac8e36a..69e53b39f 100644 --- a/src/net/sf/briar/api/transport/ConnectionRecogniser.java +++ b/src/net/sf/briar/api/transport/ConnectionRecogniser.java @@ -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; /** diff --git a/src/net/sf/briar/api/db/ContactTransport.java b/src/net/sf/briar/api/transport/ContactTransport.java similarity index 96% rename from src/net/sf/briar/api/db/ContactTransport.java rename to src/net/sf/briar/api/transport/ContactTransport.java index 90fd11f33..870c2ccc4 100644 --- a/src/net/sf/briar/api/db/ContactTransport.java +++ b/src/net/sf/briar/api/transport/ContactTransport.java @@ -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; diff --git a/src/net/sf/briar/api/db/TemporarySecret.java b/src/net/sf/briar/api/transport/TemporarySecret.java similarity index 97% rename from src/net/sf/briar/api/db/TemporarySecret.java rename to src/net/sf/briar/api/transport/TemporarySecret.java index 0289b0737..2b2fc259b 100644 --- a/src/net/sf/briar/api/db/TemporarySecret.java +++ b/src/net/sf/briar/api/transport/TemporarySecret.java @@ -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; diff --git a/src/net/sf/briar/db/Database.java b/src/net/sf/briar/db/Database.java index 10898756d..5303fae3d 100644 --- a/src/net/sf/briar/db/Database.java +++ b/src/net/sf/briar/db/Database.java @@ -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 { long incrementConnectionCounter(T txn, ContactId c, TransportId t, long period) throws DbException; + /** + * Merges the given configuration with the existing configuration for the + * given transport. + *

+ * 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. + *

+ * 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 { */ void removeVisibility(T txn, ContactId c, GroupId g) throws DbException; - /** - * Sets the configuration for the given transport, replacing any existing - * configuration for that transport. - *

- * 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 { */ 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. - *

- * Locking: transport write. - */ - void setLocalProperties(T txn, TransportId t, TransportProperties p) - throws DbException; - /** * Sets the user's rating for the given author. *

diff --git a/src/net/sf/briar/db/DatabaseCleanerImpl.java b/src/net/sf/briar/db/DatabaseCleanerImpl.java index 71322acf8..dc2f4e0d8 100644 --- a/src/net/sf/briar/db/DatabaseCleanerImpl.java +++ b/src/net/sf/briar/db/DatabaseCleanerImpl.java @@ -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 diff --git a/src/net/sf/briar/db/DatabaseComponentImpl.java b/src/net/sf/briar/db/DatabaseComponentImpl.java index 48bb097cd..8e6fb021c 100644 --- a/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -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(); diff --git a/src/net/sf/briar/db/DatabaseModule.java b/src/net/sf/briar/db/DatabaseModule.java index 7532896e2..0d36cd348 100644 --- a/src/net/sf/briar/db/DatabaseModule.java +++ b/src/net/sf/briar/db/DatabaseModule.java @@ -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 getDatabase(@DatabaseDirectory File dir, - @DatabasePassword Password password, @DatabaseMaxSize long maxSize, + Database getDatabase(DatabaseConfig config, GroupFactory groupFactory, Clock clock) { - return new H2Database(dir, password, maxSize, groupFactory, clock); + return new H2Database(config, groupFactory, clock); } @Provides @Singleton diff --git a/src/net/sf/briar/db/H2Database.java b/src/net/sf/briar/db/H2Database.java index fb91b6696..ed6fdaa75 100644 --- a/src/net/sf/briar/db/H2Database.java +++ b/src/net/sf/briar/db/H2Database.java @@ -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); diff --git a/src/net/sf/briar/db/JdbcDatabase.java b/src/net/sf/briar/db/JdbcDatabase.java index 288b97877..fb4e676d1 100644 --- a/src/net/sf/briar/db/JdbcDatabase.java +++ b/src/net/sf/briar/db/JdbcDatabase.java @@ -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 { 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 { 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 { } } - 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 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 e : c.entrySet()) { - ps.setString(2, e.getKey()); - ps.setString(3, e.getValue()); + ps.setBytes(2, t.getBytes()); + for(Entry 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 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 { } } - 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 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; diff --git a/src/net/sf/briar/api/db/Status.java b/src/net/sf/briar/db/Status.java similarity index 83% rename from src/net/sf/briar/api/db/Status.java rename to src/net/sf/briar/db/Status.java index 92a7070e6..8fcda6803 100644 --- a/src/net/sf/briar/api/db/Status.java +++ b/src/net/sf/briar/db/Status.java @@ -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. */ diff --git a/src/net/sf/briar/plugins/PluginManagerImpl.java b/src/net/sf/briar/plugins/PluginManagerImpl.java index 4d82fd0e5..267b4323d 100644 --- a/src/net/sf/briar/plugins/PluginManagerImpl.java +++ b/src/net/sf/briar/plugins/PluginManagerImpl.java @@ -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(); } - public synchronized int start() { + public synchronized int start(Context appContext) { Set ids = new HashSet(); // 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()); } diff --git a/src/net/sf/briar/plugins/bluetooth/AbstractListener.java b/src/net/sf/briar/plugins/bluetooth/AbstractListener.java deleted file mode 100644 index a7bed55c7..000000000 --- a/src/net/sf/briar/plugins/bluetooth/AbstractListener.java +++ /dev/null @@ -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 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()); - } - } -} diff --git a/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java index 10f7057ca..ae10cf8fc 100644 --- a/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java +++ b/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java @@ -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 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(); } 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 connected) { - synchronized(this) { - if(!running) return; - } + // Try to connect to known devices in parallel Map remote = - callback.getRemoteProperties(); - Map discovered = discoverContactUrls(remote); - for(Entry 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 discoverContactUrls( - Map remote) { - LocalDevice localDevice; - synchronized(this) { - if(!running) return Collections.emptyMap(); - localDevice = this.localDevice; - } - DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent(); - Map addresses = new HashMap(); - Map uuids = new HashMap(); + callback.getRemoteProperties(); for(Entry 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 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()); } } } diff --git a/src/net/sf/briar/plugins/bluetooth/BluetoothPluginFactory.java b/src/net/sf/briar/plugins/bluetooth/BluetoothPluginFactory.java index e5c165209..f3edf599c 100644 --- a/src/net/sf/briar/plugins/bluetooth/BluetoothPluginFactory.java +++ b/src/net/sf/briar/plugins/bluetooth/BluetoothPluginFactory.java @@ -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); diff --git a/src/net/sf/briar/plugins/bluetooth/ConnectionCallback.java b/src/net/sf/briar/plugins/bluetooth/ConnectionCallback.java deleted file mode 100644 index d558e6f91..000000000 --- a/src/net/sf/briar/plugins/bluetooth/ConnectionCallback.java +++ /dev/null @@ -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()); - } - } - } -} diff --git a/src/net/sf/briar/plugins/bluetooth/ContactListener.java b/src/net/sf/briar/plugins/bluetooth/ContactListener.java deleted file mode 100644 index 6e29d6d8f..000000000 --- a/src/net/sf/briar/plugins/bluetooth/ContactListener.java +++ /dev/null @@ -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 addresses; - private final Map uuids; - private final Map urls; - - ContactListener(DiscoveryAgent discoveryAgent, - Map addresses, Map uuids) { - super(discoveryAgent); - this.addresses = addresses; - this.uuids = uuids; - urls = Collections.synchronizedMap(new HashMap()); - } - - Map 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 uuids = new TreeSet(); - findNestedClassIds(record.getAttributeValue(0x1), uuids); - for(String u : uuids) { - if(uuid.equalsIgnoreCase(u.toString())) { - // The UUID matches - store the URL - urls.put(c, serviceUrl); - } - } - } - } -} \ No newline at end of file diff --git a/src/net/sf/briar/plugins/bluetooth/InvitationListener.java b/src/net/sf/briar/plugins/bluetooth/InvitationListener.java index 1dfa6b46e..5493d9839 100644 --- a/src/net/sf/briar/plugins/bluetooth/InvitationListener.java +++ b/src/net/sf/briar/plugins/bluetooth/InvitationListener.java @@ -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 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; + } } diff --git a/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java b/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java new file mode 100644 index 000000000..b3ad8b31a --- /dev/null +++ b/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java @@ -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 c = new Callable() { + public BluetoothAdapter call() throws Exception { + return BluetoothAdapter.getDefaultAdapter(); + } + }; + Future 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 connected) { + synchronized(this) { + if(!running) return; + } + // Try to connect to known devices in parallel + Map remote = + callback.getRemoteProperties(); + for(Entry 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; + } + } +} diff --git a/src/net/sf/briar/plugins/droidtooth/DroidtoothPluginFactory.java b/src/net/sf/briar/plugins/droidtooth/DroidtoothPluginFactory.java new file mode 100644 index 000000000..e1634e102 --- /dev/null +++ b/src/net/sf/briar/plugins/droidtooth/DroidtoothPluginFactory.java @@ -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); + } +} diff --git a/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java b/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java new file mode 100644 index 000000000..c493a5b67 --- /dev/null +++ b/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java @@ -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(); + } +} diff --git a/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java b/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java new file mode 100644 index 000000000..fadde3fdb --- /dev/null +++ b/src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java @@ -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 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 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; + } +} diff --git a/src/net/sf/briar/plugins/email/GmailPluginFactory.java b/src/net/sf/briar/plugins/email/GmailPluginFactory.java index 87c7c42f1..192e0d412 100644 --- a/src/net/sf/briar/plugins/email/GmailPluginFactory.java +++ b/src/net/sf/briar/plugins/email/GmailPluginFactory.java @@ -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); } diff --git a/src/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java b/src/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java index 689efc850..0dfee33b7 100644 --- a/src/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java +++ b/src/net/sf/briar/plugins/file/RemovableDrivePluginFactory.java @@ -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; diff --git a/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java b/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java index 4a828f491..1a23bbbe9 100644 --- a/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java +++ b/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java @@ -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() { diff --git a/src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java b/src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java index d8499fb32..d67457146 100644 --- a/src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java +++ b/src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java @@ -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); diff --git a/src/net/sf/briar/plugins/tor/TorPlugin.java b/src/net/sf/briar/plugins/tor/TorPlugin.java index 44c4f7984..75484bbdc 100644 --- a/src/net/sf/briar/plugins/tor/TorPlugin.java +++ b/src/net/sf/briar/plugins/tor/TorPlugin.java @@ -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; } diff --git a/src/net/sf/briar/plugins/tor/TorPluginFactory.java b/src/net/sf/briar/plugins/tor/TorPluginFactory.java index e7caf6aa1..bb43a6d3b 100644 --- a/src/net/sf/briar/plugins/tor/TorPluginFactory.java +++ b/src/net/sf/briar/plugins/tor/TorPluginFactory.java @@ -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); } diff --git a/src/net/sf/briar/transport/ConnectionRecogniserImpl.java b/src/net/sf/briar/transport/ConnectionRecogniserImpl.java index 8f39204ef..73492ed3c 100644 --- a/src/net/sf/briar/transport/ConnectionRecogniserImpl.java +++ b/src/net/sf/briar/transport/ConnectionRecogniserImpl.java @@ -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; diff --git a/src/net/sf/briar/transport/KeyManagerImpl.java b/src/net/sf/briar/transport/KeyManagerImpl.java index d80cced25..5855e6f5e 100644 --- a/src/net/sf/briar/transport/KeyManagerImpl.java +++ b/src/net/sf/briar/transport/KeyManagerImpl.java @@ -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; diff --git a/src/net/sf/briar/transport/TransportConnectionRecogniser.java b/src/net/sf/briar/transport/TransportConnectionRecogniser.java index fd588f95a..b173122f9 100644 --- a/src/net/sf/briar/transport/TransportConnectionRecogniser.java +++ b/src/net/sf/briar/transport/TransportConnectionRecogniser.java @@ -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. */ diff --git a/src/net/sf/briar/util/BoundedExecutor.java b/src/net/sf/briar/util/BoundedExecutor.java index c019353b8..26f6dce6b 100644 --- a/src/net/sf/briar/util/BoundedExecutor.java +++ b/src/net/sf/briar/util/BoundedExecutor.java @@ -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(); - executor = new ThreadPoolExecutor(minThreads, maxThreads, 60, - TimeUnit.SECONDS, queue); + executor = new ThreadPoolExecutor(minThreads, maxThreads, 60, SECONDS, + queue); } public void execute(final Runnable r) { diff --git a/src/net/sf/briar/util/FileUtils.java b/src/net/sf/briar/util/FileUtils.java index a6cd4a36f..3a726a798 100644 --- a/src/net/sf/briar/util/FileUtils.java +++ b/src/net/sf/briar/util/FileUtils.java @@ -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); diff --git a/src/net/sf/briar/util/OsUtils.java b/src/net/sf/briar/util/OsUtils.java index dd32ad897..e40d4f504 100644 --- a/src/net/sf/briar/util/OsUtils.java +++ b/src/net/sf/briar/util/OsUtils.java @@ -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; } } diff --git a/test/build.xml b/test/build.xml deleted file mode 100644 index 5d0a8916f..000000000 --- a/test/build.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/net/sf/briar/BriarTestCase.java b/test/net/sf/briar/BriarTestCase.java deleted file mode 100644 index 171b620cd..000000000 --- a/test/net/sf/briar/BriarTestCase.java +++ /dev/null @@ -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); - } -} diff --git a/test/net/sf/briar/LockFairnessTest.java b/test/net/sf/briar/LockFairnessTest.java deleted file mode 100644 index 3f847ca51..000000000 --- a/test/net/sf/briar/LockFairnessTest.java +++ /dev/null @@ -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)); - } -} diff --git a/test/net/sf/briar/ProtocolIntegrationTest.java b/test/net/sf/briar/ProtocolIntegrationTest.java deleted file mode 100644 index bbd7af2f9..000000000 --- a/test/net/sf/briar/ProtocolIntegrationTest.java +++ /dev/null @@ -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 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 batch = Arrays.asList(message.getSerialised(), - message1.getSerialised(), message2.getSerialised(), - message3.getSerialised()); - RawBatch b = packetFactory.createBatch(batch); - writer.writeBatch(b); - - Collection 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 subs = new LinkedHashMap(); - subs.put(group, 0L); - subs.put(group1, 0L); - SubscriptionUpdate s = packetFactory.createSubscriptionUpdate( - Collections.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 messages = b.getMessages(); - assertEquals(4, messages.size()); - Iterator 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 offered = o.getMessageIds(); - assertEquals(4, offered.size()); - Iterator 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 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()); - } -} diff --git a/test/net/sf/briar/TestDatabaseModule.java b/test/net/sf/briar/TestDatabaseModule.java deleted file mode 100644 index 98c350ec9..000000000 --- a/test/net/sf/briar/TestDatabaseModule.java +++ /dev/null @@ -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); - } -} diff --git a/test/net/sf/briar/TestUtils.java b/test/net/sf/briar/TestUtils.java deleted file mode 100644 index 6ecb32a4c..000000000 --- a/test/net/sf/briar/TestUtils.java +++ /dev/null @@ -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); - } -} diff --git a/test/net/sf/briar/crypto/CounterModeTest.java b/test/net/sf/briar/crypto/CounterModeTest.java deleted file mode 100644 index 96cde2001..000000000 --- a/test/net/sf/briar/crypto/CounterModeTest.java +++ /dev/null @@ -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 ciphertexts = new HashSet(); - 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 ciphertexts = new HashSet(); - 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]); - } - } -} diff --git a/test/net/sf/briar/crypto/ErasableKeyTest.java b/test/net/sf/briar/crypto/ErasableKeyTest.java deleted file mode 100644 index eb448a550..000000000 --- a/test/net/sf/briar/crypto/ErasableKeyTest.java +++ /dev/null @@ -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); - } -} diff --git a/test/net/sf/briar/crypto/KeyAgreementTest.java b/test/net/sf/briar/crypto/KeyAgreementTest.java deleted file mode 100644 index f1ba77f98..000000000 --- a/test/net/sf/briar/crypto/KeyAgreementTest.java +++ /dev/null @@ -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); - } -} diff --git a/test/net/sf/briar/crypto/KeyDerivationTest.java b/test/net/sf/briar/crypto/KeyDerivationTest.java deleted file mode 100644 index b05f536a4..000000000 --- a/test/net/sf/briar/crypto/KeyDerivationTest.java +++ /dev/null @@ -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 keys = new ArrayList(); - 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 secrets = new ArrayList(); - 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 secrets = new ArrayList(); - 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)); - } - } - } -} diff --git a/test/net/sf/briar/db/BasicH2Test.java b/test/net/sf/briar/db/BasicH2Test.java deleted file mode 100644 index 76e2384f6..000000000 --- a/test/net/sf/briar/db/BasicH2Test.java +++ /dev/null @@ -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 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 getNames() throws SQLException { - String sql = "SELECT name FROM foo ORDER BY uniqueId"; - List names = new ArrayList(); - 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); - } -} diff --git a/test/net/sf/briar/db/DatabaseCleanerImplTest.java b/test/net/sf/briar/db/DatabaseCleanerImplTest.java deleted file mode 100644 index 402674954..000000000 --- a/test/net/sf/briar/db/DatabaseCleanerImplTest.java +++ /dev/null @@ -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); - } -} diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java deleted file mode 100644 index 389bb46a6..000000000 --- a/test/net/sf/briar/db/DatabaseComponentImplTest.java +++ /dev/null @@ -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 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 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 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 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 DatabaseComponent createDatabaseComponent( - Database database, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory) { - return createDatabaseComponentImpl(database, cleaner, shutdown, - packetFactory); - } - - private DatabaseComponentImpl createDatabaseComponentImpl( - Database database, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory) { - return new DatabaseComponentImpl(database, cleaner, shutdown, - packetFactory, new SystemClock()); - } -} diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java deleted file mode 100644 index c30fe3478..000000000 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ /dev/null @@ -1,1607 +0,0 @@ -package net.sf.briar.db; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Collection; -import java.util.Collections; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.ContactId; -import net.sf.briar.api.Rating; -import net.sf.briar.api.TransportProperties; -import net.sf.briar.api.db.ContactTransport; -import net.sf.briar.api.db.DatabaseComponent; -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.ContactAddedEvent; -import net.sf.briar.api.db.event.ContactRemovedEvent; -import net.sf.briar.api.db.event.DatabaseListener; -import net.sf.briar.api.db.event.MessagesAddedEvent; -import net.sf.briar.api.db.event.RatingChangedEvent; -import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent; -import net.sf.briar.api.lifecycle.ShutdownManager; -import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.AuthorId; -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.GroupId; -import net.sf.briar.api.protocol.Message; -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.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 org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -public abstract class DatabaseComponentTest extends BriarTestCase { - - protected final Object txn = new Object(); - protected final AuthorId authorId; - protected final BatchId batchId; - protected final ContactId contactId; - protected final GroupId groupId; - protected final MessageId messageId, parentId; - private final String subject; - private final long timestamp; - private final int size; - private final byte[] raw; - private final Message message, privateMessage; - private final Group group; - private final TransportId transportId; - private final Collection transports; - private final ContactTransport contactTransport; - private final TemporarySecret temporarySecret; - - public DatabaseComponentTest() { - super(); - authorId = new AuthorId(TestUtils.getRandomId()); - batchId = new BatchId(TestUtils.getRandomId()); - contactId = new ContactId(234); - groupId = new GroupId(TestUtils.getRandomId()); - messageId = new MessageId(TestUtils.getRandomId()); - parentId = new MessageId(TestUtils.getRandomId()); - subject = "Foo"; - timestamp = System.currentTimeMillis(); - size = 1234; - raw = new byte[size]; - message = new TestMessage(messageId, null, groupId, authorId, subject, - timestamp, raw); - privateMessage = new TestMessage(messageId, null, null, null, subject, - timestamp, raw); - group = new TestGroup(groupId, "The really exciting group", null); - transportId = new TransportId(TestUtils.getRandomId()); - TransportProperties properties = new TransportProperties( - Collections.singletonMap("foo", "bar")); - Transport transport = new Transport(transportId, properties); - transports = Collections.singletonList(transport); - contactTransport = new ContactTransport(contactId, transportId, 123L, - 234L, 345L, true); - temporarySecret = new TemporarySecret(contactId, transportId, 1L, 2L, - 3L, false, 4L, new byte[32], 5L, 6L, new byte[4]); - } - - protected abstract DatabaseComponent createDatabaseComponent( - Database database, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory); - - @Test - @SuppressWarnings("unchecked") - public void testSimpleCalls() throws Exception { - final int shutdownHandle = 12345; - Mockery context = new Mockery(); - final Database 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); - final Group group = context.mock(Group.class); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - // open(false) - oneOf(database).open(false); - oneOf(cleaner).startCleaning( - with(any(DatabaseCleaner.Callback.class)), - with(any(long.class))); - oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); - will(returnValue(shutdownHandle)); - // getRating(authorId) - oneOf(database).getRating(txn, authorId); - will(returnValue(Rating.UNRATED)); - // setRating(authorId, Rating.GOOD) - oneOf(database).setRating(txn, authorId, Rating.GOOD); - will(returnValue(Rating.UNRATED)); - oneOf(database).getMessagesByAuthor(txn, authorId); - will(returnValue(Collections.emptyList())); - oneOf(listener).eventOccurred(with(any(RatingChangedEvent.class))); - // setRating(authorId, Rating.GOOD) again - oneOf(database).setRating(txn, authorId, Rating.GOOD); - will(returnValue(Rating.GOOD)); - // addContact() - oneOf(database).addContact(txn); - will(returnValue(contactId)); - oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class))); - // getContacts() - oneOf(database).getContacts(txn); - will(returnValue(Collections.singletonList(contactId))); - // getTransportProperties(transportId) - oneOf(database).getRemoteProperties(txn, transportId); - will(returnValue(Collections.emptyMap())); - // subscribe(group) - oneOf(group).getId(); - will(returnValue(groupId)); - oneOf(database).containsSubscription(txn, groupId); - will(returnValue(false)); - oneOf(database).addSubscription(txn, group); - // subscribe(group) again - oneOf(group).getId(); - will(returnValue(groupId)); - oneOf(database).containsSubscription(txn, groupId); - will(returnValue(true)); - // getMessageHeaders(groupId) - oneOf(database).getMessageHeaders(txn, groupId); - will(returnValue(Collections.emptyList())); - // getSubscriptions() - oneOf(database).getSubscriptions(txn); - will(returnValue(Collections.singletonList(groupId))); - // unsubscribe(groupId) - oneOf(database).containsSubscription(txn, groupId); - will(returnValue(true)); - oneOf(database).getVisibility(txn, groupId); - will(returnValue(Collections.emptyList())); - oneOf(database).removeSubscription(txn, groupId); - // unsubscribe(groupId) again - oneOf(database).containsSubscription(txn, groupId); - will(returnValue(false)); - // removeContact(contactId) - oneOf(database).containsContact(txn, contactId); - will(returnValue(true)); - oneOf(database).removeContact(txn, contactId); - oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class))); - // close() - oneOf(shutdown).removeShutdownHook(shutdownHandle); - oneOf(cleaner).stopCleaning(); - oneOf(database).close(); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.open(false); - db.addListener(listener); - assertEquals(Rating.UNRATED, db.getRating(authorId)); - db.setRating(authorId, Rating.GOOD); // First time - listeners called - db.setRating(authorId, Rating.GOOD); // Second time - not called - assertEquals(contactId, db.addContact()); - assertEquals(Collections.singletonList(contactId), db.getContacts()); - assertEquals(Collections.emptyMap(), - db.getRemoteProperties(transportId)); - db.subscribe(group); // First time - listeners called - db.subscribe(group); // Second time - not called - assertEquals(Collections.emptyList(), db.getMessageHeaders(groupId)); - assertEquals(Collections.singletonList(groupId), db.getSubscriptions()); - db.unsubscribe(groupId); // First time - listeners called - db.unsubscribe(groupId); // Second time - not called - db.removeContact(contactId); - db.removeListener(listener); - db.close(); - - context.assertIsSatisfied(); - } - - @Test - public void testNullParentStopsBackwardInclusion() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // setRating(authorId, Rating.GOOD) - allowing(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).setRating(txn, authorId, Rating.GOOD); - will(returnValue(Rating.UNRATED)); - // The sendability of the author's messages should be incremented - oneOf(database).getMessagesByAuthor(txn, authorId); - will(returnValue(Collections.singletonList(messageId))); - oneOf(database).getSendability(txn, messageId); - will(returnValue(0)); - oneOf(database).setSendability(txn, messageId, 1); - // Backward inclusion stops when the message has no parent - oneOf(database).getGroupMessageParent(txn, messageId); - will(returnValue(null)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.setRating(authorId, Rating.GOOD); - - context.assertIsSatisfied(); - } - - @Test - public void testUnaffectedParentStopsBackwardInclusion() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // setRating(authorId, Rating.GOOD) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).setRating(txn, authorId, Rating.GOOD); - will(returnValue(Rating.UNRATED)); - // The sendability of the author's messages should be incremented - oneOf(database).getMessagesByAuthor(txn, authorId); - will(returnValue(Collections.singletonList(messageId))); - oneOf(database).getSendability(txn, messageId); - will(returnValue(0)); - oneOf(database).setSendability(txn, messageId, 1); - // The parent exists, is in the DB, and is in the same group - oneOf(database).getGroupMessageParent(txn, messageId); - will(returnValue(parentId)); - // The parent is already sendable - oneOf(database).getSendability(txn, parentId); - will(returnValue(1)); - oneOf(database).setSendability(txn, parentId, 2); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.setRating(authorId, Rating.GOOD); - - context.assertIsSatisfied(); - } - - @Test - public void testAffectedParentContinuesBackwardInclusion() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // setRating(authorId, Rating.GOOD) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).setRating(txn, authorId, Rating.GOOD); - will(returnValue(Rating.UNRATED)); - // The sendability of the author's messages should be incremented - oneOf(database).getMessagesByAuthor(txn, authorId); - will(returnValue(Collections.singletonList(messageId))); - oneOf(database).getSendability(txn, messageId); - will(returnValue(0)); - oneOf(database).setSendability(txn, messageId, 1); - // The parent exists, is in the DB, and is in the same group - oneOf(database).getGroupMessageParent(txn, messageId); - will(returnValue(parentId)); - // The parent is not already sendable - oneOf(database).getSendability(txn, parentId); - will(returnValue(0)); - oneOf(database).setSendability(txn, parentId, 1); - // The parent has no parent - oneOf(database).getGroupMessageParent(txn, parentId); - will(returnValue(null)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.setRating(authorId, Rating.GOOD); - - context.assertIsSatisfied(); - } - - @Test - public void testGroupMessagesAreNotStoredUnlessSubscribed() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // addLocalGroupMessage(message) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsSubscription(txn, groupId, timestamp); - will(returnValue(false)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addLocalGroupMessage(message); - - context.assertIsSatisfied(); - } - - @Test - public void testDuplicateGroupMessagesAreNotStored() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // addLocalGroupMessage(message) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsSubscription(txn, groupId, timestamp); - will(returnValue(true)); - oneOf(database).addGroupMessage(txn, message); - will(returnValue(false)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addLocalGroupMessage(message); - - context.assertIsSatisfied(); - } - - @Test - public void testAddLocalGroupMessage() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // addLocalGroupMessage(message) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsSubscription(txn, groupId, timestamp); - will(returnValue(true)); - oneOf(database).addGroupMessage(txn, message); - will(returnValue(true)); - oneOf(database).getContacts(txn); - will(returnValue(Collections.singletonList(contactId))); - oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); - // The author is unrated and there are no sendable children - oneOf(database).getRating(txn, authorId); - will(returnValue(Rating.UNRATED)); - oneOf(database).getNumberOfSendableChildren(txn, messageId); - will(returnValue(0)); - oneOf(database).setSendability(txn, messageId, 0); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addLocalGroupMessage(message); - - context.assertIsSatisfied(); - } - - @Test - public void testAddingSendableMessageTriggersBackwardInclusion() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // addLocalGroupMessage(message) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsSubscription(txn, groupId, timestamp); - will(returnValue(true)); - oneOf(database).addGroupMessage(txn, message); - will(returnValue(true)); - oneOf(database).getContacts(txn); - will(returnValue(Collections.singletonList(contactId))); - oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); - // The author is rated GOOD and there are two sendable children - oneOf(database).getRating(txn, authorId); - will(returnValue(Rating.GOOD)); - oneOf(database).getNumberOfSendableChildren(txn, messageId); - will(returnValue(2)); - oneOf(database).setSendability(txn, messageId, 3); - // The sendability of the message's ancestors should be updated - oneOf(database).getGroupMessageParent(txn, messageId); - will(returnValue(null)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addLocalGroupMessage(message); - - context.assertIsSatisfied(); - } - - @Test - public void testDuplicatePrivateMessagesAreNotStored() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // addLocalPrivateMessage(privateMessage, contactId) - oneOf(database).addPrivateMessage(txn, privateMessage, contactId); - will(returnValue(false)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addLocalPrivateMessage(privateMessage, contactId); - - context.assertIsSatisfied(); - } - - @Test - public void testAddLocalPrivateMessage() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // addLocalPrivateMessage(privateMessage, contactId) - oneOf(database).addPrivateMessage(txn, privateMessage, contactId); - will(returnValue(true)); - oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addLocalPrivateMessage(privateMessage, contactId); - - context.assertIsSatisfied(); - } - - @Test - public void testVariousMethodsThrowExceptionIfContactIsMissing() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Ack ack = context.mock(Ack.class); - final Batch batch = context.mock(Batch.class); - final Offer offer = context.mock(Offer.class); - final SubscriptionUpdate subscriptionUpdate = - context.mock(SubscriptionUpdate.class); - final TransportUpdate transportUpdate = - context.mock(TransportUpdate.class); - context.checking(new Expectations() {{ - // Check whether the contact is in the DB (which it's not) - exactly(16).of(database).startTransaction(); - will(returnValue(txn)); - exactly(16).of(database).containsContact(txn, contactId); - will(returnValue(false)); - exactly(16).of(database).abortTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - try { - db.addContactTransport(contactTransport); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.addLocalPrivateMessage(privateMessage, contactId); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.generateAck(contactId, 123); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.generateBatch(contactId, 123); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.generateBatch(contactId, 123, - Collections.emptyList()); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.generateOffer(contactId, 123); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.generateSubscriptionUpdate(contactId); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.generateTransportUpdate(contactId); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.hasSendableMessages(contactId); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.receiveAck(contactId, ack); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.receiveBatch(contactId, batch); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.receiveOffer(contactId, offer); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.receiveSubscriptionUpdate(contactId, subscriptionUpdate); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.receiveTransportUpdate(contactId, transportUpdate); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.removeContact(contactId); - fail(); - } catch(NoSuchContactException expected) {} - - try { - db.setSeen(contactId, Collections.singletonList(messageId)); - fail(); - } catch(NoSuchContactException expected) {} - - context.assertIsSatisfied(); - } - - @Test - public void testVariousMethodsThrowExceptionIfContactTransportIsMissing() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // Check whether the contact transport is in the DB (which it's not) - exactly(2).of(database).startTransaction(); - will(returnValue(txn)); - exactly(2).of(database).containsContactTransport(txn, contactId, - transportId); - will(returnValue(false)); - exactly(2).of(database).abortTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - try { - db.incrementConnectionCounter(contactId, transportId, 0L); - fail(); - } catch(NoSuchContactTransportException expected) {} - - try { - db.setConnectionWindow(contactId, transportId, 0L, 0L, new byte[4]); - fail(); - } catch(NoSuchContactTransportException expected) {} - - context.assertIsSatisfied(); - } - - @Test - public void testGenerateAck() throws Exception { - final BatchId batchId1 = new BatchId(TestUtils.getRandomId()); - final Collection batchesToAck = new ArrayList(); - batchesToAck.add(batchId); - batchesToAck.add(batchId1); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Ack ack = context.mock(Ack.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the batches to ack - oneOf(database).getBatchesToAck(txn, contactId, 123); - will(returnValue(batchesToAck)); - // Create the packet - oneOf(packetFactory).createAck(batchesToAck); - will(returnValue(ack)); - // Record the batches that were acked - oneOf(database).removeBatchesToAck(txn, contactId, batchesToAck); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(ack, db.generateAck(contactId, 123)); - - context.assertIsSatisfied(); - } - - @Test - public void testGenerateBatch() throws Exception { - final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - final byte[] raw1 = new byte[size]; - final Collection sendable = Arrays.asList(messageId, - messageId1); - final Collection messages = Arrays.asList(raw, raw1); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final RawBatch batch = context.mock(RawBatch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the sendable messages - oneOf(database).getSendableMessages(txn, contactId, size * 2); - will(returnValue(sendable)); - oneOf(database).getMessage(txn, messageId); - will(returnValue(raw)); - oneOf(database).getMessage(txn, messageId1); - will(returnValue(raw1)); - // Create the packet - oneOf(packetFactory).createBatch(messages); - will(returnValue(batch)); - // Record the outstanding batch - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addOutstandingBatch(txn, contactId, batchId, - sendable); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(batch, db.generateBatch(contactId, size * 2)); - - context.assertIsSatisfied(); - } - - @Test - public void testGenerateBatchFromRequest() throws Exception { - final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - final MessageId messageId2 = new MessageId(TestUtils.getRandomId()); - final byte[] raw1 = new byte[size]; - final Collection requested = new ArrayList(); - requested.add(messageId); - requested.add(messageId1); - requested.add(messageId2); - final Collection msgs = Arrays.asList(raw1); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final RawBatch batch = context.mock(RawBatch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Try to get the requested messages - oneOf(database).getMessageIfSendable(txn, contactId, messageId); - will(returnValue(null)); // Message is not sendable - oneOf(database).getMessageIfSendable(txn, contactId, messageId1); - will(returnValue(raw1)); // Message is sendable - oneOf(database).getMessageIfSendable(txn, contactId, messageId2); - will(returnValue(null)); // Message is not sendable - // Create the packet - oneOf(packetFactory).createBatch(msgs); - will(returnValue(batch)); - // Record the outstanding batch - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addOutstandingBatch(txn, contactId, batchId, - Collections.singletonList(messageId1)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(batch, db.generateBatch(contactId, size * 3, requested)); - - context.assertIsSatisfied(); - } - - @Test - public void testGenerateOffer() throws Exception { - final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - final Collection offerable = new ArrayList(); - offerable.add(messageId); - offerable.add(messageId1); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Offer offer = context.mock(Offer.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the sendable message IDs - oneOf(database).getOfferableMessages(txn, contactId, 123); - will(returnValue(offerable)); - // Create the packet - oneOf(packetFactory).createOffer(offerable); - will(returnValue(offer)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(offer, db.generateOffer(contactId, 123)); - - context.assertIsSatisfied(); - } - - @Test - public void testGenerateSubscriptionUpdate() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final SubscriptionUpdate subscriptionUpdate = - context.mock(SubscriptionUpdate.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the visible holes and subscriptions - oneOf(database).getVisibleHoles(with(txn), with(contactId), - with(any(long.class))); - will(returnValue(Collections.emptyMap())); - oneOf(database).getVisibleSubscriptions(with(txn), with(contactId), - with(any(long.class))); - will(returnValue(Collections.singletonMap(group, 0L))); - // Get the expiry time - oneOf(database).getExpiryTime(txn); - will(returnValue(0L)); - // Create the packet - oneOf(packetFactory).createSubscriptionUpdate( - with(Collections.emptyMap()), - with(Collections.singletonMap(group, 0L)), - with(any(long.class)), - with(any(long.class))); - will(returnValue(subscriptionUpdate)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(subscriptionUpdate, - db.generateSubscriptionUpdate(contactId)); - - context.assertIsSatisfied(); - } - - @Test - public void testTransportUpdateNotSentUnlessDue() throws Exception { - final long now = System.currentTimeMillis(); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Check whether an update is due - oneOf(database).getTransportsModified(txn); - will(returnValue(now - 1L)); - oneOf(database).getTransportsSent(txn, contactId); - will(returnValue(now)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertNull(db.generateTransportUpdate(contactId)); - - context.assertIsSatisfied(); - } - - @Test - public void testGenerateTransportUpdate() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final TransportUpdate transportUpdate = - context.mock(TransportUpdate.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Check whether an update is due - oneOf(database).getTransportsModified(txn); - will(returnValue(0L)); - oneOf(database).getTransportsSent(txn, contactId); - will(returnValue(0L)); - // Get the local transport properties - oneOf(database).getLocalTransports(txn); - will(returnValue(transports)); - oneOf(database).setTransportsSent(with(txn), with(contactId), - with(any(long.class))); - // Create the packet - oneOf(packetFactory).createTransportUpdate(with(transports), - with(any(long.class))); - will(returnValue(transportUpdate)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(transportUpdate, db.generateTransportUpdate(contactId)); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveAck() throws Exception { - final BatchId batchId1 = new BatchId(TestUtils.getRandomId()); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Ack ack = context.mock(Ack.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the acked batches - oneOf(ack).getBatchIds(); - will(returnValue(Collections.singletonList(batchId))); - oneOf(database).removeAckedBatch(txn, contactId, batchId); - // Find lost batches - oneOf(database).getLostBatches(txn, contactId); - will(returnValue(Collections.singletonList(batchId1))); - oneOf(database).removeLostBatch(txn, contactId, batchId1); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveAck(contactId, ack); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveBatchStoresPrivateMessage() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Batch batch = context.mock(Batch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(privateMessage))); - // The message is stored - oneOf(database).addPrivateMessage(txn, privateMessage, contactId); - will(returnValue(true)); - oneOf(database).setStatus(txn, contactId, messageId, Status.SEEN); - // The batch must be acked - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addBatchToAck(txn, contactId, batchId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveBatch(contactId, batch); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveBatchWithDuplicatePrivateMessage() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Batch batch = context.mock(Batch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(privateMessage))); - // The message is stored, but it's a duplicate - oneOf(database).addPrivateMessage(txn, privateMessage, contactId); - will(returnValue(false)); - // The batch must still be acked - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addBatchToAck(txn, contactId, batchId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveBatch(contactId, batch); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveBatchDoesNotStoreGroupMessageUnlessSubscribed() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Batch batch = context.mock(Batch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Only store messages belonging to visible, subscribed groups - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(message))); - oneOf(database).containsVisibleSubscription(txn, groupId, - contactId, timestamp); - will(returnValue(false)); - // The message is not stored but the batch must still be acked - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addBatchToAck(txn, contactId, batchId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveBatch(contactId, batch); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveBatchDoesNotCalculateSendabilityForDuplicates() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Batch batch = context.mock(Batch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Only store messages belonging to visible, subscribed groups - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(message))); - oneOf(database).containsVisibleSubscription(txn, groupId, - contactId, timestamp); - will(returnValue(true)); - // The message is stored, but it's a duplicate - oneOf(database).addGroupMessage(txn, message); - will(returnValue(false)); - oneOf(database).setStatus(txn, contactId, messageId, Status.SEEN); - // The batch needs to be acknowledged - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addBatchToAck(txn, contactId, batchId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveBatch(contactId, batch); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveBatchCalculatesSendability() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Batch batch = context.mock(Batch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Only store messages belonging to visible, subscribed groups - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(message))); - oneOf(database).containsVisibleSubscription(txn, groupId, - contactId, timestamp); - will(returnValue(true)); - // The message is stored, and it's not a duplicate - oneOf(database).addGroupMessage(txn, message); - will(returnValue(true)); - oneOf(database).setStatus(txn, contactId, messageId, Status.SEEN); - // Set the status to NEW for all other contacts (there are none) - oneOf(database).getContacts(txn); - will(returnValue(Collections.singletonList(contactId))); - // Calculate the sendability - zero, so ancestors aren't updated - oneOf(database).getRating(txn, authorId); - will(returnValue(Rating.UNRATED)); - oneOf(database).getNumberOfSendableChildren(txn, messageId); - will(returnValue(0)); - oneOf(database).setSendability(txn, messageId, 0); - // The batch needs to be acknowledged - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addBatchToAck(txn, contactId, batchId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveBatch(contactId, batch); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveBatchUpdatesAncestorSendability() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Batch batch = context.mock(Batch.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Only store messages belonging to visible, subscribed groups - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(message))); - oneOf(database).containsVisibleSubscription(txn, groupId, - contactId, timestamp); - will(returnValue(true)); - // The message is stored, and it's not a duplicate - oneOf(database).addGroupMessage(txn, message); - will(returnValue(true)); - oneOf(database).setStatus(txn, contactId, messageId, Status.SEEN); - // Set the status to NEW for all other contacts (there are none) - oneOf(database).getContacts(txn); - will(returnValue(Collections.singletonList(contactId))); - // Calculate the sendability - ancestors are updated - oneOf(database).getRating(txn, authorId); - will(returnValue(Rating.GOOD)); - oneOf(database).getNumberOfSendableChildren(txn, messageId); - will(returnValue(1)); - oneOf(database).setSendability(txn, messageId, 2); - oneOf(database).getGroupMessageParent(txn, messageId); - will(returnValue(null)); - // The batch needs to be acknowledged - oneOf(batch).getId(); - will(returnValue(batchId)); - oneOf(database).addBatchToAck(txn, contactId, batchId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveBatch(contactId, batch); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveOffer() throws Exception { - final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - final MessageId messageId2 = new MessageId(TestUtils.getRandomId()); - final Collection offered = new ArrayList(); - offered.add(messageId); - offered.add(messageId1); - offered.add(messageId2); - final BitSet expectedRequest = new BitSet(3); - expectedRequest.set(0); - expectedRequest.set(2); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final Offer offer = context.mock(Offer.class); - final Request request = context.mock(Request.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the offered messages - oneOf(offer).getMessageIds(); - will(returnValue(offered)); - oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId); - will(returnValue(false)); // Not visible - request message # 0 - oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId1); - will(returnValue(true)); // Visible - do not request message # 1 - oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2); - will(returnValue(false)); // Not visible - request message # 2 - // Create the packet - oneOf(packetFactory).createRequest(expectedRequest, 3); - will(returnValue(request)); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - assertEquals(request, db.receiveOffer(contactId, offer)); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveSubscriptionUpdate() throws Exception { - final GroupId start = new GroupId(TestUtils.getRandomId()); - final GroupId end = new GroupId(TestUtils.getRandomId()); - final long expiry = 1234L, timestamp = 5678L; - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final SubscriptionUpdate subscriptionUpdate = - context.mock(SubscriptionUpdate.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the contents of the update - oneOf(subscriptionUpdate).getHoles(); - will(returnValue(Collections.singletonMap(start, end))); - oneOf(subscriptionUpdate).getSubscriptions(); - will(returnValue(Collections.singletonMap(group, 0L))); - oneOf(subscriptionUpdate).getExpiryTime(); - will(returnValue(expiry)); - oneOf(subscriptionUpdate).getTimestamp(); - will(returnValue(timestamp)); - // Store the contents of the update - oneOf(database).removeSubscriptions(txn, contactId, start, end); - oneOf(database).addSubscription(txn, contactId, group, 0L); - oneOf(database).setExpiryTime(txn, contactId, expiry); - oneOf(database).setSubscriptionsReceived(txn, contactId, timestamp); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveSubscriptionUpdate(contactId, subscriptionUpdate); - - context.assertIsSatisfied(); - } - - @Test - public void testReceiveTransportUpdate() throws Exception { - final long timestamp = 1234L; - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final TransportUpdate transportUpdate = - context.mock(TransportUpdate.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // Get the contents of the update - oneOf(transportUpdate).getTransports(); - will(returnValue(transports)); - oneOf(transportUpdate).getTimestamp(); - will(returnValue(timestamp)); - oneOf(database).setTransports(txn, contactId, transports, - timestamp); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.receiveTransportUpdate(contactId, transportUpdate); - - context.assertIsSatisfied(); - } - - @Test - public void testAddingGroupMessageCallsListeners() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - // addLocalGroupMessage(message) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsSubscription(txn, groupId, timestamp); - will(returnValue(true)); - oneOf(database).addGroupMessage(txn, message); - will(returnValue(true)); - oneOf(database).getContacts(txn); - will(returnValue(Collections.singletonList(contactId))); - oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); - oneOf(database).getRating(txn, authorId); - will(returnValue(Rating.UNRATED)); - oneOf(database).getNumberOfSendableChildren(txn, messageId); - will(returnValue(0)); - oneOf(database).setSendability(txn, messageId, 0); - oneOf(database).commitTransaction(txn); - // The message was added, so the listener should be called - oneOf(listener).eventOccurred(with(any(MessagesAddedEvent.class))); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.addLocalGroupMessage(message); - - context.assertIsSatisfied(); - } - - @Test - public void testAddingPrivateMessageCallsListeners() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // addLocalPrivateMessage(privateMessage, contactId) - oneOf(database).addPrivateMessage(txn, privateMessage, contactId); - will(returnValue(true)); - oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); - // The message was added, so the listener should be called - oneOf(listener).eventOccurred(with(any(MessagesAddedEvent.class))); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.addLocalPrivateMessage(privateMessage, contactId); - - context.assertIsSatisfied(); - } - - @Test - public void testAddingDuplicateGroupMessageDoesNotCallListeners() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - // addLocalGroupMessage(message) - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsSubscription(txn, groupId, timestamp); - will(returnValue(true)); - oneOf(database).addGroupMessage(txn, message); - will(returnValue(false)); - oneOf(database).commitTransaction(txn); - // The message was not added, so the listener should not be called - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.addLocalGroupMessage(message); - - context.assertIsSatisfied(); - } - - @Test - public void testAddingDuplicatePrivateMessageDoesNotCallListeners() - throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // addLocalPrivateMessage(privateMessage, contactId) - oneOf(database).addPrivateMessage(txn, privateMessage, contactId); - will(returnValue(false)); - // The message was not added, so the listener should not be called - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.addLocalPrivateMessage(privateMessage, contactId); - - context.assertIsSatisfied(); - } - - @Test - public void testTransportPropertiesChangedCallsListeners() - throws Exception { - final TransportProperties properties = - new TransportProperties(Collections.singletonMap("bar", "baz")); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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).startTransaction(); - will(returnValue(txn)); - oneOf(database).getLocalProperties(txn, transportId); - will(returnValue(new TransportProperties())); - oneOf(database).setLocalProperties(txn, transportId, properties); - oneOf(database).setTransportsModified(with(txn), - with(any(long.class))); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.setLocalProperties(transportId, properties); - - context.assertIsSatisfied(); - } - - @Test - public void testTransportPropertiesUnchangedDoesNotCallListeners() - throws Exception { - final TransportProperties properties = - new TransportProperties(Collections.singletonMap("bar", "baz")); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).getLocalProperties(txn, transportId); - will(returnValue(properties)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.setLocalProperties(transportId, properties); - - context.assertIsSatisfied(); - } - - @Test - public void testSetSeen() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - allowing(database).startTransaction(); - will(returnValue(txn)); - allowing(database).commitTransaction(txn); - allowing(database).containsContact(txn, contactId); - will(returnValue(true)); - // setSeen(contactId, Collections.singletonList(messageId)) - oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.setSeen(contactId, Collections.singletonList(messageId)); - - context.assertIsSatisfied(); - } - - @Test - public void testVisibilityChangedCallsListeners() throws Exception { - final ContactId contactId1 = new ContactId(123); - final Collection both = Arrays.asList(contactId, contactId1); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).getVisibility(txn, groupId); - will(returnValue(both)); - oneOf(database).getContacts(txn); - will(returnValue(both)); - oneOf(database).removeVisibility(txn, contactId1, groupId); - oneOf(database).commitTransaction(txn); - oneOf(listener).eventOccurred(with(any( - SubscriptionsUpdatedEvent.class))); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.setVisibility(groupId, Collections.singletonList(contactId)); - - context.assertIsSatisfied(); - } - - @Test - public void testVisibilityUnchangedDoesNotCallListeners() throws Exception { - final ContactId contactId1 = new ContactId(234); - final Collection both = Arrays.asList(contactId, contactId1); - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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); - final DatabaseListener listener = context.mock(DatabaseListener.class); - context.checking(new Expectations() {{ - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).getVisibility(txn, groupId); - will(returnValue(both)); - oneOf(database).getContacts(txn); - will(returnValue(both)); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addListener(listener); - db.setVisibility(groupId, both); - - context.assertIsSatisfied(); - } - - @Test - public void testTemporarySecrets() throws Exception { - Mockery context = new Mockery(); - @SuppressWarnings("unchecked") - final Database 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() {{ - // addSecrets() - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).containsContactTransport(txn, contactId, - transportId); - will(returnValue(true)); - oneOf(database).addSecrets(txn, - Collections.singletonList(temporarySecret)); - oneOf(database).commitTransaction(txn); - // getSecrets() - oneOf(database).startTransaction(); - will(returnValue(txn)); - oneOf(database).getSecrets(txn); - will(returnValue(Collections.singletonList(temporarySecret))); - oneOf(database).commitTransaction(txn); - }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); - - db.addSecrets(Collections.singletonList(temporarySecret)); - assertEquals(Collections.singletonList(temporarySecret), - db.getSecrets()); - - context.assertIsSatisfied(); - } -} diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java deleted file mode 100644 index d8ff0fc5d..000000000 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ /dev/null @@ -1,2032 +0,0 @@ -package net.sf.briar.db; - -import static net.sf.briar.db.DatabaseConstants.RETRANSMIT_THRESHOLD; -import static org.junit.Assert.assertArrayEquals; - -import java.io.File; -import java.sql.Connection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -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.clock.SystemClock; -import net.sf.briar.api.crypto.Password; -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; -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.MessageId; -import net.sf.briar.api.protocol.Transport; -import net.sf.briar.api.protocol.TransportId; - -import org.apache.commons.io.FileSystemUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class H2DatabaseTest extends BriarTestCase { - - private static final int ONE_MEGABYTE = 1024 * 1024; - private static final int MAX_SIZE = 5 * ONE_MEGABYTE; - - private final File testDir = TestUtils.getTestDirectory(); - // The password has the format - private final String passwordString = "foo bar"; - private final Password password = new TestPassword(); - private final Random random = new Random(); - private final GroupFactory groupFactory; - private final Group group; - private final AuthorId authorId; - private final BatchId batchId; - private final ContactId contactId; - private final GroupId groupId; - private final MessageId messageId, privateMessageId; - private final String subject; - private final long timestamp; - private final int size; - private final byte[] raw; - private final Message message, privateMessage; - private final TransportId transportId; - private final TransportProperties properties; - private final Map remoteProperties; - private final Collection remoteTransports; - - public H2DatabaseTest() throws Exception { - super(); - groupFactory = new TestGroupFactory(); - authorId = new AuthorId(TestUtils.getRandomId()); - batchId = new BatchId(TestUtils.getRandomId()); - contactId = new ContactId(1); - groupId = new GroupId(TestUtils.getRandomId()); - messageId = new MessageId(TestUtils.getRandomId()); - privateMessageId = new MessageId(TestUtils.getRandomId()); - group = new TestGroup(groupId, "Foo", null); - subject = "Foo"; - timestamp = System.currentTimeMillis(); - size = 1234; - raw = new byte[size]; - random.nextBytes(raw); - message = new TestMessage(messageId, null, groupId, authorId, subject, - timestamp, raw); - privateMessage = new TestMessage(privateMessageId, null, null, null, - subject, timestamp, raw); - transportId = new TransportId(TestUtils.getRandomId()); - properties = new TransportProperties( - Collections.singletonMap("foo", "bar")); - remoteProperties = Collections.singletonMap(contactId, properties); - Transport remoteTransport = new Transport(transportId, properties); - remoteTransports = Collections.singletonList(remoteTransport); - } - - @Before - public void setUp() { - testDir.mkdirs(); - } - - @Test - public void testPersistence() throws Exception { - // Store some records - Database db = open(false); - Connection txn = db.startTransaction(); - assertFalse(db.containsContact(txn, contactId)); - assertEquals(contactId, db.addContact(txn)); - assertTrue(db.containsContact(txn, contactId)); - assertFalse(db.containsSubscription(txn, groupId)); - db.addSubscription(txn, group); - assertTrue(db.containsSubscription(txn, groupId)); - assertFalse(db.containsMessage(txn, messageId)); - db.addGroupMessage(txn, message); - assertTrue(db.containsMessage(txn, messageId)); - assertFalse(db.containsMessage(txn, privateMessageId)); - db.addPrivateMessage(txn, privateMessage, contactId); - assertTrue(db.containsMessage(txn, privateMessageId)); - db.commitTransaction(txn); - db.close(); - - // Check that the records are still there - db = open(true); - txn = db.startTransaction(); - assertTrue(db.containsContact(txn, contactId)); - assertTrue(db.containsSubscription(txn, groupId)); - assertTrue(db.containsMessage(txn, messageId)); - byte[] raw1 = db.getMessage(txn, messageId); - assertArrayEquals(raw, raw1); - assertTrue(db.containsMessage(txn, privateMessageId)); - raw1 = db.getMessage(txn, privateMessageId); - assertArrayEquals(raw, raw1); - // Delete the records - db.removeMessage(txn, messageId); - db.removeMessage(txn, privateMessageId); - db.removeContact(txn, contactId); - db.removeSubscription(txn, groupId); - db.commitTransaction(txn); - db.close(); - - // Check that the records are gone - db = open(true); - txn = db.startTransaction(); - assertFalse(db.containsContact(txn, contactId)); - assertEquals(Collections.emptyMap(), - db.getRemoteProperties(txn, transportId)); - assertFalse(db.containsSubscription(txn, groupId)); - assertFalse(db.containsMessage(txn, messageId)); - assertFalse(db.containsMessage(txn, privateMessageId)); - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testContactIdsIncrease() throws Exception { - ContactId contactId1 = new ContactId(2); - ContactId contactId2 = new ContactId(3); - ContactId contactId3 = new ContactId(4); - Database db = open(false); - Connection txn = db.startTransaction(); - - // Create three contacts - assertFalse(db.containsContact(txn, contactId)); - assertEquals(contactId, db.addContact(txn)); - assertTrue(db.containsContact(txn, contactId)); - assertFalse(db.containsContact(txn, contactId1)); - assertEquals(contactId1, db.addContact(txn)); - assertTrue(db.containsContact(txn, contactId1)); - assertFalse(db.containsContact(txn, contactId2)); - assertEquals(contactId2, db.addContact(txn)); - assertTrue(db.containsContact(txn, contactId2)); - // Delete the contact with the highest ID - db.removeContact(txn, contactId2); - assertFalse(db.containsContact(txn, contactId2)); - // Add another contact - a new ID should be created - assertFalse(db.containsContact(txn, contactId3)); - assertEquals(contactId3, db.addContact(txn)); - assertTrue(db.containsContact(txn, contactId3)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testRatings() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Unknown authors should be unrated - assertEquals(Rating.UNRATED, db.getRating(txn, authorId)); - // Store a rating - db.setRating(txn, authorId, Rating.GOOD); - // Check that the rating was stored - assertEquals(Rating.GOOD, db.getRating(txn, authorId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testUnsubscribingRemovesGroupMessage() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group and store a message - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - - // Unsubscribing from the group should remove the message - assertTrue(db.containsMessage(txn, messageId)); - db.removeSubscription(txn, groupId); - assertFalse(db.containsMessage(txn, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testRemovingContactRemovesPrivateMessage() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and store a private message - assertEquals(contactId, db.addContact(txn)); - db.addPrivateMessage(txn, privateMessage, contactId); - - // Removing the contact should remove the message - assertTrue(db.containsMessage(txn, privateMessageId)); - db.removeContact(txn, contactId); - assertFalse(db.containsMessage(txn, privateMessageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendablePrivateMessagesMustHaveStatusNew() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and store a private message - assertEquals(contactId, db.addContact(txn)); - db.addPrivateMessage(txn, privateMessage, contactId); - - // The message has no status yet, so it should not be sendable - assertFalse(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Changing the status to NEW should make the message sendable - db.setStatus(txn, contactId, privateMessageId, Status.NEW); - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(privateMessageId, it.next()); - assertFalse(it.hasNext()); - - // Changing the status to SENT should make the message unsendable - db.setStatus(txn, contactId, privateMessageId, Status.SENT); - assertFalse(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Changing the status to SEEN should also make the message unsendable - db.setStatus(txn, contactId, privateMessageId, Status.SEEN); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendablePrivateMessagesMustFitCapacity() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and store a private message - assertEquals(contactId, db.addContact(txn)); - db.addPrivateMessage(txn, privateMessage, contactId); - db.setStatus(txn, contactId, privateMessageId, Status.NEW); - - // The message is sendable, but too large to send - assertTrue(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, size - 1).iterator(); - assertFalse(it.hasNext()); - - // The message is just the right size to send - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, size).iterator(); - assertTrue(it.hasNext()); - assertEquals(privateMessageId, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendableGroupMessagesMustHavePositiveSendability() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The message should not be sendable - assertFalse(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Changing the sendability to > 0 should make the message sendable - db.setSendability(txn, messageId, 1); - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - // Changing the sendability to 0 should make the message unsendable - db.setSendability(txn, messageId, 0); - assertFalse(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendableGroupMessagesMustHaveStatusNew() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - - // The message has no status yet, so it should not be sendable - assertFalse(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Changing the status to Status.NEW should make the message sendable - db.setStatus(txn, contactId, messageId, Status.NEW); - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - // Changing the status to SENT should make the message unsendable - db.setStatus(txn, contactId, messageId, Status.SENT); - assertFalse(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Changing the status to SEEN should also make the message unsendable - db.setStatus(txn, contactId, messageId, Status.SEEN); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendableGroupMessagesMustBeSubscribed() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The contact is not subscribed, so the message should not be sendable - assertFalse(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // The contact subscribing should make the message sendable - db.addSubscription(txn, contactId, group, 0L); - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - // The contact unsubscribing should make the message unsendable - db.removeSubscriptions(txn, contactId, null, null); - assertFalse(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendableGroupMessagesMustBeNewerThanSubscriptions() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The message is older than the contact's subscription, so it should - // not be sendable - db.addSubscription(txn, contactId, group, timestamp + 1); - assertFalse(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Changing the contact's subscription should make the message sendable - db.removeSubscriptions(txn, contactId, null, null); - db.addSubscription(txn, contactId, group, timestamp); - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendableGroupMessagesMustFitCapacity() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The message is sendable, but too large to send - assertTrue(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, size - 1).iterator(); - assertFalse(it.hasNext()); - - // The message is just the right size to send - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, size).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSendableGroupMessagesMustBeVisible() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The subscription is not visible to the contact, so the message - // should not be sendable - assertFalse(db.hasSendableMessages(txn, contactId)); - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Making the subscription visible should make the message sendable - db.addVisibility(txn, contactId, groupId); - assertTrue(db.hasSendableMessages(txn, contactId)); - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testBatchesToAck() throws Exception { - BatchId batchId1 = new BatchId(TestUtils.getRandomId()); - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and some batches to ack - assertEquals(contactId, db.addContact(txn)); - db.addBatchToAck(txn, contactId, batchId); - db.addBatchToAck(txn, contactId, batchId1); - - // Both batch IDs should be returned - Collection acks = db.getBatchesToAck(txn, contactId, 1234); - assertEquals(2, acks.size()); - assertTrue(acks.contains(batchId)); - assertTrue(acks.contains(batchId1)); - - // Remove the batch IDs - db.removeBatchesToAck(txn, contactId, acks); - - // Both batch IDs should have been removed - acks = db.getBatchesToAck(txn, contactId, 1234); - assertEquals(0, acks.size()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testDuplicateBatchesReceived() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and receive the same batch twice - assertEquals(contactId, db.addContact(txn)); - db.addBatchToAck(txn, contactId, batchId); - db.addBatchToAck(txn, contactId, batchId); - - // The batch ID should only be returned once - Collection acks = db.getBatchesToAck(txn, contactId, 1234); - assertEquals(1, acks.size()); - assertTrue(acks.contains(batchId)); - - // Remove the batch ID - db.removeBatchesToAck(txn, contactId, acks); - - // The batch ID should have been removed - acks = db.getBatchesToAck(txn, contactId, 1234); - assertEquals(0, acks.size()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSameBatchCannotBeSentTwice() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - - // Add an outstanding batch - db.addOutstandingBatch(txn, contactId, batchId, - Collections.singletonList(messageId)); - - // It should not be possible to add the same outstanding batch again - try { - db.addOutstandingBatch(txn, contactId, batchId, - Collections.singletonList(messageId)); - fail(); - } catch(DbException expected) {} - - db.abortTransaction(txn); - db.close(); - } - - @Test - public void testSameBatchCanBeSentToDifferentContacts() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add two contacts, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - ContactId contactId1 = db.addContact(txn); - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - - // Add an outstanding batch for the first contact - db.addOutstandingBatch(txn, contactId, batchId, - Collections.singletonList(messageId)); - - // Add the same outstanding batch for the second contact - db.addOutstandingBatch(txn, contactId1, batchId, - Collections.singletonList(messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testRemoveAckedBatch() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // Retrieve the message from the database and mark it as sent - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - db.setStatus(txn, contactId, messageId, Status.SENT); - db.addOutstandingBatch(txn, contactId, batchId, - Collections.singletonList(messageId)); - - // The message should no longer be sendable - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Pretend that the batch was acked - db.removeAckedBatch(txn, contactId, batchId); - - // The message still should not be sendable - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testRemoveLostBatch() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // Get the message and mark it as sent - Iterator it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - db.setStatus(txn, contactId, messageId, Status.SENT); - db.addOutstandingBatch(txn, contactId, batchId, - Collections.singletonList(messageId)); - - // The message should no longer be sendable - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Pretend that the batch was lost - db.removeLostBatch(txn, contactId, batchId); - - // The message should be sendable again - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testRetransmission() throws Exception { - BatchId[] ids = new BatchId[RETRANSMIT_THRESHOLD + 5]; - for(int i = 0; i < ids.length; i++) { - ids[i] = new BatchId(TestUtils.getRandomId()); - } - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact - assertEquals(contactId, db.addContact(txn)); - - // Add some outstanding batches, a few ms apart - for(int i = 0; i < ids.length; i++) { - db.addOutstandingBatch(txn, contactId, ids[i], - Collections.emptyList()); - Thread.sleep(5); - } - - // The contact acks the batches in reverse order. The first - // RETRANSMIT_THRESHOLD - 1 acks should not trigger any retransmissions - for(int i = 0; i < RETRANSMIT_THRESHOLD - 1; i++) { - db.removeAckedBatch(txn, contactId, ids[ids.length - i - 1]); - Collection lost = db.getLostBatches(txn, contactId); - assertEquals(Collections.emptyList(), lost); - } - - // The next ack should trigger the retransmission of the remaining - // five outstanding batches - int index = ids.length - RETRANSMIT_THRESHOLD; - db.removeAckedBatch(txn, contactId, ids[index]); - Collection lost = db.getLostBatches(txn, contactId); - for(int i = 0; i < index; i++) { - assertTrue(lost.contains(ids[i])); - } - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testNoRetransmission() throws Exception { - BatchId[] ids = new BatchId[RETRANSMIT_THRESHOLD * 2]; - for(int i = 0; i < ids.length; i++) { - ids[i] = new BatchId(TestUtils.getRandomId()); - } - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact - assertEquals(contactId, db.addContact(txn)); - - // Add some outstanding batches, a few ms apart - for(int i = 0; i < ids.length; i++) { - db.addOutstandingBatch(txn, contactId, ids[i], - Collections.emptyList()); - Thread.sleep(5); - } - - // The contact acks the batches in the order they were sent - nothing - // should be retransmitted - for(int i = 0; i < ids.length; i++) { - db.removeAckedBatch(txn, contactId, ids[i]); - Collection lost = db.getLostBatches(txn, contactId); - assertEquals(Collections.emptyList(), lost); - } - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessagesByAuthor() throws Exception { - AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); - MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - Message message1 = new TestMessage(messageId1, null, groupId, authorId1, - subject, timestamp, raw); - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group and store two messages - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - db.addGroupMessage(txn, message1); - - // Check that each message is retrievable via its author - Iterator it = - db.getMessagesByAuthor(txn, authorId).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - it = db.getMessagesByAuthor(txn, authorId1).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId1, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetNumberOfSendableChildren() throws Exception { - MessageId childId1 = new MessageId(TestUtils.getRandomId()); - MessageId childId2 = new MessageId(TestUtils.getRandomId()); - MessageId childId3 = new MessageId(TestUtils.getRandomId()); - GroupId groupId1 = new GroupId(TestUtils.getRandomId()); - Group group1 = groupFactory.createGroup(groupId1, "Another group name", - null); - Message child1 = new TestMessage(childId1, messageId, groupId, - authorId, subject, timestamp, raw); - Message child2 = new TestMessage(childId2, messageId, groupId, - authorId, subject, timestamp, raw); - // The third child is in a different group - Message child3 = new TestMessage(childId3, messageId, groupId1, - authorId, subject, timestamp, raw); - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to the groups and store the messages - db.addSubscription(txn, group); - db.addSubscription(txn, group1); - db.addGroupMessage(txn, message); - db.addGroupMessage(txn, child1); - db.addGroupMessage(txn, child2); - db.addGroupMessage(txn, child3); - // Make all the children sendable - db.setSendability(txn, childId1, 1); - db.setSendability(txn, childId2, 5); - db.setSendability(txn, childId3, 3); - - // There should be two sendable children - assertEquals(2, db.getNumberOfSendableChildren(txn, messageId)); - // Make one of the children unsendable - db.setSendability(txn, childId1, 0); - // Now there should be one sendable child - assertEquals(1, db.getNumberOfSendableChildren(txn, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetOldMessages() throws Exception { - MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - Message message1 = new TestMessage(messageId1, null, groupId, authorId, - subject, timestamp + 1000, raw); - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group and store two messages - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - db.addGroupMessage(txn, message1); - - // Allowing enough capacity for one message should return the older one - Iterator it = db.getOldMessages(txn, size).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - // Allowing enough capacity for both messages should return both - Collection ids = new HashSet(); - for(MessageId id : db.getOldMessages(txn, size * 2)) ids.add(id); - assertEquals(2, ids.size()); - assertTrue(ids.contains(messageId)); - assertTrue(ids.contains(messageId1)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetFreeSpace() throws Exception { - byte[] largeBody = new byte[ONE_MEGABYTE]; - for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i; - Message message1 = new TestMessage(messageId, null, groupId, authorId, - subject, timestamp, largeBody); - Database db = open(false); - - // Sanity check: there should be enough space on disk for this test - String path = testDir.getAbsolutePath(); - assertTrue(FileSystemUtils.freeSpaceKb(path) * 1024L > MAX_SIZE); - - // The free space should not be more than the allowed maximum size - long free = db.getFreeSpace(); - assertTrue(free <= MAX_SIZE); - assertTrue(free > 0); - - // Storing a message should reduce the free space - Connection txn = db.startTransaction(); - db.addSubscription(txn, group); - db.addGroupMessage(txn, message1); - db.commitTransaction(txn); - assertTrue(db.getFreeSpace() < free); - - db.close(); - } - - @Test - public void testCloseWaitsForCommit() throws Exception { - final CountDownLatch closing = new CountDownLatch(1); - final CountDownLatch closed = new CountDownLatch(1); - final AtomicBoolean transactionFinished = new AtomicBoolean(false); - final AtomicBoolean error = new AtomicBoolean(false); - final Database db = open(false); - - // Start a transaction - Connection txn = db.startTransaction(); - // In another thread, close the database - Thread close = new Thread() { - public void run() { - try { - closing.countDown(); - db.close(); - if(!transactionFinished.get()) error.set(true); - closed.countDown(); - } catch(Exception e) { - error.set(true); - } - } - }; - close.start(); - closing.await(); - // Do whatever the transaction needs to do - Thread.sleep(10); - transactionFinished.set(true); - // Commit the transaction - db.commitTransaction(txn); - // The other thread should now terminate - assertTrue(closed.await(5, TimeUnit.SECONDS)); - // Check that the other thread didn't encounter an error - assertFalse(error.get()); - } - - @Test - public void testCloseWaitsForAbort() throws Exception { - final CountDownLatch closing = new CountDownLatch(1); - final CountDownLatch closed = new CountDownLatch(1); - final AtomicBoolean transactionFinished = new AtomicBoolean(false); - final AtomicBoolean error = new AtomicBoolean(false); - final Database db = open(false); - - // Start a transaction - Connection txn = db.startTransaction(); - // In another thread, close the database - Thread close = new Thread() { - public void run() { - try { - closing.countDown(); - db.close(); - if(!transactionFinished.get()) error.set(true); - closed.countDown(); - } catch(Exception e) { - error.set(true); - } - } - }; - close.start(); - closing.await(); - // Do whatever the transaction needs to do - Thread.sleep(10); - transactionFinished.set(true); - // Abort the transaction - db.abortTransaction(txn); - // The other thread should now terminate - assertTrue(closed.await(5, TimeUnit.SECONDS)); - // Check that the other thread didn't encounter an error - assertFalse(error.get()); - } - - @Test - public void testUpdateTransportProperties() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact with a transport - assertEquals(contactId, db.addContact(txn)); - db.setTransports(txn, contactId, remoteTransports, 1); - assertEquals(remoteProperties, - db.getRemoteProperties(txn, transportId)); - - // Replace the transport properties - TransportProperties properties1 = - new TransportProperties(Collections.singletonMap("baz", "bam")); - Transport remoteTransport1 = new Transport(transportId, properties1); - Collection remoteTransports1 = - Collections.singletonList(remoteTransport1); - Map remoteProperties1 = - Collections.singletonMap(contactId, properties1); - db.setTransports(txn, contactId, remoteTransports1, 2); - assertEquals(remoteProperties1, - db.getRemoteProperties(txn, transportId)); - - // Remove the transport properties - properties1 = new TransportProperties(); - remoteTransport1 = new Transport(transportId, properties1); - remoteTransports1 = Collections.singletonList(remoteTransport1); - remoteProperties1 = Collections.singletonMap(contactId, properties1); - db.setTransports(txn, contactId, remoteTransports1, 3); - assertEquals(Collections.emptyMap(), - db.getRemoteProperties(txn, transportId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testLocalTransports() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Set the transport properties - db.setLocalProperties(txn, transportId, properties); - assertEquals(Collections.singletonList(properties), - db.getLocalTransports(txn)); - - // Remove the transport properties but leave the transport - db.setLocalProperties(txn, transportId, new TransportProperties()); - assertEquals(Collections.emptyList(), db.getLocalTransports(txn)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testUpdateTransportConfig() throws Exception { - TransportConfig config = - new TransportConfig(Collections.singletonMap("foo", "bar")); - TransportConfig config1 = - new TransportConfig(Collections.singletonMap("baz", "bam")); - - Database db = open(false); - Connection txn = db.startTransaction(); - - // Set the transport config - db.setConfig(txn, transportId, config); - assertEquals(config, db.getConfig(txn, transportId)); - - // Update the transport config - db.setConfig(txn, transportId, config1); - assertEquals(config1, db.getConfig(txn, transportId)); - - // Remove the transport config - db.setConfig(txn, transportId, new TransportConfig()); - assertEquals(Collections.emptyMap(), db.getConfig(txn, transportId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testTransportsNotUpdatedIfTimestampIsOld() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact with a transport - assertEquals(contactId, db.addContact(txn)); - db.setTransports(txn, contactId, remoteTransports, 1); - assertEquals(remoteProperties, - db.getRemoteProperties(txn, transportId)); - - // Replace the transport properties using a timestamp of 2 - TransportProperties properties1 = - new TransportProperties(Collections.singletonMap("baz", "bam")); - Transport remoteTransport1 = new Transport(transportId, properties1); - Collection remoteTransports1 = - Collections.singletonList(remoteTransport1); - Map remoteProperties1 = - Collections.singletonMap(contactId, properties1); - db.setTransports(txn, contactId, remoteTransports1, 2); - assertEquals(remoteProperties1, - db.getRemoteProperties(txn, transportId)); - - // Try to replace the transport properties using a timestamp of 1 - TransportProperties properties2 = - new TransportProperties(Collections.singletonMap("quux", "etc")); - Transport remoteTransport2 = new Transport(transportId, properties2); - Collection remoteTransports2 = - Collections.singletonList(remoteTransport2); - db.setTransports(txn, contactId, remoteTransports2, 1); - - // The old properties should still be there - assertEquals(remoteProperties1, - db.getRemoteProperties(txn, transportId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageIfSendableReturnsNullIfNotInDatabase() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addSubscription(txn, contactId, group, 0L); - - // The message is not in the database - assertNull(db.getMessageIfSendable(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageIfSendableReturnsNullIfSeen() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - - // Set the sendability to > 0 and the status to SEEN - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.SEEN); - - // The message is not sendable because its status is SEEN - assertNull(db.getMessageIfSendable(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageIfSendableReturnsNullIfNotSendable() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - - // Set the sendability to 0 and the status to NEW - db.setSendability(txn, messageId, 0); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The message is not sendable because its sendability is 0 - assertNull(db.getMessageIfSendable(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageIfSendableReturnsNullIfOld() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - - // the message is older than the contact's subscription - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, timestamp + 1); - db.addGroupMessage(txn, message); - - // Set the sendability to > 0 and the status to NEW - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The message is not sendable because it's too old - assertNull(db.getMessageIfSendable(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageIfSendableReturnsMessage() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - - // Set the sendability to > 0 and the status to NEW - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The message is sendable so it should be returned - byte[] b = db.getMessageIfSendable(txn, contactId, messageId); - assertArrayEquals(raw, b); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetStatusSeenIfVisibleRequiresMessageInDatabase() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - - // The message is not in the database - assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetStatusSeenIfVisibleRequiresLocalSubscription() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact with a subscription - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, contactId, group, 0L); - - // There's no local subscription for the group - assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetStatusSeenIfVisibleRequiresContactSubscription() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // There's no contact subscription for the group - assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetStatusSeenIfVisibleRequiresVisibility() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - db.addSubscription(txn, contactId, group, 0L); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // The subscription is not visible - assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetStatusSeenIfVisibleReturnsTrueIfAlreadySeen() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - - // The message has already been seen by the contact - db.setStatus(txn, contactId, messageId, Status.SEEN); - - assertTrue(db.setStatusSeenIfVisible(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetStatusSeenIfVisibleReturnsTrueIfNew() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - - // The message has not been seen by the contact - db.setStatus(txn, contactId, messageId, Status.NEW); - - assertTrue(db.setStatusSeenIfVisible(txn, contactId, messageId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testVisibility() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - // The group should not be visible to the contact - assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); - // Make the group visible to the contact - db.addVisibility(txn, contactId, groupId); - assertEquals(Collections.singletonList(contactId), - db.getVisibility(txn, groupId)); - // Make the group invisible again - db.removeVisibility(txn, contactId, groupId); - assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetGroupMessageParentWithNoParent() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group - db.addSubscription(txn, group); - - // A message with no parent should return null - MessageId childId = new MessageId(TestUtils.getRandomId()); - Message child = new TestMessage(childId, null, groupId, null, subject, - timestamp, raw); - db.addGroupMessage(txn, child); - assertTrue(db.containsMessage(txn, childId)); - assertNull(db.getGroupMessageParent(txn, childId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetGroupMessageParentWithAbsentParent() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group - db.addSubscription(txn, group); - - // A message with an absent parent should return null - MessageId childId = new MessageId(TestUtils.getRandomId()); - MessageId parentId = new MessageId(TestUtils.getRandomId()); - Message child = new TestMessage(childId, parentId, groupId, null, - subject, timestamp, raw); - db.addGroupMessage(txn, child); - assertTrue(db.containsMessage(txn, childId)); - assertFalse(db.containsMessage(txn, parentId)); - assertNull(db.getGroupMessageParent(txn, childId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetGroupMessageParentWithParentInAnotherGroup() - throws Exception { - GroupId groupId1 = new GroupId(TestUtils.getRandomId()); - Group group1 = groupFactory.createGroup(groupId1, "Group name", null); - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to two groups - db.addSubscription(txn, group); - db.addSubscription(txn, group1); - - // A message with a parent in another group should return null - MessageId childId = new MessageId(TestUtils.getRandomId()); - MessageId parentId = new MessageId(TestUtils.getRandomId()); - Message child = new TestMessage(childId, parentId, groupId, null, - subject, timestamp, raw); - Message parent = new TestMessage(parentId, null, groupId1, null, - subject, timestamp, raw); - db.addGroupMessage(txn, child); - db.addGroupMessage(txn, parent); - assertTrue(db.containsMessage(txn, childId)); - assertTrue(db.containsMessage(txn, parentId)); - assertNull(db.getGroupMessageParent(txn, childId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetGroupMessageParentWithPrivateParent() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - - // A message with a private parent should return null - MessageId childId = new MessageId(TestUtils.getRandomId()); - Message child = new TestMessage(childId, privateMessageId, groupId, - null, subject, timestamp, raw); - db.addGroupMessage(txn, child); - db.addPrivateMessage(txn, privateMessage, contactId); - assertTrue(db.containsMessage(txn, childId)); - assertTrue(db.containsMessage(txn, privateMessageId)); - assertNull(db.getGroupMessageParent(txn, childId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetGroupMessageParentWithParentInSameGroup() - throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group - db.addSubscription(txn, group); - - // A message with a parent in the same group should return the parent - MessageId childId = new MessageId(TestUtils.getRandomId()); - MessageId parentId = new MessageId(TestUtils.getRandomId()); - Message child = new TestMessage(childId, parentId, groupId, null, - subject, timestamp, raw); - Message parent = new TestMessage(parentId, null, groupId, null, - subject, timestamp, raw); - db.addGroupMessage(txn, child); - db.addGroupMessage(txn, parent); - assertTrue(db.containsMessage(txn, childId)); - assertTrue(db.containsMessage(txn, parentId)); - assertEquals(parentId, db.getGroupMessageParent(txn, childId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageBody() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - - // Store a couple of messages - int bodyLength = raw.length - 20; - Message message1 = new TestMessage(messageId, null, groupId, null, - subject, timestamp, raw, 5, bodyLength); - Message privateMessage1 = new TestMessage(privateMessageId, null, null, - null, subject, timestamp, raw, 10, bodyLength); - db.addGroupMessage(txn, message1); - db.addPrivateMessage(txn, privateMessage1, contactId); - - // Calculate the expected message bodies - byte[] expectedBody = new byte[bodyLength]; - System.arraycopy(raw, 5, expectedBody, 0, bodyLength); - assertFalse(Arrays.equals(expectedBody, new byte[bodyLength])); - byte[] expectedBody1 = new byte[bodyLength]; - System.arraycopy(raw, 10, expectedBody1, 0, bodyLength); - System.arraycopy(raw, 10, expectedBody1, 0, bodyLength); - - // Retrieve the raw messages - assertArrayEquals(raw, db.getMessage(txn, messageId)); - assertArrayEquals(raw, db.getMessage(txn, privateMessageId)); - - // Retrieve the message bodies - byte[] body = db.getMessageBody(txn, messageId); - assertArrayEquals(expectedBody, body); - byte[] body1 = db.getMessageBody(txn, privateMessageId); - assertArrayEquals(expectedBody1, body1); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetMessageHeaders() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group - db.addSubscription(txn, group); - - // Store a couple of messages - db.addGroupMessage(txn, message); - MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - MessageId parentId = new MessageId(TestUtils.getRandomId()); - long timestamp1 = System.currentTimeMillis(); - Message message1 = new TestMessage(messageId1, parentId, groupId, - authorId, subject, timestamp1, raw); - db.addGroupMessage(txn, message1); - // Mark one of the messages read - assertFalse(db.setRead(txn, messageId, true)); - - // Retrieve the message headers - Collection headers = db.getMessageHeaders(txn, groupId); - Iterator it = headers.iterator(); - boolean messageFound = false, message1Found = false; - // First header (order is undefined) - assertTrue(it.hasNext()); - MessageHeader header = it.next(); - if(messageId.equals(header.getId())) { - assertHeadersMatch(message, header); - assertTrue(header.getRead()); - assertFalse(header.getStarred()); - messageFound = true; - } else if(messageId1.equals(header.getId())) { - assertHeadersMatch(message1, header); - assertFalse(header.getRead()); - assertFalse(header.getStarred()); - message1Found = true; - } else { - fail(); - } - // Second header - assertTrue(it.hasNext()); - header = it.next(); - if(messageId.equals(header.getId())) { - assertHeadersMatch(message, header); - assertTrue(header.getRead()); - assertFalse(header.getStarred()); - messageFound = true; - } else if(messageId1.equals(header.getId())) { - assertHeadersMatch(message1, header); - assertFalse(header.getRead()); - assertFalse(header.getStarred()); - message1Found = true; - } else { - fail(); - } - // No more headers - assertFalse(it.hasNext()); - assertTrue(messageFound); - assertTrue(message1Found); - - db.commitTransaction(txn); - db.close(); - } - - private void assertHeadersMatch(Message m, MessageHeader h) { - assertEquals(m.getId(), h.getId()); - if(m.getParent() == null) assertNull(h.getParent()); - else assertEquals(m.getParent(), h.getParent()); - if(m.getGroup() == null) assertNull(h.getGroup()); - else assertEquals(m.getGroup(), h.getGroup()); - if(m.getAuthor() == null) assertNull(h.getAuthor()); - else assertEquals(m.getAuthor(), h.getAuthor()); - assertEquals(m.getSubject(), h.getSubject()); - assertEquals(m.getTimestamp(), h.getTimestamp()); - } - - @Test - public void testReadFlag() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group and store a message - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - - // The message should be unread by default - assertFalse(db.getRead(txn, messageId)); - // Marking the message read should return the old value - assertFalse(db.setRead(txn, messageId, true)); - assertTrue(db.setRead(txn, messageId, true)); - // The message should be read - assertTrue(db.getRead(txn, messageId)); - // Marking the message unread should return the old value - assertTrue(db.setRead(txn, messageId, false)); - assertFalse(db.setRead(txn, messageId, false)); - // Unsubscribe from the group - db.removeSubscription(txn, groupId); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testStarredFlag() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a group and store a message - db.addSubscription(txn, group); - db.addGroupMessage(txn, message); - - // The message should be unstarred by default - assertFalse(db.getStarred(txn, messageId)); - // Starring the message should return the old value - assertFalse(db.setStarred(txn, messageId, true)); - assertTrue(db.setStarred(txn, messageId, true)); - // The message should be starred - assertTrue(db.getStarred(txn, messageId)); - // Unstarring the message should return the old value - assertTrue(db.setStarred(txn, messageId, false)); - assertFalse(db.setStarred(txn, messageId, false)); - // Unsubscribe from the group - db.removeSubscription(txn, groupId); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testGetUnreadMessageCounts() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to a couple of groups - db.addSubscription(txn, group); - GroupId groupId1 = new GroupId(TestUtils.getRandomId()); - Group group1 = groupFactory.createGroup(groupId1, "Another group", - null); - db.addSubscription(txn, group1); - - // Store two messages in the first group - db.addGroupMessage(txn, message); - MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - Message message1 = new TestMessage(messageId1, null, groupId, - authorId, subject, timestamp, raw); - db.addGroupMessage(txn, message1); - - // Store one message in the second group - MessageId messageId2 = new MessageId(TestUtils.getRandomId()); - Message message2 = new TestMessage(messageId2, null, groupId1, - authorId, subject, timestamp, raw); - db.addGroupMessage(txn, message2); - - // Mark one of the messages in the first group read - assertFalse(db.setRead(txn, messageId, true)); - - // There should be one unread message in each group - Map counts = db.getUnreadMessageCounts(txn); - assertEquals(2, counts.size()); - Integer count = counts.get(groupId); - assertNotNull(count); - assertEquals(1, count.intValue()); - count = counts.get(groupId1); - assertNotNull(count); - assertEquals(1, count.intValue()); - - // Mark the read message unread (it will now be false rather than null) - assertTrue(db.setRead(txn, messageId, false)); - - // Mark the message in the second group read - assertFalse(db.setRead(txn, messageId2, true)); - - // There should be two unread messages in the first group, none in - // the second group - counts = db.getUnreadMessageCounts(txn); - assertEquals(1, counts.size()); - count = counts.get(groupId); - assertNotNull(count); - assertEquals(2, count.intValue()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testMultipleSubscriptionsAndUnsubscriptions() throws Exception { - // Create some groups - List groups = new ArrayList(); - for(int i = 0; i < 100; i++) { - GroupId id = new GroupId(TestUtils.getRandomId()); - groups.add(groupFactory.createGroup(id, "Group name", null)); - } - - Database db = open(false); - Connection txn = db.startTransaction(); - - // Subscribe to the groups and add a contact - for(Group g : groups) db.addSubscription(txn, g); - assertEquals(contactId, db.addContact(txn)); - - // Make the groups visible to the contact - Collections.shuffle(groups); - for(Group g : groups) db.addVisibility(txn, contactId, g.getId()); - - // Make some of the groups invisible to the contact and remove them all - Collections.shuffle(groups); - for(Group g : groups) { - if(Math.random() < 0.5) - db.removeVisibility(txn, contactId, g.getId()); - db.removeSubscription(txn, g.getId()); - } - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testTemporarySecrets() throws Exception { - // Create some temporary secrets - Random random = new Random(); - byte[] secret1 = new byte[32], bitmap1 = new byte[4]; - random.nextBytes(secret1); - random.nextBytes(bitmap1); - TemporarySecret s1 = new TemporarySecret(contactId, transportId, 123L, - 234L, 345L, false, 0L, secret1, 456L, 567L, bitmap1); - byte[] secret2 = new byte[32], bitmap2 = new byte[4]; - random.nextBytes(secret2); - random.nextBytes(bitmap2); - TemporarySecret s2 = new TemporarySecret(contactId, transportId, 1234L, - 2345L, 3456L, false, 1L, secret2, 4567L, 5678L, bitmap2); - byte[] secret3 = new byte[32], bitmap3 = new byte[4]; - random.nextBytes(secret3); - random.nextBytes(bitmap3); - TemporarySecret s3 = new TemporarySecret(contactId, transportId, 12345L, - 23456L, 34567L, false, 0L, secret3, 45678L, 56789L, bitmap3); - - Database db = open(false); - Connection txn = db.startTransaction(); - - // Initially there should be no secrets in the database - assertEquals(Collections.emptyList(), db.getSecrets(txn)); - - // Add a contact and the first two secrets - assertEquals(contactId, db.addContact(txn)); - db.addSecrets(txn, Arrays.asList(s1, s2)); - - // Retrieve the first two secrets - Collection secrets = db.getSecrets(txn); - assertEquals(2, secrets.size()); - boolean foundFirst = false, foundSecond = false; - for(TemporarySecret s : secrets) { - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - if(s.getPeriod() == 0L) { - assertEquals(s1.getEpoch(), s.getEpoch()); - assertEquals(s1.getClockDifference(), s.getClockDifference()); - assertEquals(s1.getLatency(), s.getLatency()); - assertEquals(s1.getAlice(), s.getAlice()); - assertArrayEquals(s1.getSecret(), s.getSecret()); - assertEquals(s1.getOutgoingConnectionCounter(), - s.getOutgoingConnectionCounter()); - assertEquals(s1.getWindowCentre(), s.getWindowCentre()); - assertArrayEquals(s1.getWindowBitmap(), s.getWindowBitmap()); - foundFirst = true; - } else if(s.getPeriod() == 1L) { - assertEquals(s2.getEpoch(), s.getEpoch()); - assertEquals(s2.getClockDifference(), s.getClockDifference()); - assertEquals(s2.getLatency(), s.getLatency()); - assertEquals(s2.getAlice(), s.getAlice()); - assertArrayEquals(s2.getSecret(), s.getSecret()); - assertEquals(s2.getOutgoingConnectionCounter(), - s.getOutgoingConnectionCounter()); - assertEquals(s2.getWindowCentre(), s.getWindowCentre()); - assertArrayEquals(s2.getWindowBitmap(), s.getWindowBitmap()); - foundSecond = true; - } else { - fail(); - } - } - assertTrue(foundFirst); - assertTrue(foundSecond); - - // Adding the third secret (period 2) should delete the first (period 0) - db.addSecrets(txn, Arrays.asList(s3)); - secrets = db.getSecrets(txn); - assertEquals(2, secrets.size()); - foundSecond = false; - boolean foundThird = false; - for(TemporarySecret s : secrets) { - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - if(s.getPeriod() == 1L) { - assertEquals(s2.getEpoch(), s.getEpoch()); - assertEquals(s2.getClockDifference(), s.getClockDifference()); - assertEquals(s2.getLatency(), s.getLatency()); - assertEquals(s2.getAlice(), s.getAlice()); - assertArrayEquals(s2.getSecret(), s.getSecret()); - assertEquals(s2.getOutgoingConnectionCounter(), - s.getOutgoingConnectionCounter()); - assertEquals(s2.getWindowCentre(), s.getWindowCentre()); - assertArrayEquals(s2.getWindowBitmap(), s.getWindowBitmap()); - foundSecond = true; - } else if(s.getPeriod() == 2L) { - assertEquals(s3.getEpoch(), s.getEpoch()); - assertEquals(s3.getClockDifference(), s.getClockDifference()); - assertEquals(s3.getLatency(), s.getLatency()); - assertEquals(s3.getAlice(), s.getAlice()); - assertArrayEquals(s3.getSecret(), s.getSecret()); - assertEquals(s3.getOutgoingConnectionCounter(), - s.getOutgoingConnectionCounter()); - assertEquals(s3.getWindowCentre(), s.getWindowCentre()); - assertArrayEquals(s3.getWindowBitmap(), s.getWindowBitmap()); - foundThird = true; - } else { - fail(); - } - } - assertTrue(foundSecond); - assertTrue(foundThird); - - // Removing the contact should remove the secrets - db.removeContact(txn, contactId); - assertEquals(Collections.emptyList(), db.getSecrets(txn)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testIncrementConnectionCounter() throws Exception { - // Create a temporary secret - Random random = new Random(); - byte[] secret = new byte[32], bitmap = new byte[4]; - random.nextBytes(secret); - TemporarySecret s = new TemporarySecret(contactId, transportId, 0L, - 0L, 0L, false, 0L, secret, 0L, 0L, bitmap); - - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and the temporary secret - assertEquals(contactId, db.addContact(txn)); - db.addSecrets(txn, Arrays.asList(s)); - - // Retrieve the secret - Collection secrets = db.getSecrets(txn); - assertEquals(1, secrets.size()); - s = secrets.iterator().next(); - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - assertEquals(0L, s.getPeriod()); - assertArrayEquals(secret, s.getSecret()); - assertEquals(0L, s.getOutgoingConnectionCounter()); - assertEquals(0L, s.getWindowCentre()); - assertArrayEquals(bitmap, s.getWindowBitmap()); - - // Increment the connection counter twice and retrieve the secret again - assertEquals(0L, db.incrementConnectionCounter(txn, contactId, - transportId, 0L)); - assertEquals(1L, db.incrementConnectionCounter(txn, contactId, - transportId, 0L)); - secrets = db.getSecrets(txn); - assertEquals(1, secrets.size()); - s = secrets.iterator().next(); - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - assertEquals(0L, s.getPeriod()); - assertArrayEquals(secret, s.getSecret()); - assertEquals(2L, s.getOutgoingConnectionCounter()); - assertEquals(0L, s.getWindowCentre()); - assertArrayEquals(bitmap, s.getWindowBitmap()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testSetConnectionWindow() throws Exception { - // Create a temporary secret - Random random = new Random(); - byte[] secret = new byte[32], bitmap = new byte[4]; - random.nextBytes(secret); - TemporarySecret s = new TemporarySecret(contactId, transportId, 0L, - 0L, 0L, false, 0L, secret, 0L, 0L, bitmap); - - Database db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and the temporary secret - assertEquals(contactId, db.addContact(txn)); - db.addSecrets(txn, Arrays.asList(s)); - - // Retrieve the secret - Collection secrets = db.getSecrets(txn); - assertEquals(1, secrets.size()); - s = secrets.iterator().next(); - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - assertEquals(0L, s.getPeriod()); - assertArrayEquals(secret, s.getSecret()); - assertEquals(0L, s.getOutgoingConnectionCounter()); - assertEquals(0L, s.getWindowCentre()); - assertArrayEquals(bitmap, s.getWindowBitmap()); - - // Update the connection window and retrieve the secret again - db.setConnectionWindow(txn, contactId, transportId, 0L, 1L, bitmap); - bitmap[0] = 4; - db.setConnectionWindow(txn, contactId, transportId, 0L, 1L, bitmap); - secrets = db.getSecrets(txn); - assertEquals(1, secrets.size()); - s = secrets.iterator().next(); - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - assertEquals(0L, s.getPeriod()); - assertArrayEquals(secret, s.getSecret()); - assertEquals(0L, s.getOutgoingConnectionCounter()); - assertEquals(1L, s.getWindowCentre()); - assertArrayEquals(bitmap, s.getWindowBitmap()); - - // Updating a nonexistent window should not throw an exception - db.setConnectionWindow(txn, contactId, transportId, 1L, 1L, bitmap); - // The nonexistent window should not have been created - secrets = db.getSecrets(txn); - assertEquals(1, secrets.size()); - s = secrets.iterator().next(); - assertEquals(contactId, s.getContactId()); - assertEquals(transportId, s.getTransportId()); - assertEquals(0L, s.getPeriod()); - assertArrayEquals(secret, s.getSecret()); - assertEquals(0L, s.getOutgoingConnectionCounter()); - assertEquals(1L, s.getWindowCentre()); - assertArrayEquals(bitmap, s.getWindowBitmap()); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testContactTransports() throws Exception { - // Create some contact transports - TransportId transportId1 = new TransportId(TestUtils.getRandomId()); - ContactTransport ct1 = new ContactTransport(contactId, transportId, - 123L, 2345L, 34567L, true); - ContactTransport ct2 = new ContactTransport(contactId, transportId1, - 12345L, 2345L, 345L, false); - - Database db = open(false); - Connection txn = db.startTransaction(); - - // Initially there should be no contact transports in the database - assertEquals(Collections.emptyList(), db.getContactTransports(txn)); - - // Add a contact and the contact transports - assertEquals(contactId, db.addContact(txn)); - db.addContactTransport(txn, ct1); - db.addContactTransport(txn, ct2); - - // Retrieve the contact transports - Collection cts = db.getContactTransports(txn); - assertEquals(2, cts.size()); - boolean foundFirst = false, foundSecond = false; - for(ContactTransport ct : cts) { - assertEquals(contactId, ct.getContactId()); - if(ct.getTransportId().equals(transportId)) { - assertEquals(123L, ct.getEpoch()); - assertEquals(2345L, ct.getClockDifference()); - assertEquals(34567L, ct.getLatency()); - assertTrue(ct.getAlice()); - foundFirst = true; - } else if(ct.getTransportId().equals(transportId1)) { - assertEquals(12345L, ct.getEpoch()); - assertEquals(2345L, ct.getClockDifference()); - assertEquals(345L, ct.getLatency()); - assertFalse(ct.getAlice()); - foundSecond = true; - } else { - fail(); - } - } - assertTrue(foundFirst); - assertTrue(foundSecond); - - // Removing the contact should remove the contact transports - db.removeContact(txn, contactId); - assertEquals(Collections.emptyList(), db.getContactTransports(txn)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testExceptionHandling() throws Exception { - Database db = open(false); - Connection txn = db.startTransaction(); - try { - // Ask for a nonexistent message - an exception should be thrown - db.getMessage(txn, messageId); - fail(); - } catch(DbException expected) { - // It should be possible to abort the transaction without error - db.abortTransaction(txn); - } - // It should be possible to close the database cleanly - db.close(); - } - - private Database open(boolean resume) throws Exception { - Database db = new H2Database(testDir, password, MAX_SIZE, - groupFactory, new SystemClock()); - db.open(resume); - return db; - } - - @After - public void tearDown() { - TestUtils.deleteTestDirectory(testDir); - } - - private class TestPassword implements Password { - - public char[] getPassword() { - return passwordString.toCharArray(); - } - } -} diff --git a/test/net/sf/briar/db/TestGroup.java b/test/net/sf/briar/db/TestGroup.java deleted file mode 100644 index 4e78e0788..000000000 --- a/test/net/sf/briar/db/TestGroup.java +++ /dev/null @@ -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; - } -} diff --git a/test/net/sf/briar/db/TestGroupFactory.java b/test/net/sf/briar/db/TestGroupFactory.java deleted file mode 100644 index 89f29b92d..000000000 --- a/test/net/sf/briar/db/TestGroupFactory.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/test/net/sf/briar/db/TestMessage.java b/test/net/sf/briar/db/TestMessage.java deleted file mode 100644 index 4dbe7584a..000000000 --- a/test/net/sf/briar/db/TestMessage.java +++ /dev/null @@ -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(); - } -} diff --git a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java deleted file mode 100644 index da3821569..000000000 --- a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java +++ /dev/null @@ -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 handles = new HashSet(); - 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(); - } -} diff --git a/test/net/sf/briar/lifecycle/WindowsShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/WindowsShutdownManagerImplTest.java deleted file mode 100644 index ce872e97d..000000000 --- a/test/net/sf/briar/lifecycle/WindowsShutdownManagerImplTest.java +++ /dev/null @@ -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(); - } - } - } -} diff --git a/test/net/sf/briar/plugins/DuplexClientTest.java b/test/net/sf/briar/plugins/DuplexClientTest.java deleted file mode 100644 index 2e58e14ba..000000000 --- a/test/net/sf/briar/plugins/DuplexClientTest.java +++ /dev/null @@ -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 remote = null; - - public ClientCallback(TransportConfig config, TransportProperties local, - Map remote) { - this.config = config; - this.local = local; - this.remote = remote; - } - - public TransportConfig getConfig() { - return config; - } - - public TransportProperties getLocalProperties() { - return local; - } - - public Map 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) {} - } -} diff --git a/test/net/sf/briar/plugins/DuplexServerTest.java b/test/net/sf/briar/plugins/DuplexServerTest.java deleted file mode 100644 index 662f299b8..000000000 --- a/test/net/sf/briar/plugins/DuplexServerTest.java +++ /dev/null @@ -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 remote; - - public ServerCallback(TransportConfig config, TransportProperties local, - Map remote) { - this.config = config; - this.local = local; - this.remote = remote; - } - - public TransportConfig getConfig() { - return config; - } - - public TransportProperties getLocalProperties() { - return local; - } - - public Map 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) {} - } -} diff --git a/test/net/sf/briar/plugins/DuplexTest.java b/test/net/sf/briar/plugins/DuplexTest.java deleted file mode 100644 index 80eee278f..000000000 --- a/test/net/sf/briar/plugins/DuplexTest.java +++ /dev/null @@ -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; - } - } -} diff --git a/test/net/sf/briar/plugins/ImmediateExecutor.java b/test/net/sf/briar/plugins/ImmediateExecutor.java deleted file mode 100644 index fe21666e0..000000000 --- a/test/net/sf/briar/plugins/ImmediateExecutor.java +++ /dev/null @@ -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(); - } -} diff --git a/test/net/sf/briar/plugins/PluginManagerImplTest.java b/test/net/sf/briar/plugins/PluginManagerImplTest.java deleted file mode 100644 index 79fcee1b1..000000000 --- a/test/net/sf/briar/plugins/PluginManagerImplTest.java +++ /dev/null @@ -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(); - } -} diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java deleted file mode 100644 index 402b9509f..000000000 --- a/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java +++ /dev/null @@ -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 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(); - } - } -} diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java deleted file mode 100644 index d59dda8f5..000000000 --- a/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java +++ /dev/null @@ -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(); - } - } -} diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothTest.java deleted file mode 100644 index 17676bb08..000000000 --- a/test/net/sf/briar/plugins/bluetooth/BluetoothTest.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/test/net/sf/briar/plugins/email/GmailPluginTest.java b/test/net/sf/briar/plugins/email/GmailPluginTest.java deleted file mode 100644 index 8d20fc3db..000000000 --- a/test/net/sf/briar/plugins/email/GmailPluginTest.java +++ /dev/null @@ -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 map = new HashMap(); - - @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 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(); - } - -} diff --git a/test/net/sf/briar/plugins/file/LinuxRemovableDriveFinderTest.java b/test/net/sf/briar/plugins/file/LinuxRemovableDriveFinderTest.java deleted file mode 100644 index 6634adc65..000000000 --- a/test/net/sf/briar/plugins/file/LinuxRemovableDriveFinderTest.java +++ /dev/null @@ -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)); - } -} diff --git a/test/net/sf/briar/plugins/file/MacRemovableDriveFinderTest.java b/test/net/sf/briar/plugins/file/MacRemovableDriveFinderTest.java deleted file mode 100644 index 5b0fae470..000000000 --- a/test/net/sf/briar/plugins/file/MacRemovableDriveFinderTest.java +++ /dev/null @@ -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 = " 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)); - } -} diff --git a/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java b/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java deleted file mode 100644 index caf2ecca1..000000000 --- a/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java +++ /dev/null @@ -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 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 detected = new ArrayList(); - 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 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(); - } -} diff --git a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java deleted file mode 100644 index c2883cdbf..000000000 --- a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java +++ /dev/null @@ -1,376 +0,0 @@ -package net.sf.briar.plugins.file; - -import static net.sf.briar.api.transport.TransportConstants.MIN_CONNECTION_LENGTH; -import static org.junit.Assert.assertArrayEquals; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Executor; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.ContactId; -import net.sf.briar.api.plugins.simplex.SimplexPluginCallback; -import net.sf.briar.api.plugins.simplex.SimplexTransportWriter; -import net.sf.briar.plugins.ImmediateExecutor; -import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class RemovableDrivePluginTest extends BriarTestCase { - - private final File testDir = TestUtils.getTestDirectory(); - private final ContactId contactId = new ContactId(234); - - @Before - public void setUp() { - testDir.mkdirs(); - } - - @Test - public void testGetId() { - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - - assertArrayEquals(RemovableDrivePlugin.TRANSPORT_ID, - plugin.getId().getBytes()); - - context.assertIsSatisfied(); - } - - @Test - public void testWriterIsNullIfNoDrivesAreFound() throws Exception { - final List drives = Collections.emptyList(); - - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(finder).findRemovableDrives(); - will(returnValue(drives)); - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - assertNull(plugin.createWriter(contactId)); - - context.assertIsSatisfied(); - } - - @Test - public void testWriterIsNullIfNoDriveIsChosen() throws Exception { - final File drive1 = new File(testDir, "1"); - final File drive2 = new File(testDir, "2"); - final List drives = new ArrayList(); - drives.add(drive1); - drives.add(drive2); - - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(finder).findRemovableDrives(); - will(returnValue(drives)); - oneOf(callback).showChoice(with(any(String[].class)), - with(any(String.class))); - will(returnValue(-1)); // The user cancelled the choice - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - assertNull(plugin.createWriter(contactId)); - File[] files = drive1.listFiles(); - assertTrue(files == null || files.length == 0); - - context.assertIsSatisfied(); - } - - @Test - public void testWriterIsNullIfOutputDirDoesNotExist() throws Exception { - final File drive1 = new File(testDir, "1"); - final File drive2 = new File(testDir, "2"); - final List drives = new ArrayList(); - drives.add(drive1); - drives.add(drive2); - - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(finder).findRemovableDrives(); - will(returnValue(drives)); - oneOf(callback).showChoice(with(any(String[].class)), - with(any(String.class))); - will(returnValue(0)); // The user chose drive1 but it doesn't exist - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - assertNull(plugin.createWriter(contactId)); - File[] files = drive1.listFiles(); - assertTrue(files == null || files.length == 0); - - context.assertIsSatisfied(); - } - - @Test - public void testWriterIsNullIfOutputDirIsAFile() throws Exception { - final File drive1 = new File(testDir, "1"); - final File drive2 = new File(testDir, "2"); - final List drives = new ArrayList(); - drives.add(drive1); - drives.add(drive2); - // Create drive1 as a file rather than a directory - assertTrue(drive1.createNewFile()); - - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(finder).findRemovableDrives(); - will(returnValue(drives)); - oneOf(callback).showChoice(with(any(String[].class)), - with(any(String.class))); - will(returnValue(0)); // The user chose drive1 but it's not a dir - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - assertNull(plugin.createWriter(contactId)); - File[] files = drive1.listFiles(); - assertTrue(files == null || files.length == 0); - - context.assertIsSatisfied(); - } - - @Test - public void testWriterIsNotNullIfOutputDirIsADir() throws Exception { - final File drive1 = new File(testDir, "1"); - final File drive2 = new File(testDir, "2"); - final List drives = new ArrayList(); - drives.add(drive1); - drives.add(drive2); - // Create drive1 as a directory - assertTrue(drive1.mkdir()); - - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(finder).findRemovableDrives(); - will(returnValue(drives)); - oneOf(callback).showChoice(with(any(String[].class)), - with(any(String.class))); - will(returnValue(0)); // The user chose drive1 - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - assertNotNull(plugin.createWriter(contactId)); - // The output file should exist and should be empty - File[] files = drive1.listFiles(); - assertNotNull(files); - assertEquals(1, files.length); - assertEquals(0L, files[0].length()); - - context.assertIsSatisfied(); - } - - @Test - public void testWritingToWriter() throws Exception { - final File drive1 = new File(testDir, "1"); - final File drive2 = new File(testDir, "2"); - final List drives = new ArrayList(); - drives.add(drive1); - drives.add(drive2); - // Create drive1 as a directory - assertTrue(drive1.mkdir()); - - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(finder).findRemovableDrives(); - will(returnValue(drives)); - oneOf(callback).showChoice(with(any(String[].class)), - with(any(String.class))); - will(returnValue(0)); // The user chose drive1 - oneOf(callback).showMessage(with(any(String.class))); - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - SimplexTransportWriter writer = plugin.createWriter(contactId); - assertNotNull(writer); - // The output file should exist and should be empty - File[] files = drive1.listFiles(); - assertNotNull(files); - assertEquals(1, files.length); - assertEquals(0L, files[0].length()); - // Writing to the output stream should increase the size of the file - OutputStream out = writer.getOutputStream(); - out.write(new byte[123]); - out.flush(); - out.close(); - // Disposing of the writer should not delete the file - writer.dispose(false); - assertTrue(files[0].exists()); - assertEquals(123L, files[0].length()); - - context.assertIsSatisfied(); - } - - @Test - public void testEmptyDriveIsIgnored() throws Exception { - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - plugin.start(); - - plugin.driveInserted(testDir); - - context.assertIsSatisfied(); - } - - @Test - public void testFilenames() { - Mockery context = new Mockery(); - final Executor executor = context.mock(Executor.class); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor, - callback, finder, monitor); - - assertFalse(plugin.isPossibleConnectionFilename("abcdefg.dat")); - assertFalse(plugin.isPossibleConnectionFilename("abcdefghi.dat")); - assertFalse(plugin.isPossibleConnectionFilename("abcdefgh_dat")); - assertFalse(plugin.isPossibleConnectionFilename("abcdefgh.rat")); - assertTrue(plugin.isPossibleConnectionFilename("abcdefgh.dat")); - assertTrue(plugin.isPossibleConnectionFilename("ABCDEFGH.DAT")); - - context.assertIsSatisfied(); - } - - @Test - public void testReaderIsCreated() throws Exception { - Mockery context = new Mockery(); - final SimplexPluginCallback callback = - context.mock(SimplexPluginCallback.class); - final RemovableDriveFinder finder = - context.mock(RemovableDriveFinder.class); - final RemovableDriveMonitor monitor = - context.mock(RemovableDriveMonitor.class); - - context.checking(new Expectations() {{ - oneOf(monitor).start(with(any(Callback.class))); - oneOf(callback).readerCreated(with(any(FileTransportReader.class))); - }}); - - RemovableDrivePlugin plugin = new RemovableDrivePlugin( - new ImmediateExecutor(), callback, finder, monitor); - plugin.start(); - - File f = new File(testDir, "abcdefgh.dat"); - OutputStream out = new FileOutputStream(f); - out.write(new byte[MIN_CONNECTION_LENGTH]); - out.flush(); - out.close(); - assertEquals(MIN_CONNECTION_LENGTH, f.length()); - plugin.driveInserted(testDir); - - context.assertIsSatisfied(); - } - - @After - public void tearDown() { - TestUtils.deleteTestDirectory(testDir); - } -} diff --git a/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java b/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java deleted file mode 100644 index 4e7af6079..000000000 --- a/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package net.sf.briar.plugins.file; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback; -import net.sf.briar.util.OsUtils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class UnixRemovableDriveMonitorTest extends BriarTestCase { - - private final File testDir = TestUtils.getTestDirectory(); - - @Before - public void setUp() { - testDir.mkdirs(); - } - - @Test - public void testNonexistentDir() throws Exception { - if(!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) { - System.err.println("Warning: Skipping test"); - return; - } - File doesNotExist = new File(testDir, "doesNotExist"); - RemovableDriveMonitor monitor = createMonitor(doesNotExist); - monitor.start(new Callback() { - - public void driveInserted(File root) { - fail(); - } - - public void exceptionThrown(IOException e) { - fail(); - } - }); - monitor.stop(); - } - - @Test - public void testOneCallbackPerFile() throws Exception { - if(!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) { - System.err.println("Warning: Skipping test"); - return; - } - // Create a callback that will wait for two files before stopping - final List detected = new ArrayList(); - final CountDownLatch latch = new CountDownLatch(2); - final 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 - RemovableDriveMonitor monitor = createMonitor(testDir); - monitor.start(callback); - // Create two files in the test directory - File file1 = new File(testDir, "1"); - File file2 = new File(testDir, "2"); - assertTrue(file1.createNewFile()); - assertTrue(file2.createNewFile()); - // Wait for the monitor to detect the files - assertTrue(latch.await(5, TimeUnit.SECONDS)); - monitor.stop(); - // Check that both files were detected - assertEquals(2, detected.size()); - assertTrue(detected.contains(file1)); - assertTrue(detected.contains(file2)); - } - - @After - public void tearDown() { - TestUtils.deleteTestDirectory(testDir); - } - - private RemovableDriveMonitor createMonitor(final File dir) { - return new UnixRemovableDriveMonitor() { - @Override - protected String[] getPathsToWatch() { - return new String[] { dir.getPath() }; - } - }; - } -} diff --git a/test/net/sf/briar/plugins/socket/LanSocketClientTest.java b/test/net/sf/briar/plugins/socket/LanSocketClientTest.java deleted file mode 100644 index 8070b9191..000000000 --- a/test/net/sf/briar/plugins/socket/LanSocketClientTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.sf.briar.plugins.socket; - -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.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 LanSocketClientTest extends DuplexClientTest { - - private LanSocketClientTest(Executor executor, String serverAddress, - String serverPort) { - // Store the server's internal address and port - TransportProperties p = new TransportProperties(); - p.put("internal", serverAddress); - p.put("port", serverPort); - Map remote = - Collections.singletonMap(contactId, p); - // Create the plugin - callback = new ClientCallback(new TransportConfig(), - new TransportProperties(), remote); - plugin = new LanSocketPlugin(executor, callback, 0L); - } - - public static void main(String[] args) throws Exception { - if(args.length != 2) { - System.err.println("Please specify the server's address and port"); - System.exit(1); - } - ExecutorService executor = Executors.newCachedThreadPool(); - try { - new LanSocketClientTest(executor, args[0], args[1]).run(); - } finally { - executor.shutdown(); - } - } -} diff --git a/test/net/sf/briar/plugins/socket/LanSocketServerTest.java b/test/net/sf/briar/plugins/socket/LanSocketServerTest.java deleted file mode 100644 index 414d03116..000000000 --- a/test/net/sf/briar/plugins/socket/LanSocketServerTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.sf.briar.plugins.socket; - -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.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 LanSocketServerTest extends DuplexServerTest { - - private LanSocketServerTest(Executor executor) { - callback = new ServerCallback(new TransportConfig(), - new TransportProperties(), - Collections.singletonMap(contactId, new TransportProperties())); - plugin = new LanSocketPlugin(executor, callback, 0L); - } - - public static void main(String[] args) throws Exception { - ExecutorService executor = Executors.newCachedThreadPool(); - try { - new LanSocketServerTest(executor).run(); - } finally { - executor.shutdown(); - } - } -} diff --git a/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java b/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java deleted file mode 100644 index af06bcbea..000000000 --- a/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package net.sf.briar.plugins.socket; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.Hashtable; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -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.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; - -import org.junit.Test; - -public class SimpleSocketPluginTest extends BriarTestCase { - - private final ContactId contactId = new ContactId(234); - - @Test - public void testIncomingConnection() throws Exception { - Callback callback = new Callback(); - callback.local.put("internal", "127.0.0.1"); - callback.local.put("port", "0"); - Executor e = Executors.newCachedThreadPool(); - SimpleSocketPlugin plugin = new SimpleSocketPlugin(e, callback, 0L); - plugin.start(); - // The plugin should have bound a socket and stored the port number - assertTrue(callback.propertiesLatch.await(5, TimeUnit.SECONDS)); - String host = callback.local.get("internal"); - assertNotNull(host); - assertEquals("127.0.0.1", host); - String portString = callback.local.get("port"); - assertNotNull(portString); - int port = Integer.valueOf(portString); - assertTrue(port > 0 && port < 65536); - // The plugin should be listening on the port - InetSocketAddress addr = new InetSocketAddress(host, port); - Socket s = new Socket(); - s.connect(addr, 100); - assertTrue(callback.connectionsLatch.await(5, TimeUnit.SECONDS)); - s.close(); - // Stop the plugin - plugin.stop(); - } - - @Test - public void testOutgoingConnection() throws Exception { - Callback callback = new Callback(); - Executor e = Executors.newCachedThreadPool(); - SimpleSocketPlugin plugin = new SimpleSocketPlugin(e, callback, 0L); - plugin.start(); - // Listen on a local port - final ServerSocket ss = new ServerSocket(); - ss.bind(new InetSocketAddress("127.0.0.1", 0), 10); - int port = ss.getLocalPort(); - final CountDownLatch latch = new CountDownLatch(1); - final AtomicBoolean error = new AtomicBoolean(false); - new Thread() { - @Override - public void run() { - try { - ss.accept(); - latch.countDown(); - } catch(IOException e) { - error.set(true); - } - } - }.start(); - // Tell the plugin about the port - TransportProperties p = new TransportProperties(); - p.put("internal", "127.0.0.1"); - p.put("port", String.valueOf(port)); - callback.remote.put(contactId, p); - // Connect to the port - DuplexTransportConnection d = plugin.createConnection(contactId); - assertNotNull(d); - // Check that the connection was accepted - assertTrue(latch.await(5, TimeUnit.SECONDS)); - assertFalse(error.get()); - // Clean up - d.dispose(false, true); - ss.close(); - plugin.stop(); - } - - private static class Callback implements DuplexPluginCallback { - - private final Map remote = - new Hashtable(); - private final CountDownLatch propertiesLatch = new CountDownLatch(1); - private final CountDownLatch connectionsLatch = new CountDownLatch(1); - - private volatile TransportProperties local = new TransportProperties(); - - public TransportConfig getConfig() { - return new TransportConfig(); - } - - public TransportProperties getLocalProperties() { - return local; - } - - public Map getRemoteProperties() { - return remote; - } - - public void setConfig(TransportConfig c) {} - - public void setLocalProperties(TransportProperties p) { - local = p; - propertiesLatch.countDown(); - } - - 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) { - connectionsLatch.countDown(); - } - - public void outgoingConnectionCreated(ContactId c, - DuplexTransportConnection d) {} - } -} diff --git a/test/net/sf/briar/plugins/tor/TorPluginTest.java b/test/net/sf/briar/plugins/tor/TorPluginTest.java deleted file mode 100644 index 76d691fb6..000000000 --- a/test/net/sf/briar/plugins/tor/TorPluginTest.java +++ /dev/null @@ -1,180 +0,0 @@ -package net.sf.briar.plugins.tor; - -import java.io.PrintStream; -import java.util.Hashtable; -import java.util.Map; -import java.util.Scanner; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import junit.framework.AssertionFailedError; - -import net.sf.briar.BriarTestCase; -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; - -import org.junit.Test; - -public class TorPluginTest extends BriarTestCase { - - private final ContactId contactId = new ContactId(234); - - @Test - public void testHiddenService() throws Exception { - System.err.println("======== testHiddenService ========"); - Executor e = Executors.newCachedThreadPool(); - TorPlugin serverPlugin = null, clientPlugin = null; - try { - // Create a plugin instance for the server - Callback serverCallback = new Callback(); - serverPlugin = new TorPlugin(e, serverCallback, 0L); - System.out.println("Starting server plugin"); - serverPlugin.start(); - // The plugin should create a hidden service... eventually - assertTrue(serverCallback.latch.await(5, TimeUnit.MINUTES)); - System.out.println("Started server plugin"); - String onion = serverCallback.local.get("onion"); - assertNotNull(onion); - assertTrue(onion.endsWith(".onion")); - // Create another plugin instance for the client - Callback clientCallback = new Callback(); - clientCallback.config.put("noHiddenService", ""); - TransportProperties p = new TransportProperties(); - p.put("onion", onion); - clientCallback.remote.put(contactId, p); - clientPlugin = new TorPlugin(e, clientCallback, 0L); - System.out.println("Starting client plugin"); - clientPlugin.start(); - // The plugin should start without creating a hidden service - assertTrue(clientCallback.latch.await(5, TimeUnit.MINUTES)); - System.out.println("Started client plugin"); - // Connect to the server's hidden service - System.out.println("Connecting to hidden service"); - DuplexTransportConnection clientEnd = - clientPlugin.createConnection(contactId); - assertNotNull(clientEnd); - DuplexTransportConnection serverEnd = serverCallback.incomingConnection; - assertNotNull(serverEnd); - System.out.println("Connected to hidden service"); - // Send some data through the Tor connection - PrintStream out = new PrintStream(clientEnd.getOutputStream()); - out.println("Hello world"); - out.flush(); - Scanner in = new Scanner(serverEnd.getInputStream()); - assertTrue(in.hasNextLine()); - assertEquals("Hello world", in.nextLine()); - serverEnd.dispose(false, false); - clientEnd.dispose(false, false); - } catch(AssertionFailedError e1) { - System.out.println(e); - } finally { - // Stop the plugins - System.out.println("Stopping plugins"); - if(serverPlugin != null) serverPlugin.stop(); - if(clientPlugin != null) clientPlugin.stop(); - System.out.println("Stopped plugins"); - } - } - - @Test - public void testStoreAndRetrievePrivateKey() throws Exception { - System.err.println("======== testStoreAndRetrievePrivateKey ========"); - Executor e = Executors.newCachedThreadPool(); - TorPlugin plugin = null; - try { - // Start a plugin instance with no private key - Callback callback = new Callback(); - plugin = new TorPlugin(e, callback, 0L); - System.out.println("Starting plugin without private key"); - plugin.start(); - // The plugin should create a hidden service... eventually - assertTrue(callback.latch.await(5, TimeUnit.MINUTES)); - System.out.println("Started plugin"); - String onion = callback.local.get("onion"); - assertNotNull(onion); - assertTrue(onion.endsWith(".onion")); - // Get the PEM-encoded private key - String privateKey = callback.config.get("privateKey"); - assertNotNull(privateKey); - // Stop the plugin - System.out.println("Stopping plugin"); - plugin.stop(); - System.out.println("Stopped plugin"); - // Start another instance, reusing the private key - callback = new Callback(); - callback.config.put("privateKey", privateKey); - plugin = new TorPlugin(e, callback, 0L); - System.out.println("Starting plugin with private key"); - plugin.start(); - // The plugin should create a hidden service... eventually - assertTrue(callback.latch.await(5, TimeUnit.MINUTES)); - System.out.println("Started plugin"); - // The onion URL should be the same - assertEquals(onion, callback.local.get("onion")); - // The private key should be the same - assertEquals(privateKey, callback.config.get("privateKey")); - } catch(AssertionFailedError e1) { - System.out.println(e); - } finally { - // Stop the plugin - System.out.println("Stopping plugin"); - if(plugin != null) plugin.stop(); - System.out.println("Stopped plugin"); - } - } - - private static class Callback implements DuplexPluginCallback { - - private final Map remote = - new Hashtable(); - private final CountDownLatch latch = new CountDownLatch(1); - - private TransportConfig config = new TransportConfig(); - private TransportProperties local = new TransportProperties(); - - private volatile DuplexTransportConnection incomingConnection = null; - - public TransportConfig getConfig() { - return config; - } - - public TransportProperties getLocalProperties() { - return local; - } - - public Map getRemoteProperties() { - return remote; - } - - public void setConfig(TransportConfig c) { - config = c; - } - - public void setLocalProperties(TransportProperties p) { - local = p; - latch.countDown(); - } - - 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) { - incomingConnection = d; - } - - public void outgoingConnectionCreated(ContactId c, - DuplexTransportConnection d) {} - } -} diff --git a/test/net/sf/briar/protocol/AckReaderTest.java b/test/net/sf/briar/protocol/AckReaderTest.java deleted file mode 100644 index a40a1f297..000000000 --- a/test/net/sf/briar/protocol/AckReaderTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.sf.briar.protocol; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.Collection; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.PacketFactory; -import net.sf.briar.api.protocol.Types; -import net.sf.briar.api.serial.Reader; -import net.sf.briar.api.serial.ReaderFactory; -import net.sf.briar.api.serial.SerialComponent; -import net.sf.briar.api.serial.Writer; -import net.sf.briar.api.serial.WriterFactory; -import net.sf.briar.serial.SerialModule; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class AckReaderTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final SerialComponent serial; - private final ReaderFactory readerFactory; - private final WriterFactory writerFactory; - private final Mockery context; - - public AckReaderTest() throws Exception { - super(); - Injector i = Guice.createInjector(new SerialModule()); - serial = i.getInstance(SerialComponent.class); - readerFactory = i.getInstance(ReaderFactory.class); - writerFactory = i.getInstance(WriterFactory.class); - context = new Mockery(); - } - - @Test - public void testFormatExceptionIfAckIsTooLarge() throws Exception { - PacketFactory packetFactory = context.mock(PacketFactory.class); - AckReader ackReader = new AckReader(packetFactory); - - byte[] b = createAck(true); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.ACK, ackReader); - - try { - reader.readStruct(Types.ACK, Ack.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - @Test - @SuppressWarnings("unchecked") - public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - AckReader ackReader = new AckReader(packetFactory); - final Ack ack = context.mock(Ack.class); - context.checking(new Expectations() {{ - oneOf(packetFactory).createAck(with(any(Collection.class))); - will(returnValue(ack)); - }}); - - byte[] b = createAck(false); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.ACK, ackReader); - - assertEquals(ack, reader.readStruct(Types.ACK, Ack.class)); - context.assertIsSatisfied(); - } - - @Test - public void testEmptyAck() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - AckReader ackReader = new AckReader(packetFactory); - - byte[] b = createEmptyAck(); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.ACK, ackReader); - - try { - reader.readStruct(Types.ACK, Ack.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - private byte[] createAck(boolean tooBig) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.ACK); - w.writeListStart(); - while(out.size() + serial.getSerialisedUniqueIdLength() - < MAX_PACKET_LENGTH) { - w.writeBytes(TestUtils.getRandomId()); - } - if(tooBig) w.writeBytes(TestUtils.getRandomId()); - w.writeListEnd(); - assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH); - return out.toByteArray(); - } - - private byte[] createEmptyAck() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.ACK); - w.writeListStart(); - w.writeListEnd(); - return out.toByteArray(); - } -} diff --git a/test/net/sf/briar/protocol/BatchReaderTest.java b/test/net/sf/briar/protocol/BatchReaderTest.java deleted file mode 100644 index 6323638b0..000000000 --- a/test/net/sf/briar/protocol/BatchReaderTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package net.sf.briar.protocol; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Collections; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.Types; -import net.sf.briar.api.protocol.UnverifiedBatch; -import net.sf.briar.api.serial.Reader; -import net.sf.briar.api.serial.ReaderFactory; -import net.sf.briar.api.serial.StructReader; -import net.sf.briar.api.serial.Writer; -import net.sf.briar.api.serial.WriterFactory; -import net.sf.briar.serial.SerialModule; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class BatchReaderTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final ReaderFactory readerFactory; - private final WriterFactory writerFactory; - private final Mockery context; - private final UnverifiedMessage message; - private final StructReader messageReader; - - public BatchReaderTest() throws Exception { - super(); - Injector i = Guice.createInjector(new SerialModule()); - readerFactory = i.getInstance(ReaderFactory.class); - writerFactory = i.getInstance(WriterFactory.class); - context = new Mockery(); - message = context.mock(UnverifiedMessage.class); - messageReader = new TestMessageReader(); - } - - @Test - public void testFormatExceptionIfBatchIsTooLarge() throws Exception { - UnverifiedBatchFactory batchFactory = - context.mock(UnverifiedBatchFactory.class); - BatchReader batchReader = new BatchReader(messageReader, batchFactory); - - byte[] b = createBatch(MAX_PACKET_LENGTH + 1); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.BATCH, batchReader); - - try { - reader.readStruct(Types.BATCH, UnverifiedBatch.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - @Test - public void testNoFormatExceptionIfBatchIsMaximumSize() throws Exception { - final UnverifiedBatchFactory batchFactory = - context.mock(UnverifiedBatchFactory.class); - BatchReader batchReader = new BatchReader(messageReader, batchFactory); - final UnverifiedBatch batch = context.mock(UnverifiedBatch.class); - context.checking(new Expectations() {{ - oneOf(batchFactory).createUnverifiedBatch( - Collections.singletonList(message)); - will(returnValue(batch)); - }}); - - byte[] b = createBatch(MAX_PACKET_LENGTH); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.BATCH, batchReader); - - assertEquals(batch, reader.readStruct(Types.BATCH, - UnverifiedBatch.class)); - context.assertIsSatisfied(); - } - - @Test - public void testEmptyBatch() throws Exception { - final UnverifiedBatchFactory batchFactory = - context.mock(UnverifiedBatchFactory.class); - BatchReader batchReader = new BatchReader(messageReader, batchFactory); - - byte[] b = createEmptyBatch(); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.BATCH, batchReader); - - try { - reader.readStruct(Types.BATCH, UnverifiedBatch.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - private byte[] createBatch(int size) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(size); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.BATCH); - w.writeListStart(); - // We're using a fake message reader, so it's OK to use a fake message - w.writeStructId(Types.MESSAGE); - w.writeBytes(new byte[size - 10]); - w.writeListEnd(); - byte[] b = out.toByteArray(); - assertEquals(size, b.length); - return b; - } - - private byte[] createEmptyBatch() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.BATCH); - w.writeListStart(); - w.writeListEnd(); - return out.toByteArray(); - } - - private class TestMessageReader implements StructReader { - - public UnverifiedMessage readStruct(Reader r) throws IOException { - r.readStructId(Types.MESSAGE); - r.readBytes(); - return message; - } - } -} diff --git a/test/net/sf/briar/protocol/ConstantsTest.java b/test/net/sf/briar/protocol/ConstantsTest.java deleted file mode 100644 index e4c6af3a1..000000000 --- a/test/net/sf/briar/protocol/ConstantsTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package net.sf.briar.protocol; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_AUTHOR_NAME_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_BODY_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_GROUP_NAME_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PUBLIC_KEY_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_SUBJECT_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_TRANSPORTS; - -import java.io.ByteArrayOutputStream; -import java.security.PrivateKey; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -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.BatchId; -import net.sf.briar.api.protocol.Group; -import net.sf.briar.api.protocol.GroupFactory; -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.ProtocolWriter; -import net.sf.briar.api.protocol.ProtocolWriterFactory; -import net.sf.briar.api.protocol.RawBatch; -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.protocol.UniqueId; -import net.sf.briar.crypto.CryptoModule; -import net.sf.briar.serial.SerialModule; - -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class ConstantsTest extends BriarTestCase { - - private final CryptoComponent crypto; - private final GroupFactory groupFactory; - private final AuthorFactory authorFactory; - private final MessageFactory messageFactory; - private final PacketFactory packetFactory; - private final ProtocolWriterFactory protocolWriterFactory; - - public ConstantsTest() throws Exception { - super(); - Injector i = Guice.createInjector(new CryptoModule(), - new ProtocolModule(), new SerialModule()); - crypto = i.getInstance(CryptoComponent.class); - groupFactory = i.getInstance(GroupFactory.class); - authorFactory = i.getInstance(AuthorFactory.class); - messageFactory = i.getInstance(MessageFactory.class); - packetFactory = i.getInstance(PacketFactory.class); - protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class); - } - - @Test - public void testBatchesFitIntoLargeAck() throws Exception { - testBatchesFitIntoAck(MAX_PACKET_LENGTH); - } - - @Test - public void testBatchesFitIntoSmallAck() throws Exception { - testBatchesFitIntoAck(1000); - } - - private void testBatchesFitIntoAck(int length) throws Exception { - // Create an ack with as many batch IDs as possible - ByteArrayOutputStream out = new ByteArrayOutputStream(length); - ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out, - true); - int maxBatches = writer.getMaxBatchesForAck(length); - Collection acked = new ArrayList(); - for(int i = 0; i < maxBatches; i++) { - acked.add(new BatchId(TestUtils.getRandomId())); - } - Ack a = packetFactory.createAck(acked); - writer.writeAck(a); - // Check the size of the serialised ack - assertTrue(out.size() <= length); - } - - @Test - public void testMessageFitsIntoBatch() throws Exception { - // Create a maximum-length group - String groupName = createRandomString(MAX_GROUP_NAME_LENGTH); - byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH]; - Group group = groupFactory.createGroup(groupName, groupPublic); - // Create a maximum-length author - String authorName = createRandomString(MAX_AUTHOR_NAME_LENGTH); - byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH]; - Author author = authorFactory.createAuthor(authorName, authorPublic); - // Create a maximum-length message - PrivateKey groupPrivate = crypto.generateSignatureKeyPair().getPrivate(); - PrivateKey authorPrivate = crypto.generateSignatureKeyPair().getPrivate(); - String subject = createRandomString(MAX_SUBJECT_LENGTH); - byte[] body = new byte[MAX_BODY_LENGTH]; - Message message = messageFactory.createMessage(null, group, - groupPrivate, author, authorPrivate, subject, body); - // Add the message to a batch - ByteArrayOutputStream out = - new ByteArrayOutputStream(MAX_PACKET_LENGTH); - ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out, - true); - RawBatch b = packetFactory.createBatch(Collections.singletonList( - message.getSerialised())); - writer.writeBatch(b); - // Check the size of the serialised batch - assertTrue(out.size() > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH - + MAX_PUBLIC_KEY_LENGTH + MAX_AUTHOR_NAME_LENGTH - + MAX_PUBLIC_KEY_LENGTH + MAX_BODY_LENGTH); - assertTrue(out.size() <= MAX_PACKET_LENGTH); - } - - @Test - public void testMessagesFitIntoLargeOffer() throws Exception { - testMessagesFitIntoOffer(MAX_PACKET_LENGTH); - } - - @Test - public void testMessagesFitIntoSmallOffer() throws Exception { - testMessagesFitIntoOffer(1000); - } - - private void testMessagesFitIntoOffer(int length) throws Exception { - // Create an offer with as many message IDs as possible - ByteArrayOutputStream out = new ByteArrayOutputStream(length); - ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out, - true); - int maxMessages = writer.getMaxMessagesForOffer(length); - Collection offered = new ArrayList(); - for(int i = 0; i < maxMessages; i++) { - offered.add(new MessageId(TestUtils.getRandomId())); - } - Offer o = packetFactory.createOffer(offered); - writer.writeOffer(o); - // Check the size of the serialised offer - assertTrue(out.size() <= length); - } - - @Test - public void testTransportsFitIntoUpdate() throws Exception { - // Create the maximum number of plugins, each with the maximum number - // of maximum-length properties - Collection transports = new ArrayList(); - for(int i = 0; i < MAX_TRANSPORTS; i++) { - TransportId id = new TransportId(TestUtils.getRandomId()); - Transport t = new Transport(id); - for(int j = 0; j < MAX_PROPERTIES_PER_TRANSPORT; j++) { - String key = createRandomString(MAX_PROPERTY_LENGTH); - String value = createRandomString(MAX_PROPERTY_LENGTH); - t.put(key, value); - } - transports.add(t); - } - // Add the transports to an update - ByteArrayOutputStream out = - new ByteArrayOutputStream(MAX_PACKET_LENGTH); - ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out, - true); - TransportUpdate t = packetFactory.createTransportUpdate(transports, - Long.MAX_VALUE); - writer.writeTransportUpdate(t); - // Check the size of the serialised update - assertTrue(out.size() > MAX_TRANSPORTS * (UniqueId.LENGTH + 4 - + (MAX_PROPERTIES_PER_TRANSPORT * MAX_PROPERTY_LENGTH * 2)) - + 8); - assertTrue(out.size() <= MAX_PACKET_LENGTH); - } - - private static String createRandomString(int length) throws Exception { - StringBuilder s = new StringBuilder(length); - for(int i = 0; i < length; i++) { - int digit = (int) (Math.random() * 10); - s.append((char) ('0' + digit)); - } - String string = s.toString(); - assertEquals(length, string.getBytes("UTF-8").length); - return string; - } -} diff --git a/test/net/sf/briar/protocol/ConsumersTest.java b/test/net/sf/briar/protocol/ConsumersTest.java deleted file mode 100644 index 0bb89d6d2..000000000 --- a/test/net/sf/briar/protocol/ConsumersTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package net.sf.briar.protocol; - -import static org.junit.Assert.assertArrayEquals; - -import java.security.GeneralSecurityException; -import java.util.Random; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.crypto.MessageDigest; -import net.sf.briar.api.serial.CopyingConsumer; -import net.sf.briar.api.serial.CountingConsumer; -import net.sf.briar.api.serial.DigestingConsumer; - -import org.junit.Test; - -public class ConsumersTest extends BriarTestCase { - - @Test - public void testDigestingConsumer() throws Exception { - byte[] data = new byte[1234]; - // Generate some random data and digest it - new Random().nextBytes(data); - MessageDigest messageDigest = new TestMessageDigest(); - messageDigest.update(data); - byte[] dig = messageDigest.digest(); - // Check that feeding a DigestingConsumer generates the same digest - DigestingConsumer dc = new DigestingConsumer(messageDigest); - dc.write(data[0]); - dc.write(data, 1, data.length - 2); - dc.write(data[data.length - 1]); - byte[] dig1 = messageDigest.digest(); - assertArrayEquals(dig, dig1); - } - - @Test - public void testCountingConsumer() throws Exception { - byte[] data = new byte[1234]; - CountingConsumer cc = new CountingConsumer(data.length); - cc.write(data[0]); - cc.write(data, 1, data.length - 2); - cc.write(data[data.length - 1]); - assertEquals(data.length, cc.getCount()); - try { - cc.write((byte) 0); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testCopyingConsumer() throws Exception { - byte[] data = new byte[1234]; - new Random().nextBytes(data); - // Check that a CopyingConsumer creates a faithful copy - CopyingConsumer cc = new CopyingConsumer(); - cc.write(data[0]); - cc.write(data, 1, data.length - 2); - cc.write(data[data.length - 1]); - assertArrayEquals(data, cc.getCopy()); - } - - private static class TestMessageDigest implements MessageDigest { - - private final java.security.MessageDigest delegate; - - private TestMessageDigest() throws GeneralSecurityException { - delegate = java.security.MessageDigest.getInstance("SHA-256"); - } - - public byte[] digest() { - return delegate.digest(); - } - - public byte[] digest(byte[] input) { - return delegate.digest(input); - } - - public int digest(byte[] buf, int offset, int len) { - byte[] digest = digest(); - len = Math.min(len, digest.length); - System.arraycopy(digest, 0, buf, offset, len); - return len; - } - - public int getDigestLength() { - return delegate.getDigestLength(); - } - - public void reset() { - delegate.reset(); - } - - public void update(byte input) { - delegate.update(input); - } - - public void update(byte[] input) { - delegate.update(input); - } - - public void update(byte[] input, int offset, int len) { - delegate.update(input, offset, len); - } - } -} diff --git a/test/net/sf/briar/protocol/OfferReaderTest.java b/test/net/sf/briar/protocol/OfferReaderTest.java deleted file mode 100644 index d3ecbc0fb..000000000 --- a/test/net/sf/briar/protocol/OfferReaderTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.sf.briar.protocol; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.Collection; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; -import net.sf.briar.api.protocol.Types; -import net.sf.briar.api.serial.Reader; -import net.sf.briar.api.serial.ReaderFactory; -import net.sf.briar.api.serial.SerialComponent; -import net.sf.briar.api.serial.Writer; -import net.sf.briar.api.serial.WriterFactory; -import net.sf.briar.serial.SerialModule; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class OfferReaderTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final SerialComponent serial; - private final ReaderFactory readerFactory; - private final WriterFactory writerFactory; - private final Mockery context; - - public OfferReaderTest() throws Exception { - super(); - Injector i = Guice.createInjector(new SerialModule()); - serial = i.getInstance(SerialComponent.class); - readerFactory = i.getInstance(ReaderFactory.class); - writerFactory = i.getInstance(WriterFactory.class); - context = new Mockery(); - } - - @Test - public void testFormatExceptionIfOfferIsTooLarge() throws Exception { - PacketFactory packetFactory = context.mock(PacketFactory.class); - OfferReader offerReader = new OfferReader(packetFactory); - - byte[] b = createOffer(true); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.OFFER, offerReader); - - try { - reader.readStruct(Types.OFFER, Offer.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - @Test - @SuppressWarnings("unchecked") - public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - OfferReader offerReader = new OfferReader(packetFactory); - final Offer offer = context.mock(Offer.class); - context.checking(new Expectations() {{ - oneOf(packetFactory).createOffer(with(any(Collection.class))); - will(returnValue(offer)); - }}); - - byte[] b = createOffer(false); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.OFFER, offerReader); - - assertEquals(offer, reader.readStruct(Types.OFFER, Offer.class)); - context.assertIsSatisfied(); - } - - @Test - public void testEmptyOffer() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - OfferReader offerReader = new OfferReader(packetFactory); - - byte[] b = createEmptyOffer(); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.OFFER, offerReader); - - try { - reader.readStruct(Types.OFFER, Offer.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - private byte[] createOffer(boolean tooBig) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.OFFER); - w.writeListStart(); - while(out.size() + serial.getSerialisedUniqueIdLength() - < MAX_PACKET_LENGTH) { - w.writeBytes(TestUtils.getRandomId()); - } - if(tooBig) w.writeBytes(TestUtils.getRandomId()); - w.writeListEnd(); - assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH); - return out.toByteArray(); - } - - private byte[] createEmptyOffer() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.OFFER); - w.writeListStart(); - w.writeListEnd(); - return out.toByteArray(); - } -} diff --git a/test/net/sf/briar/protocol/ProtocolIntegrationTest.java b/test/net/sf/briar/protocol/ProtocolIntegrationTest.java deleted file mode 100644 index 4c939bc9c..000000000 --- a/test/net/sf/briar/protocol/ProtocolIntegrationTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package net.sf.briar.protocol; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.protocol.Ack; -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.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.crypto.CryptoModule; -import net.sf.briar.serial.SerialModule; - -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class ProtocolIntegrationTest extends BriarTestCase { - - private final ProtocolReaderFactory readerFactory; - private final ProtocolWriterFactory writerFactory; - private final PacketFactory packetFactory; - private final BatchId batchId; - private final Group group; - private final Message message; - private final String subject = "Hello"; - private final String messageBody = "Hello world"; - private final BitSet bitSet; - private final Map subscriptions; - private final Collection transports; - private final long timestamp = System.currentTimeMillis(); - - public ProtocolIntegrationTest() throws Exception { - super(); - Injector i = Guice.createInjector(new CryptoModule(), - new ProtocolModule(), new SerialModule()); - readerFactory = i.getInstance(ProtocolReaderFactory.class); - writerFactory = i.getInstance(ProtocolWriterFactory.class); - packetFactory = i.getInstance(PacketFactory.class); - batchId = new BatchId(TestUtils.getRandomId()); - GroupFactory groupFactory = i.getInstance(GroupFactory.class); - group = groupFactory.createGroup("Unrestricted group", null); - MessageFactory messageFactory = i.getInstance(MessageFactory.class); - message = messageFactory.createMessage(null, group, subject, - messageBody.getBytes("UTF-8")); - bitSet = new BitSet(); - bitSet.set(3); - bitSet.set(7); - subscriptions = Collections.singletonMap(group, 123L); - 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 { - // Write - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ProtocolWriter writer = writerFactory.createProtocolWriter(out, true); - - Ack a = packetFactory.createAck(Collections.singletonList(batchId)); - writer.writeAck(a); - - RawBatch b = packetFactory.createBatch(Collections.singletonList( - message.getSerialised())); - writer.writeBatch(b); - - Offer o = packetFactory.createOffer(Collections.singletonList( - message.getId())); - writer.writeOffer(o); - - Request r = packetFactory.createRequest(bitSet, 10); - writer.writeRequest(r); - - SubscriptionUpdate s = packetFactory.createSubscriptionUpdate( - Collections.emptyMap(), subscriptions, 0L, - timestamp); - writer.writeSubscriptionUpdate(s); - - TransportUpdate t = packetFactory.createTransportUpdate(transports, - timestamp); - writer.writeTransportUpdate(t); - - // Read - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - ProtocolReader reader = readerFactory.createProtocolReader(in); - - a = reader.readAck(); - assertEquals(Collections.singletonList(batchId), a.getBatchIds()); - - Batch b1 = reader.readBatch().verify(); - assertEquals(Collections.singletonList(message), b1.getMessages()); - - o = reader.readOffer(); - assertEquals(Collections.singletonList(message.getId()), - o.getMessageIds()); - - r = reader.readRequest(); - assertEquals(bitSet, r.getBitmap()); - assertEquals(10, r.getLength()); - - s = reader.readSubscriptionUpdate(); - assertEquals(subscriptions, s.getSubscriptions()); - assertEquals(timestamp, s.getTimestamp()); - - t = reader.readTransportUpdate(); - assertEquals(transports, t.getTransports()); - assertEquals(timestamp, t.getTimestamp()); - } -} diff --git a/test/net/sf/briar/protocol/ProtocolWriterImplTest.java b/test/net/sf/briar/protocol/ProtocolWriterImplTest.java deleted file mode 100644 index 4f2343b19..000000000 --- a/test/net/sf/briar/protocol/ProtocolWriterImplTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.sf.briar.protocol; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.BitSet; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.protocol.PacketFactory; -import net.sf.briar.api.protocol.ProtocolWriter; -import net.sf.briar.api.protocol.Request; -import net.sf.briar.api.serial.SerialComponent; -import net.sf.briar.api.serial.WriterFactory; -import net.sf.briar.crypto.CryptoModule; -import net.sf.briar.serial.SerialModule; -import net.sf.briar.util.StringUtils; - -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class ProtocolWriterImplTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final PacketFactory packetFactory; - private final SerialComponent serial; - private final WriterFactory writerFactory; - - public ProtocolWriterImplTest() { - super(); - Injector i = Guice.createInjector(new CryptoModule(), - new ProtocolModule(), new SerialModule()); - packetFactory = i.getInstance(PacketFactory.class); - serial = i.getInstance(SerialComponent.class); - writerFactory = i.getInstance(WriterFactory.class); - } - - @Test - public void testWriteBitmapNoPadding() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ProtocolWriter w = new ProtocolWriterImpl(serial, writerFactory, out, - true); - BitSet b = new BitSet(); - // 11011001 = 0xD9 - b.set(0); - b.set(1); - b.set(3); - b.set(4); - b.set(7); - // 01011001 = 0x59 - b.set(9); - b.set(11); - b.set(12); - b.set(15); - Request r = packetFactory.createRequest(b, 16); - w.writeRequest(r); - // Short user tag 6, 0 as uint7, short bytes with length 2, 0xD959 - byte[] output = out.toByteArray(); - assertEquals("C6" + "00" + "92" + "D959", - StringUtils.toHexString(output)); - } - - @Test - public void testWriteBitmapWithPadding() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ProtocolWriter w = new ProtocolWriterImpl(serial, writerFactory, out, - true); - BitSet b = new BitSet(); - // 01011001 = 0x59 - b.set(1); - b.set(3); - b.set(4); - b.set(7); - // 11011xxx = 0xD8, after padding - b.set(8); - b.set(9); - b.set(11); - b.set(12); - Request r = packetFactory.createRequest(b, 13); - w.writeRequest(r); - // Short user tag 6, 3 as uint7, short bytes with length 2, 0x59D8 - byte[] output = out.toByteArray(); - assertEquals("C6" + "03" + "92" + "59D8", - StringUtils.toHexString(output)); - } -} diff --git a/test/net/sf/briar/protocol/RequestReaderTest.java b/test/net/sf/briar/protocol/RequestReaderTest.java deleted file mode 100644 index 7dc377ecd..000000000 --- a/test/net/sf/briar/protocol/RequestReaderTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.sf.briar.protocol; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.BitSet; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.PacketFactory; -import net.sf.briar.api.protocol.Request; -import net.sf.briar.api.protocol.Types; -import net.sf.briar.api.serial.Reader; -import net.sf.briar.api.serial.ReaderFactory; -import net.sf.briar.api.serial.Writer; -import net.sf.briar.api.serial.WriterFactory; -import net.sf.briar.crypto.CryptoModule; -import net.sf.briar.serial.SerialModule; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class RequestReaderTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final ReaderFactory readerFactory; - private final WriterFactory writerFactory; - private final PacketFactory packetFactory; - private final Mockery context; - - public RequestReaderTest() throws Exception { - super(); - Injector i = Guice.createInjector(new CryptoModule(), - new ProtocolModule(), new SerialModule()); - readerFactory = i.getInstance(ReaderFactory.class); - writerFactory = i.getInstance(WriterFactory.class); - packetFactory = i.getInstance(PacketFactory.class); - context = new Mockery(); - } - - @Test - public void testFormatExceptionIfRequestIsTooLarge() throws Exception { - PacketFactory packetFactory = context.mock(PacketFactory.class); - RequestReader requestReader = new RequestReader(packetFactory); - - byte[] b = createRequest(true); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.REQUEST, requestReader); - - try { - reader.readStruct(Types.REQUEST, Request.class); - fail(); - } catch(FormatException expected) {} - context.assertIsSatisfied(); - } - - @Test - public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - RequestReader requestReader = new RequestReader(packetFactory); - final Request request = context.mock(Request.class); - context.checking(new Expectations() {{ - oneOf(packetFactory).createRequest(with(any(BitSet.class)), - with(any(int.class))); - will(returnValue(request)); - }}); - - byte[] b = createRequest(false); - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - reader.addStructReader(Types.REQUEST, requestReader); - - assertEquals(request, reader.readStruct(Types.REQUEST, - Request.class)); - context.assertIsSatisfied(); - } - - @Test - public void testBitmapDecoding() throws Exception { - // Test sizes from 0 to 1000 bits - for(int i = 0; i < 1000; i++) { - // Create a BitSet of size i with one in ten bits set (on average) - BitSet requested = new BitSet(i); - for(int j = 0; j < i; j++) if(Math.random() < 0.1) requested.set(j); - // Encode the BitSet as a bitmap - int bytes = i % 8 == 0 ? i / 8 : i / 8 + 1; - byte[] bitmap = new byte[bytes]; - for(int j = 0; j < i; j++) { - if(requested.get(j)) { - int offset = j / 8; - byte bit = (byte) (128 >> j % 8); - bitmap[offset] |= bit; - } - } - // Create a serialised request containing the bitmap - byte[] b = createRequest(bitmap); - // Deserialise the request - ByteArrayInputStream in = new ByteArrayInputStream(b); - Reader reader = readerFactory.createReader(in); - RequestReader requestReader = new RequestReader(packetFactory); - reader.addStructReader(Types.REQUEST, requestReader); - Request r = reader.readStruct(Types.REQUEST, Request.class); - BitSet decoded = r.getBitmap(); - // Check that the decoded BitSet matches the original - we can't - // use equals() because of padding, but the first i bits should - // match and the cardinalities should be equal, indicating that no - // padding bits are set - for(int j = 0; j < i; j++) { - assertEquals(requested.get(j), decoded.get(j)); - } - assertEquals(requested.cardinality(), decoded.cardinality()); - } - } - - private byte[] createRequest(boolean tooBig) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.REQUEST); - // Allow one byte for the REQUEST tag, one byte for the padding length - // as a uint7, one byte for the BYTES tag, and five bytes for the - // length of the byte array as an int32 - int size = MAX_PACKET_LENGTH - 8; - if(tooBig) size++; - assertTrue(size > Short.MAX_VALUE); - w.writeUint7((byte) 0); - w.writeBytes(new byte[size]); - assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH); - return out.toByteArray(); - } - - private byte[] createRequest(byte[] bitmap) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer w = writerFactory.createWriter(out); - w.writeStructId(Types.REQUEST); - w.writeUint7((byte) 0); - w.writeBytes(bitmap); - return out.toByteArray(); - } -} diff --git a/test/net/sf/briar/protocol/UnverifiedBatchImplTest.java b/test/net/sf/briar/protocol/UnverifiedBatchImplTest.java deleted file mode 100644 index 126f8a983..000000000 --- a/test/net/sf/briar/protocol/UnverifiedBatchImplTest.java +++ /dev/null @@ -1,244 +0,0 @@ -package net.sf.briar.protocol; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.Signature; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.Random; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.crypto.MessageDigest; -import net.sf.briar.api.protocol.Author; -import net.sf.briar.api.protocol.AuthorId; -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.GroupId; -import net.sf.briar.api.protocol.Message; -import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.UnverifiedBatch; -import net.sf.briar.crypto.CryptoModule; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class UnverifiedBatchImplTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final CryptoComponent crypto; - private final byte[] raw, raw1; - private final String subject; - private final long timestamp; - - public UnverifiedBatchImplTest() { - super(); - Injector i = Guice.createInjector(new CryptoModule()); - crypto = i.getInstance(CryptoComponent.class); - Random r = new Random(); - raw = new byte[123]; - r.nextBytes(raw); - raw1 = new byte[1234]; - r.nextBytes(raw1); - subject = "Unit tests are exciting"; - timestamp = System.currentTimeMillis(); - } - - @Test - public void testIds() throws Exception { - // Calculate the expected batch and message IDs - MessageDigest messageDigest = crypto.getMessageDigest(); - messageDigest.update(raw); - messageDigest.update(raw1); - BatchId batchId = new BatchId(messageDigest.digest()); - messageDigest.update(raw); - MessageId messageId = new MessageId(messageDigest.digest()); - messageDigest.update(raw1); - MessageId messageId1 = new MessageId(messageDigest.digest()); - // Verify the batch - Mockery context = new Mockery(); - final UnverifiedMessage message = - context.mock(UnverifiedMessage.class, "message"); - final UnverifiedMessage message1 = - context.mock(UnverifiedMessage.class, "message1"); - context.checking(new Expectations() {{ - // First message - oneOf(message).getRaw(); - will(returnValue(raw)); - oneOf(message).getAuthor(); - will(returnValue(null)); - oneOf(message).getGroup(); - will(returnValue(null)); - oneOf(message).getParent(); - will(returnValue(null)); - oneOf(message).getSubject(); - will(returnValue(subject)); - oneOf(message).getTimestamp(); - will(returnValue(timestamp)); - oneOf(message).getBodyStart(); - will(returnValue(10)); - oneOf(message).getBodyLength(); - will(returnValue(100)); - // Second message - oneOf(message1).getRaw(); - will(returnValue(raw1)); - oneOf(message1).getAuthor(); - will(returnValue(null)); - oneOf(message1).getGroup(); - will(returnValue(null)); - oneOf(message1).getParent(); - will(returnValue(null)); - oneOf(message1).getSubject(); - will(returnValue(subject)); - oneOf(message1).getTimestamp(); - will(returnValue(timestamp)); - oneOf(message1).getBodyStart(); - will(returnValue(10)); - oneOf(message1).getBodyLength(); - will(returnValue(1000)); - }}); - Collection messages = Arrays.asList(message, - message1); - UnverifiedBatch batch = new UnverifiedBatchImpl(crypto, messages); - Batch verifiedBatch = batch.verify(); - // Check that the batch and message IDs match - assertEquals(batchId, verifiedBatch.getId()); - Collection verifiedMessages = verifiedBatch.getMessages(); - assertEquals(2, verifiedMessages.size()); - Iterator it = verifiedMessages.iterator(); - Message verifiedMessage = it.next(); - assertEquals(messageId, verifiedMessage.getId()); - Message verifiedMessage1 = it.next(); - assertEquals(messageId1, verifiedMessage1.getId()); - context.assertIsSatisfied(); - } - - @Test - public void testSignatures() throws Exception { - final int signedByAuthor = 100, signedByGroup = 110; - final KeyPair authorKeyPair = crypto.generateSignatureKeyPair(); - final KeyPair groupKeyPair = crypto.generateSignatureKeyPair(); - Signature signature = crypto.getSignature(); - // Calculate the expected author and group signatures - signature.initSign(authorKeyPair.getPrivate()); - signature.update(raw, 0, signedByAuthor); - final byte[] authorSignature = signature.sign(); - signature.initSign(groupKeyPair.getPrivate()); - signature.update(raw, 0, signedByGroup); - final byte[] groupSignature = signature.sign(); - // Verify the batch - Mockery context = new Mockery(); - final UnverifiedMessage message = - context.mock(UnverifiedMessage.class, "message"); - final Author author = context.mock(Author.class); - final Group group = context.mock(Group.class); - final UnverifiedMessage message1 = - context.mock(UnverifiedMessage.class, "message1"); - context.checking(new Expectations() {{ - // First message - oneOf(message).getRaw(); - will(returnValue(raw)); - oneOf(message).getAuthor(); - will(returnValue(author)); - oneOf(author).getPublicKey(); - will(returnValue(authorKeyPair.getPublic().getEncoded())); - oneOf(message).getLengthSignedByAuthor(); - will(returnValue(signedByAuthor)); - oneOf(message).getAuthorSignature(); - will(returnValue(authorSignature)); - oneOf(message).getGroup(); - will(returnValue(group)); - exactly(2).of(group).getPublicKey(); - will(returnValue(groupKeyPair.getPublic().getEncoded())); - oneOf(message).getLengthSignedByGroup(); - will(returnValue(signedByGroup)); - oneOf(message).getGroupSignature(); - will(returnValue(groupSignature)); - oneOf(author).getId(); - will(returnValue(new AuthorId(TestUtils.getRandomId()))); - oneOf(group).getId(); - will(returnValue(new GroupId(TestUtils.getRandomId()))); - oneOf(message).getParent(); - will(returnValue(null)); - oneOf(message).getSubject(); - will(returnValue(subject)); - oneOf(message).getTimestamp(); - will(returnValue(timestamp)); - oneOf(message).getBodyStart(); - will(returnValue(10)); - oneOf(message).getBodyLength(); - will(returnValue(100)); - // Second message - oneOf(message1).getRaw(); - will(returnValue(raw1)); - oneOf(message1).getAuthor(); - will(returnValue(null)); - oneOf(message1).getGroup(); - will(returnValue(null)); - oneOf(message1).getParent(); - will(returnValue(null)); - oneOf(message1).getSubject(); - will(returnValue(subject)); - oneOf(message1).getTimestamp(); - will(returnValue(timestamp)); - oneOf(message1).getBodyStart(); - will(returnValue(10)); - oneOf(message1).getBodyLength(); - will(returnValue(1000)); - }}); - Collection messages = Arrays.asList(message, - message1); - UnverifiedBatch batch = new UnverifiedBatchImpl(crypto, messages); - batch.verify(); - context.assertIsSatisfied(); - } - - @Test - public void testExceptionThrownIfMessageIsModified() throws Exception { - final int signedByAuthor = 100; - final KeyPair authorKeyPair = crypto.generateSignatureKeyPair(); - Signature signature = crypto.getSignature(); - // Calculate the expected author signature - signature.initSign(authorKeyPair.getPrivate()); - signature.update(raw, 0, signedByAuthor); - final byte[] authorSignature = signature.sign(); - // Modify the message - raw[signedByAuthor / 2] ^= 0xff; - // Verify the batch - Mockery context = new Mockery(); - final UnverifiedMessage message = - context.mock(UnverifiedMessage.class, "message"); - final Author author = context.mock(Author.class); - final UnverifiedMessage message1 = - context.mock(UnverifiedMessage.class, "message1"); - context.checking(new Expectations() {{ - // First message - verification will fail at the author's signature - oneOf(message).getRaw(); - will(returnValue(raw)); - oneOf(message).getAuthor(); - will(returnValue(author)); - oneOf(author).getPublicKey(); - will(returnValue(authorKeyPair.getPublic().getEncoded())); - oneOf(message).getLengthSignedByAuthor(); - will(returnValue(signedByAuthor)); - oneOf(message).getAuthorSignature(); - will(returnValue(authorSignature)); - }}); - Collection messages = Arrays.asList(message, - message1); - UnverifiedBatch batch = new UnverifiedBatchImpl(crypto, messages); - try { - batch.verify(); - fail(); - } catch(GeneralSecurityException expected) {} - context.assertIsSatisfied(); - } -} diff --git a/test/net/sf/briar/protocol/simplex/OutgoingSimplexConnectionTest.java b/test/net/sf/briar/protocol/simplex/OutgoingSimplexConnectionTest.java deleted file mode 100644 index 62463294b..000000000 --- a/test/net/sf/briar/protocol/simplex/OutgoingSimplexConnectionTest.java +++ /dev/null @@ -1,176 +0,0 @@ -package net.sf.briar.protocol.simplex; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MIN_CONNECTION_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.ByteArrayOutputStream; -import java.util.Collections; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.ContactId; -import net.sf.briar.api.db.DatabaseComponent; -import net.sf.briar.api.db.DatabaseExecutor; -import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.BatchId; -import net.sf.briar.api.protocol.ProtocolWriterFactory; -import net.sf.briar.api.protocol.RawBatch; -import net.sf.briar.api.protocol.TransportId; -import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.transport.ConnectionContext; -import net.sf.briar.api.transport.ConnectionRegistry; -import net.sf.briar.api.transport.ConnectionWriterFactory; -import net.sf.briar.crypto.CryptoModule; -import net.sf.briar.protocol.ProtocolModule; -import net.sf.briar.protocol.duplex.DuplexProtocolModule; -import net.sf.briar.serial.SerialModule; -import net.sf.briar.transport.TransportModule; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; - -public class OutgoingSimplexConnectionTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final Mockery context; - private final DatabaseComponent db; - private final ConnectionRegistry connRegistry; - private final ConnectionWriterFactory connFactory; - private final ProtocolWriterFactory protoFactory; - private final ContactId contactId; - private final TransportId transportId; - private final byte[] secret; - - public OutgoingSimplexConnectionTest() { - super(); - context = new Mockery(); - db = context.mock(DatabaseComponent.class); - Module testModule = new AbstractModule() { - @Override - public void configure() { - bind(DatabaseComponent.class).toInstance(db); - bind(Executor.class).annotatedWith( - DatabaseExecutor.class).toInstance( - Executors.newCachedThreadPool()); - } - }; - Injector i = Guice.createInjector(testModule, new CryptoModule(), - new SerialModule(), new TransportModule(), - new SimplexProtocolModule(), new ProtocolModule(), - new DuplexProtocolModule()); - connRegistry = i.getInstance(ConnectionRegistry.class); - connFactory = i.getInstance(ConnectionWriterFactory.class); - protoFactory = i.getInstance(ProtocolWriterFactory.class); - contactId = new ContactId(234); - transportId = new TransportId(TestUtils.getRandomId()); - secret = new byte[32]; - } - - @Test - public void testConnectionTooShort() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TestSimplexTransportWriter transport = new TestSimplexTransportWriter( - out, MAX_PACKET_LENGTH, true); - ConnectionContext ctx = new ConnectionContext(contactId, transportId, - secret, 0L, true); - OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db, - connRegistry, connFactory, protoFactory, ctx, transport); - connection.write(); - // Nothing should have been written - assertEquals(0, out.size()); - // The transport should have been disposed with exception == true - assertTrue(transport.getDisposed()); - assertTrue(transport.getException()); - } - - @Test - public void testNothingToSend() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TestSimplexTransportWriter transport = new TestSimplexTransportWriter( - out, MIN_CONNECTION_LENGTH, true); - ConnectionContext ctx = new ConnectionContext(contactId, transportId, - secret, 0L, true); - OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db, - connRegistry, connFactory, protoFactory, ctx, transport); - context.checking(new Expectations() {{ - // No transports to send - oneOf(db).generateTransportUpdate(contactId); - will(returnValue(null)); - // No subscriptions to send - oneOf(db).generateSubscriptionUpdate(contactId); - will(returnValue(null)); - // No acks to send - oneOf(db).generateAck(with(contactId), with(any(int.class))); - will(returnValue(null)); - // No batches to send - oneOf(db).generateBatch(with(contactId), with(any(int.class))); - will(returnValue(null)); - }}); - connection.write(); - // Nothing should have been written - assertEquals(0, out.size()); - // The transport should have been disposed with exception == false - assertTrue(transport.getDisposed()); - assertFalse(transport.getException()); - context.assertIsSatisfied(); - } - - @Test - public void testSomethingToSend() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TestSimplexTransportWriter transport = new TestSimplexTransportWriter( - out, MIN_CONNECTION_LENGTH, true); - ConnectionContext ctx = new ConnectionContext(contactId, transportId, - secret, 0L, true); - OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db, - connRegistry, connFactory, protoFactory, ctx, transport); - final Ack ack = context.mock(Ack.class); - final BatchId batchId = new BatchId(TestUtils.getRandomId()); - final RawBatch batch = context.mock(RawBatch.class); - final byte[] message = new byte[1234]; - context.checking(new Expectations() {{ - // No transports to send - oneOf(db).generateTransportUpdate(contactId); - will(returnValue(null)); - // No subscriptions to send - oneOf(db).generateSubscriptionUpdate(contactId); - will(returnValue(null)); - // One ack to send - oneOf(db).generateAck(with(contactId), with(any(int.class))); - will(returnValue(ack)); - oneOf(ack).getBatchIds(); - will(returnValue(Collections.singletonList(batchId))); - // No more acks - oneOf(db).generateAck(with(contactId), with(any(int.class))); - will(returnValue(null)); - // One batch to send - oneOf(db).generateBatch(with(contactId), with(any(int.class))); - will(returnValue(batch)); - oneOf(batch).getMessages(); - will(returnValue(Collections.singletonList(message))); - // No more batches - oneOf(db).generateBatch(with(contactId), with(any(int.class))); - will(returnValue(null)); - }}); - connection.write(); - // Something should have been written - int overhead = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH; - assertTrue(out.size() > overhead + UniqueId.LENGTH + message.length); - // The transport should have been disposed with exception == false - assertTrue(transport.getDisposed()); - assertFalse(transport.getException()); - context.assertIsSatisfied(); - } -} diff --git a/test/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java b/test/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java deleted file mode 100644 index 8448a0934..000000000 --- a/test/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package net.sf.briar.protocol.simplex; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.util.Collection; -import java.util.Collections; -import java.util.Random; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestDatabaseModule; -import net.sf.briar.TestUtils; -import net.sf.briar.api.ContactId; -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.event.DatabaseEvent; -import net.sf.briar.api.db.event.DatabaseListener; -import net.sf.briar.api.db.event.MessagesAddedEvent; -import net.sf.briar.api.protocol.Message; -import net.sf.briar.api.protocol.MessageFactory; -import net.sf.briar.api.protocol.ProtocolReaderFactory; -import net.sf.briar.api.protocol.ProtocolWriterFactory; -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.ConnectionReaderFactory; -import net.sf.briar.api.transport.ConnectionRecogniser; -import net.sf.briar.api.transport.ConnectionRegistry; -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.plugins.ImmediateExecutor; -import net.sf.briar.protocol.ProtocolModule; -import net.sf.briar.protocol.duplex.DuplexProtocolModule; -import net.sf.briar.serial.SerialModule; -import net.sf.briar.transport.TransportModule; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class SimplexProtocolIntegrationTest extends BriarTestCase { - - private static final long CLOCK_DIFFERENCE = 60 * 1000L; - private static final long LATENCY = 60 * 1000L; - - private final File testDir = TestUtils.getTestDirectory(); - private final File aliceDir = new File(testDir, "alice"); - private final File bobDir = new File(testDir, "bob"); - private final TransportId transportId; - private final byte[] initialSecret; - private final long epoch; - - private Injector alice, bob; - - public SimplexProtocolIntegrationTest() throws Exception { - super(); - transportId = new TransportId(TestUtils.getRandomId()); - // Create matching secrets for Alice and Bob - initialSecret = new byte[32]; - new Random().nextBytes(initialSecret); - long rotationPeriod = 2 * CLOCK_DIFFERENCE + LATENCY; - epoch = System.currentTimeMillis() - 2 * rotationPeriod; - } - - @Before - public void setUp() { - testDir.mkdirs(); - alice = createInjector(aliceDir); - bob = createInjector(bobDir); - } - - private Injector createInjector(File dir) { - return Guice.createInjector(new ClockModule(), new CryptoModule(), - new DatabaseModule(), new LifecycleModule(), - new ProtocolModule(), new SerialModule(), - new TestDatabaseModule(dir), new SimplexProtocolModule(), - new TransportModule(), new DuplexProtocolModule()); - } - - @Test - public void testInjection() { - DatabaseComponent aliceDb = alice.getInstance(DatabaseComponent.class); - DatabaseComponent bobDb = bob.getInstance(DatabaseComponent.class); - assertFalse(aliceDb == bobDb); - } - - @Test - public void testWriteAndRead() throws Exception { - read(write()); - } - - private byte[] write() throws Exception { - // Open Alice's database - DatabaseComponent db = alice.getInstance(DatabaseComponent.class); - db.open(false); - // Start Alice's key manager - KeyManager km = alice.getInstance(KeyManager.class); - km.start(); - // Add Bob as a contact - ContactId contactId = db.addContact(); - ContactTransport ct = new ContactTransport(contactId, transportId, - epoch, CLOCK_DIFFERENCE, LATENCY, true); - db.addContactTransport(ct); - km.contactTransportAdded(ct, initialSecret.clone()); - // Send Bob a message - String subject = "Hello"; - byte[] body = "Hi Bob!".getBytes("UTF-8"); - MessageFactory messageFactory = alice.getInstance(MessageFactory.class); - Message message = messageFactory.createMessage(null, subject, body); - db.addLocalPrivateMessage(message, contactId); - // Create an outgoing simplex connection - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ConnectionRegistry connRegistry = - alice.getInstance(ConnectionRegistry.class); - ConnectionWriterFactory connFactory = - alice.getInstance(ConnectionWriterFactory.class); - ProtocolWriterFactory protoFactory = - alice.getInstance(ProtocolWriterFactory.class); - TestSimplexTransportWriter transport = new TestSimplexTransportWriter( - out, Long.MAX_VALUE, false); - ConnectionContext ctx = km.getConnectionContext(contactId, transportId); - assertNotNull(ctx); - OutgoingSimplexConnection simplex = new OutgoingSimplexConnection(db, - connRegistry, connFactory, protoFactory, ctx, transport); - // Write whatever needs to be written - simplex.write(); - assertTrue(transport.getDisposed()); - assertFalse(transport.getException()); - // Clean up - km.stop(); - db.close(); - // Return the contents of the simplex connection - return out.toByteArray(); - } - - private void read(byte[] b) throws Exception { - // Open Bob's database - DatabaseComponent db = bob.getInstance(DatabaseComponent.class); - db.open(false); - // Start Bob's key manager - KeyManager km = bob.getInstance(KeyManager.class); - km.start(); - // Add Alice as a contact - ContactId contactId = db.addContact(); - ContactTransport ct = new ContactTransport(contactId, transportId, - epoch, CLOCK_DIFFERENCE, LATENCY, false); - db.addContactTransport(ct); - km.contactTransportAdded(ct, initialSecret.clone()); - // Set up a database listener - MessageListener listener = new MessageListener(); - db.addListener(listener); - // Fake a transport update from Alice - TransportUpdate transportUpdate = new TransportUpdate() { - - public Collection getTransports() { - Transport t = new Transport(transportId); - return Collections.singletonList(t); - } - - public long getTimestamp() { - return System.currentTimeMillis(); - } - }; - db.receiveTransportUpdate(contactId, transportUpdate); - // Create a connection recogniser and recognise the connection - ByteArrayInputStream in = new ByteArrayInputStream(b); - ConnectionRecogniser rec = bob.getInstance(ConnectionRecogniser.class); - byte[] tag = new byte[TAG_LENGTH]; - int read = in.read(tag); - assertEquals(tag.length, read); - ConnectionContext ctx = rec.acceptConnection(transportId, tag); - assertNotNull(ctx); - // Create an incoming simplex connection - ConnectionRegistry connRegistry = - bob.getInstance(ConnectionRegistry.class); - ConnectionReaderFactory connFactory = - bob.getInstance(ConnectionReaderFactory.class); - ProtocolReaderFactory protoFactory = - bob.getInstance(ProtocolReaderFactory.class); - TestSimplexTransportReader transport = - new TestSimplexTransportReader(in); - IncomingSimplexConnection simplex = new IncomingSimplexConnection( - new ImmediateExecutor(), new ImmediateExecutor(), db, - connRegistry, connFactory, protoFactory, ctx, transport); - // No messages should have been added yet - assertFalse(listener.messagesAdded); - // Read whatever needs to be read - simplex.read(); - assertTrue(transport.getDisposed()); - assertFalse(transport.getException()); - assertTrue(transport.getRecognised()); - // The private message from Alice should have been added - assertTrue(listener.messagesAdded); - // Clean up - km.stop(); - db.close(); - } - - @After - public void tearDown() { - TestUtils.deleteTestDirectory(testDir); - } - - private static class MessageListener implements DatabaseListener { - - private boolean messagesAdded = false; - - public void eventOccurred(DatabaseEvent e) { - if(e instanceof MessagesAddedEvent) - messagesAdded = true; - } - } -} diff --git a/test/net/sf/briar/protocol/simplex/TestSimplexTransportReader.java b/test/net/sf/briar/protocol/simplex/TestSimplexTransportReader.java deleted file mode 100644 index 1d85ed3ec..000000000 --- a/test/net/sf/briar/protocol/simplex/TestSimplexTransportReader.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.sf.briar.protocol.simplex; - -import java.io.InputStream; - -import net.sf.briar.api.plugins.simplex.SimplexTransportReader; - -class TestSimplexTransportReader implements SimplexTransportReader { - - private final InputStream in; - - private boolean disposed = false, exception = false, recognised = false; - - TestSimplexTransportReader(InputStream in) { - this.in = in; - } - - public InputStream getInputStream() { - return in; - } - - public void dispose(boolean exception, boolean recognised) { - assert !disposed; - disposed = true; - this.exception = exception; - this.recognised = recognised; - } - - boolean getDisposed() { - return disposed; - } - - boolean getException() { - return exception; - } - - boolean getRecognised() { - return recognised; - } -} \ No newline at end of file diff --git a/test/net/sf/briar/protocol/simplex/TestSimplexTransportWriter.java b/test/net/sf/briar/protocol/simplex/TestSimplexTransportWriter.java deleted file mode 100644 index 7a1524cb5..000000000 --- a/test/net/sf/briar/protocol/simplex/TestSimplexTransportWriter.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.sf.briar.protocol.simplex; - -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; - -import net.sf.briar.api.plugins.simplex.SimplexTransportWriter; - -class TestSimplexTransportWriter implements SimplexTransportWriter { - - private final ByteArrayOutputStream out; - private final long capacity; - private final boolean flush; - - private boolean disposed = false, exception = false; - - TestSimplexTransportWriter(ByteArrayOutputStream out, long capacity, - boolean flush) { - this.out = out; - this.capacity = capacity; - this.flush = flush; - } - - public long getCapacity() { - return capacity; - } - - public OutputStream getOutputStream() { - return out; - } - - public boolean shouldFlush() { - return flush; - } - - public void dispose(boolean exception) { - assert !disposed; - disposed = true; - this.exception = exception; - } - - boolean getDisposed() { - return disposed; - } - - boolean getException() { - return exception; - } -} \ No newline at end of file diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java deleted file mode 100644 index a79cdb56b..000000000 --- a/test/net/sf/briar/serial/ReaderImplTest.java +++ /dev/null @@ -1,556 +0,0 @@ -package net.sf.briar.serial; - -import static org.junit.Assert.assertArrayEquals; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.Bytes; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.serial.Consumer; -import net.sf.briar.api.serial.StructReader; -import net.sf.briar.api.serial.Reader; -import net.sf.briar.util.StringUtils; - -import org.junit.Test; - -public class ReaderImplTest extends BriarTestCase { - - private ByteArrayInputStream in = null; - private ReaderImpl r = null; - - @Test - public void testReadBoolean() throws Exception { - setContents("FFFE"); - assertFalse(r.readBoolean()); - assertTrue(r.readBoolean()); - assertTrue(r.eof()); - } - - @Test - public void testReadInt8() throws Exception { - setContents("FD00" + "FDFF" + "FD7F" + "FD80"); - assertEquals((byte) 0, r.readInt8()); - assertEquals((byte) -1, r.readInt8()); - assertEquals(Byte.MAX_VALUE, r.readInt8()); - assertEquals(Byte.MIN_VALUE, r.readInt8()); - assertTrue(r.eof()); - } - - @Test - public void testReadInt16() throws Exception { - setContents("FC0000" + "FCFFFF" + "FC7FFF" + "FC8000"); - assertEquals((short) 0, r.readInt16()); - assertEquals((short) -1, r.readInt16()); - assertEquals(Short.MAX_VALUE, r.readInt16()); - assertEquals(Short.MIN_VALUE, r.readInt16()); - assertTrue(r.eof()); - } - - @Test - public void testReadInt32() throws Exception { - setContents("FB00000000" + "FBFFFFFFFF" + "FB7FFFFFFF" + "FB80000000"); - assertEquals(0, r.readInt32()); - assertEquals(-1, r.readInt32()); - assertEquals(Integer.MAX_VALUE, r.readInt32()); - assertEquals(Integer.MIN_VALUE, r.readInt32()); - assertTrue(r.eof()); - } - - @Test - public void testReadInt64() throws Exception { - setContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF" - + "FA7FFFFFFFFFFFFFFF" + "FA8000000000000000"); - assertEquals(0L, r.readInt64()); - assertEquals(-1L, r.readInt64()); - assertEquals(Long.MAX_VALUE, r.readInt64()); - assertEquals(Long.MIN_VALUE, r.readInt64()); - assertTrue(r.eof()); - } - - @Test - public void testReadIntAny() throws Exception { - setContents("00" + "7F" + "FD80" + "FDFF" + "FC0080" + "FC7FFF" - + "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000"); - assertEquals(0L, r.readIntAny()); - assertEquals(127L, r.readIntAny()); - assertEquals(-128L, r.readIntAny()); - assertEquals(-1L, r.readIntAny()); - assertEquals(128L, r.readIntAny()); - assertEquals(32767L, r.readIntAny()); - assertEquals(32768L, r.readIntAny()); - assertEquals(2147483647L, r.readIntAny()); - assertEquals(2147483648L, r.readIntAny()); - assertTrue(r.eof()); - } - - @Test - public void testReadFloat32() throws Exception { - // http://babbage.cs.qc.edu/IEEE-754/Decimal.html - // http://steve.hollasch.net/cgindex/coding/ieeefloat.html - setContents("F900000000" + "F93F800000" + "F940000000" + "F9BF800000" - + "F980000000" + "F9FF800000" + "F97F800000" + "F97FC00000"); - assertEquals(0F, r.readFloat32()); - assertEquals(1F, r.readFloat32()); - assertEquals(2F, r.readFloat32()); - assertEquals(-1F, r.readFloat32()); - assertEquals(-0F, r.readFloat32()); - assertEquals(Float.NEGATIVE_INFINITY, r.readFloat32()); - assertEquals(Float.POSITIVE_INFINITY, r.readFloat32()); - assertTrue(Float.isNaN(r.readFloat32())); - assertTrue(r.eof()); - } - - @Test - public void testReadFloat64() throws Exception { - setContents("F80000000000000000" + "F83FF0000000000000" - + "F84000000000000000" + "F8BFF0000000000000" - + "F88000000000000000" + "F8FFF0000000000000" - + "F87FF0000000000000" + "F87FF8000000000000"); - assertEquals(0.0, r.readFloat64()); - assertEquals(1.0, r.readFloat64()); - assertEquals(2.0, r.readFloat64()); - assertEquals(-1.0, r.readFloat64()); - assertEquals(-0.0, r.readFloat64()); - assertEquals(Double.NEGATIVE_INFINITY, r.readFloat64()); - assertEquals(Double.POSITIVE_INFINITY, r.readFloat64()); - assertTrue(Double.isNaN(r.readFloat64())); - assertTrue(r.eof()); - } - - @Test - public void testReadString() throws Exception { - setContents("F703666F6F" + "83666F6F" + "F700" + "80"); - assertEquals("foo", r.readString()); - assertEquals("foo", r.readString()); - assertEquals("", r.readString()); - assertEquals("", r.readString()); - assertTrue(r.eof()); - } - - @Test - public void testReadStringMaxLength() throws Exception { - setContents("83666F6F" + "83666F6F"); - assertEquals("foo", r.readString(3)); - try { - r.readString(2); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testReadBytes() throws Exception { - setContents("F603010203" + "93010203" + "F600" + "90"); - assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes()); - assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes()); - assertArrayEquals(new byte[] {}, r.readBytes()); - assertArrayEquals(new byte[] {}, r.readBytes()); - assertTrue(r.eof()); - } - - @Test - public void testReadBytesMaxLength() throws Exception { - setContents("93010203" + "93010203"); - assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3)); - try { - r.readBytes(2); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testReadShortList() throws Exception { - setContents("A" + "3" + "01" + "83666F6F" + "FC0080"); - List l = r.readList(Object.class); - assertNotNull(l); - assertEquals(3, l.size()); - assertEquals((byte) 1, l.get(0)); - assertEquals("foo", l.get(1)); - assertEquals((short) 128, l.get(2)); - assertTrue(r.eof()); - } - - @Test - public void testReadList() throws Exception { - setContents("F5" + "01" + "83666F6F" + "FC0080" + "F3"); - List l = r.readList(Object.class); - assertNotNull(l); - assertEquals(3, l.size()); - assertEquals((byte) 1, l.get(0)); - assertEquals("foo", l.get(1)); - assertEquals((short) 128, l.get(2)); - assertTrue(r.eof()); - } - - @Test - public void testReadListTypeSafe() throws Exception { - setContents("A" + "3" + "01" + "02" + "03"); - List l = r.readList(Byte.class); - assertNotNull(l); - assertEquals(3, l.size()); - assertEquals(Byte.valueOf((byte) 1), l.get(0)); - assertEquals(Byte.valueOf((byte) 2), l.get(1)); - assertEquals(Byte.valueOf((byte) 3), l.get(2)); - assertTrue(r.eof()); - } - - @Test - public void testReadListTypeSafeThrowsFormatException() throws Exception { - setContents("A" + "3" + "01" + "83666F6F" + "03"); - // Trying to read a mixed list as a list of bytes should throw a - // FormatException - try { - r.readList(Byte.class); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testReadShortMap() throws Exception { - setContents("B" + "2" + "83666F6F" + "7B" + "90" + "F2"); - Map m = r.readMap(Object.class, Object.class); - assertNotNull(m); - assertEquals(2, m.size()); - assertEquals((byte) 123, m.get("foo")); - Bytes b = new Bytes(new byte[] {}); - assertTrue(m.containsKey(b)); - assertNull(m.get(b)); - assertTrue(r.eof()); - } - - @Test - public void testReadMap() throws Exception { - setContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3"); - Map m = r.readMap(Object.class, Object.class); - assertNotNull(m); - assertEquals(2, m.size()); - assertEquals((byte) 123, m.get("foo")); - Bytes b = new Bytes(new byte[] {}); - assertTrue(m.containsKey(b)); - assertNull(m.get(b)); - assertTrue(r.eof()); - } - - @Test - public void testReadMapTypeSafe() throws Exception { - setContents("B" + "2" + "83666F6F" + "7B" + "80" + "F2"); - Map m = r.readMap(String.class, Byte.class); - assertNotNull(m); - assertEquals(2, m.size()); - assertEquals(Byte.valueOf((byte) 123), m.get("foo")); - assertTrue(m.containsKey("")); - assertNull(m.get("")); - assertTrue(r.eof()); - } - - @Test - public void testMapKeysMustBeUnique() throws Exception { - setContents("B" + "2" + "83666F6F" + "01" + "83626172" + "02" - + "B" + "2" + "83666F6F" + "01" + "83666F6F" + "02"); - // The first map has unique keys - Map m = r.readMap(String.class, Byte.class); - assertNotNull(m); - assertEquals(2, m.size()); - assertEquals(Byte.valueOf((byte) 1), m.get("foo")); - assertEquals(Byte.valueOf((byte) 2), m.get("bar")); - // The second map has a duplicate key - try { - r.readMap(String.class, Byte.class); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testReadDelimitedList() throws Exception { - setContents("F5" + "01" + "83666F6F" + "FC0080" + "F3"); - List l = r.readList(Object.class); - assertNotNull(l); - assertEquals(3, l.size()); - assertEquals((byte) 1, l.get(0)); - assertEquals("foo", l.get(1)); - assertEquals((short) 128, l.get(2)); - assertTrue(r.eof()); - } - - @Test - public void testReadDelimitedListElements() throws Exception { - setContents("F5" + "01" + "83666F6F" + "FC0080" + "F3"); - assertTrue(r.hasListStart()); - r.readListStart(); - assertFalse(r.hasListEnd()); - assertEquals((byte) 1, r.readIntAny()); - assertFalse(r.hasListEnd()); - assertEquals("foo", r.readString()); - assertFalse(r.hasListEnd()); - assertEquals((short) 128, r.readIntAny()); - assertTrue(r.hasListEnd()); - r.readListEnd(); - assertTrue(r.eof()); - } - - @Test - public void testReadDelimitedListTypeSafe() throws Exception { - setContents("F5" + "01" + "02" + "03" + "F3"); - List l = r.readList(Byte.class); - assertNotNull(l); - assertEquals(3, l.size()); - assertEquals(Byte.valueOf((byte) 1), l.get(0)); - assertEquals(Byte.valueOf((byte) 2), l.get(1)); - assertEquals(Byte.valueOf((byte) 3), l.get(2)); - assertTrue(r.eof()); - } - - @Test - public void testReadDelimitedMap() throws Exception { - setContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3"); - Map m = r.readMap(Object.class, Object.class); - assertNotNull(m); - assertEquals(2, m.size()); - assertEquals((byte) 123, m.get("foo")); - Bytes b = new Bytes(new byte[] {}); - assertTrue(m.containsKey(b)); - assertNull(m.get(b)); - assertTrue(r.eof()); - } - - @Test - public void testReadDelimitedMapEntries() throws Exception { - setContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3"); - assertTrue(r.hasMapStart()); - r.readMapStart(); - assertFalse(r.hasMapEnd()); - assertEquals("foo", r.readString()); - assertFalse(r.hasMapEnd()); - assertEquals((byte) 123, r.readIntAny()); - assertFalse(r.hasMapEnd()); - assertArrayEquals(new byte[] {}, r.readBytes()); - assertFalse(r.hasMapEnd()); - assertTrue(r.hasNull()); - r.readNull(); - assertTrue(r.hasMapEnd()); - r.readMapEnd(); - assertTrue(r.eof()); - } - - @Test - public void testReadDelimitedMapTypeSafe() throws Exception { - setContents("F4" + "83666F6F" + "7B" + "80" + "F2" + "F3"); - Map m = r.readMap(String.class, Byte.class); - assertNotNull(m); - assertEquals(2, m.size()); - assertEquals(Byte.valueOf((byte) 123), m.get("foo")); - assertTrue(m.containsKey("")); - assertNull(m.get("")); - assertTrue(r.eof()); - } - - @Test - @SuppressWarnings("unchecked") - public void testReadNestedMapsAndLists() throws Exception { - setContents("B" + "1" + "B" + "1" + "83666F6F" + "7B" - + "A" + "1" + "01"); - Map m = r.readMap(Object.class, Object.class); - assertNotNull(m); - assertEquals(1, m.size()); - Entry e = m.entrySet().iterator().next(); - Map m1 = (Map) e.getKey(); - assertNotNull(m1); - assertEquals(1, m1.size()); - assertEquals((byte) 123, m1.get("foo")); - List l = (List) e.getValue(); - assertNotNull(l); - assertEquals(1, l.size()); - assertEquals((byte) 1, l.get(0)); - assertTrue(r.eof()); - } - - @Test - public void testReadStruct() throws Exception { - setContents("C0" + "83666F6F" + "F1" + "FF" + "83666F6F"); - // Add readers for two structs - r.addStructReader(0, new StructReader() { - public Foo readStruct(Reader r) throws IOException { - r.readStructId(0); - return new Foo(r.readString()); - } - }); - r.addStructReader(255, new StructReader() { - public Bar readStruct(Reader r) throws IOException { - r.readStructId(255); - return new Bar(r.readString()); - } - }); - // Test both ID formats, short and long - assertTrue(r.hasStruct(0)); - assertEquals("foo", r.readStruct(0, Foo.class).s); - assertTrue(r.hasStruct(255)); - assertEquals("foo", r.readStruct(255, Bar.class).s); - } - - @Test - public void testReadStructWithConsumer() throws Exception { - setContents("C0" + "83666F6F" + "F1" + "FF" + "83666F6F"); - // Add readers for two structs - r.addStructReader(0, new StructReader() { - public Foo readStruct(Reader r) throws IOException { - r.readStructId(0); - return new Foo(r.readString()); - } - }); - r.addStructReader(255, new StructReader() { - public Bar readStruct(Reader r) throws IOException { - r.readStructId(255); - return new Bar(r.readString()); - } - }); - // Add a consumer - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - r.addConsumer(new Consumer() { - - public void write(byte b) throws IOException { - out.write(b); - } - - public void write(byte[] b, int off, int len) throws IOException { - out.write(b, off, len); - } - }); - // Test both ID formats, short and long - assertTrue(r.hasStruct(0)); - assertEquals("foo", r.readStruct(0, Foo.class).s); - assertTrue(r.hasStruct(255)); - assertEquals("foo", r.readStruct(255, Bar.class).s); - // Check that everything was passed to the consumer - assertEquals("C0" + "83666F6F" + "F1" + "FF" + "83666F6F", - StringUtils.toHexString(out.toByteArray())); - } - - @Test - public void testUnknownStructIdThrowsFormatException() throws Exception { - setContents("C0" + "83666F6F"); - assertTrue(r.hasStruct(0)); - // No reader has been added for struct ID 0 - try { - r.readStruct(0, Foo.class); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testWrongClassThrowsFormatException() throws Exception { - setContents("C0" + "83666F6F"); - // Add a reader for struct ID 0, class Foo - r.addStructReader(0, new StructReader() { - public Foo readStruct(Reader r) throws IOException { - r.readStructId(0); - return new Foo(r.readString()); - } - }); - assertTrue(r.hasStruct(0)); - // Trying to read the struct as class Bar should throw a FormatException - try { - r.readStruct(0, Bar.class); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testReadListUsingStructReader() throws Exception { - setContents("A" + "1" + "C0" + "83666F6F"); - // Add a reader for a struct - r.addStructReader(0, new StructReader() { - public Foo readStruct(Reader r) throws IOException { - r.readStructId(0); - return new Foo(r.readString()); - } - }); - // Check that the reader is used for lists - List l = r.readList(Foo.class); - assertEquals(1, l.size()); - assertEquals("foo", l.get(0).s); - } - - @Test - public void testReadMapUsingStructReader() throws Exception { - setContents("B" + "1" + "C0" + "83666F6F" + "C1" + "83626172"); - // Add readers for two structs - r.addStructReader(0, new StructReader() { - public Foo readStruct(Reader r) throws IOException { - r.readStructId(0); - return new Foo(r.readString()); - } - }); - r.addStructReader(1, new StructReader() { - public Bar readStruct(Reader r) throws IOException { - r.readStructId(1); - return new Bar(r.readString()); - } - }); - // Check that the readers are used for maps - Map m = r.readMap(Foo.class, Bar.class); - assertEquals(1, m.size()); - Entry e = m.entrySet().iterator().next(); - assertEquals("foo", e.getKey().s); - assertEquals("bar", e.getValue().s); - } - - @Test - public void testMaxLengthAppliesInsideMap() throws Exception { - setContents("B" + "1" + "83666F6F" + "93010203"); - r.setMaxStringLength(3); - r.setMaxBytesLength(3); - Map m = r.readMap(String.class, Bytes.class); - String key = "foo"; - Bytes value = new Bytes(new byte[] {1, 2, 3}); - assertEquals(Collections.singletonMap(key, value), m); - // The max string length should be applied inside the map - setContents("B" + "1" + "83666F6F" + "93010203"); - r.setMaxStringLength(2); - try { - r.readMap(String.class, Bytes.class); - fail(); - } catch(FormatException expected) {} - // The max bytes length should be applied inside the map - setContents("B" + "1" + "83666F6F" + "93010203"); - r.setMaxBytesLength(2); - try { - r.readMap(String.class, Bytes.class); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testReadEmptyInput() throws Exception { - setContents(""); - assertTrue(r.eof()); - } - - private void setContents(String hex) { - in = new ByteArrayInputStream(StringUtils.fromHexString(hex)); - r = new ReaderImpl(in); - } - - private static class Foo { - - private final String s; - - private Foo(String s) { - this.s = s; - } - } - - private static class Bar { - - private final String s; - - private Bar(String s) { - this.s = s; - } - } -} diff --git a/test/net/sf/briar/serial/WriterImplTest.java b/test/net/sf/briar/serial/WriterImplTest.java deleted file mode 100644 index 7c9112e9d..000000000 --- a/test/net/sf/briar/serial/WriterImplTest.java +++ /dev/null @@ -1,291 +0,0 @@ -package net.sf.briar.serial; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.util.StringUtils; - -import org.junit.Before; -import org.junit.Test; - -public class WriterImplTest extends BriarTestCase { - - private ByteArrayOutputStream out = null; - private WriterImpl w = null; - - @Before - public void setUp() { - out = new ByteArrayOutputStream(); - w = new WriterImpl(out); - } - - @Test - public void testWriteBoolean() throws IOException { - w.writeBoolean(true); - w.writeBoolean(false); - // TRUE tag, FALSE tag - checkContents("FE" + "FF"); - } - - @Test - public void testWriteUint7() throws IOException { - w.writeUint7((byte) 0); - w.writeUint7(Byte.MAX_VALUE); - // 0, 127 - checkContents("00" + "7F"); - } - - @Test - public void testWriteInt8() throws IOException { - w.writeInt8((byte) 0); - w.writeInt8((byte) -1); - w.writeInt8(Byte.MIN_VALUE); - w.writeInt8(Byte.MAX_VALUE); - // INT8 tag, 0, INT8 tag, -1, INT8 tag, -128, INT8 tag, 127 - checkContents("FD" + "00" + "FD" + "FF" + "FD" + "80" + "FD" + "7F"); - } - - @Test - public void testWriteInt16() throws IOException { - w.writeInt16((short) 0); - w.writeInt16((short) -1); - w.writeInt16(Short.MIN_VALUE); - w.writeInt16(Short.MAX_VALUE); - // INT16 tag, 0, INT16 tag, -1, INT16 tag, -32768, INT16 tag, 32767 - checkContents("FC" + "0000" + "FC" + "FFFF" + "FC" + "8000" - + "FC" + "7FFF"); - } - - @Test - public void testWriteInt32() throws IOException { - w.writeInt32(0); - w.writeInt32(-1); - w.writeInt32(Integer.MIN_VALUE); - w.writeInt32(Integer.MAX_VALUE); - // INT32 tag, 0, INT32 tag, -1, etc - checkContents("FB" + "00000000" + "FB" + "FFFFFFFF" + "FB" + "80000000" - + "FB" + "7FFFFFFF"); - } - - @Test - public void testWriteInt64() throws IOException { - w.writeInt64(0L); - w.writeInt64(-1L); - w.writeInt64(Long.MIN_VALUE); - w.writeInt64(Long.MAX_VALUE); - // INT64 tag, 0, INT64 tag, -1, etc - checkContents("FA" + "0000000000000000" + "FA" + "FFFFFFFFFFFFFFFF" - + "FA" + "8000000000000000" + "FA" + "7FFFFFFFFFFFFFFF"); - } - - @Test - public void testWriteIntAny() throws IOException { - w.writeIntAny(0); // uint7 - w.writeIntAny(-1); // int8 - w.writeIntAny(Byte.MAX_VALUE); // uint7 - w.writeIntAny(Byte.MAX_VALUE + 1); // int16 - w.writeIntAny(Short.MAX_VALUE); // int16 - w.writeIntAny(Short.MAX_VALUE + 1); // int32 - w.writeIntAny(Integer.MAX_VALUE); // int32 - w.writeIntAny(Integer.MAX_VALUE + 1L); // int64 - checkContents("00" + "FDFF" + "7F" + "FC0080" + "FC7FFF" - + "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000"); - } - - @Test - public void testWriteFloat32() throws IOException { - // http://babbage.cs.qc.edu/IEEE-754/Decimal.html - // 1 bit for sign, 8 for exponent, 23 for significand - w.writeFloat32(0F); // 0 0 0 -> 0x00000000 - w.writeFloat32(1F); // 0 127 1 -> 0x3F800000 - w.writeFloat32(2F); // 0 128 1 -> 0x40000000 - w.writeFloat32(-1F); // 1 127 1 -> 0xBF800000 - w.writeFloat32(-0F); // 1 0 0 -> 0x80000000 - // http://steve.hollasch.net/cgindex/coding/ieeefloat.html - w.writeFloat32(Float.NEGATIVE_INFINITY); // 1 255 0 -> 0xFF800000 - w.writeFloat32(Float.POSITIVE_INFINITY); // 0 255 0 -> 0x7F800000 - w.writeFloat32(Float.NaN); // 0 255 1 -> 0x7FC00000 - checkContents("F9" + "00000000" + "F9" + "3F800000" + "F9" + "40000000" - + "F9" + "BF800000" + "F9" + "80000000" + "F9" + "FF800000" - + "F9" + "7F800000" + "F9" + "7FC00000"); - } - - @Test - public void testWriteFloat64() throws IOException { - // 1 bit for sign, 11 for exponent, 52 for significand - w.writeFloat64(0.0); // 0 0 0 -> 0x0000000000000000 - w.writeFloat64(1.0); // 0 1023 1 -> 0x3FF0000000000000 - w.writeFloat64(2.0); // 0 1024 1 -> 0x4000000000000000 - w.writeFloat64(-1.0); // 1 1023 1 -> 0xBFF0000000000000 - w.writeFloat64(-0.0); // 1 0 0 -> 0x8000000000000000 - w.writeFloat64(Double.NEGATIVE_INFINITY); // 1 2047 0 -> 0xFFF00000... - w.writeFloat64(Double.POSITIVE_INFINITY); // 0 2047 0 -> 0x7FF00000... - w.writeFloat64(Double.NaN); // 0 2047 1 -> 0x7FF8000000000000 - checkContents("F8" + "0000000000000000" + "F8" + "3FF0000000000000" - + "F8" + "4000000000000000" + "F8" + "BFF0000000000000" - + "F8" + "8000000000000000" + "F8" + "FFF0000000000000" - + "F8" + "7FF0000000000000" + "F8" + "7FF8000000000000"); - } - - @Test - public void testWriteShortString() throws IOException { - w.writeString("foo bar baz bam"); - // SHORT_STRING tag, length 15, UTF-8 bytes - checkContents("8" + "F" + "666F6F206261722062617A2062616D"); - } - - @Test - public void testWriteString() throws IOException { - w.writeString("foo bar baz bam "); - // STRING tag, length 16 as uint7, UTF-8 bytes - checkContents("F7" + "10" + "666F6F206261722062617A2062616D20"); - } - - @Test - public void testWriteShortBytes() throws IOException { - w.writeBytes(new byte[] { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 - }); - // SHORT_BYTES tag, length 15, bytes - checkContents("9" + "F" + "000102030405060708090A0B0C0D0E"); - } - - @Test - public void testWriteBytes() throws IOException { - w.writeBytes(new byte[] { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }); - // BYTES tag, length 16 as uint7, bytes - checkContents("F6" + "10" + "000102030405060708090A0B0C0D0E0F"); - } - - @Test - public void testWriteShortList() throws IOException { - List l = new ArrayList(); - for(int i = 0; i < 15; i++) l.add(i); - w.writeList(l); - // SHORT_LIST tag, length, elements as uint7 - checkContents("A" + "F" + "000102030405060708090A0B0C0D0E"); - } - - @Test - public void testWriteList() throws IOException { - List l = new ArrayList(); - for(int i = 0; i < 16; i++) l.add(i); - w.writeList(l); - // LIST tag, elements as uint7, END tag - checkContents("F5" + "000102030405060708090A0B0C0D0E0F" + "F3"); - } - - @Test - public void testListCanContainNull() throws IOException { - List l = new ArrayList(); - l.add(1); - l.add(null); - l.add(2); - w.writeList(l); - // SHORT_LIST tag, length, 1 as uint7, null, 2 as uint7 - checkContents("A" + "3" + "01" + "F2" + "02"); - } - - @Test - public void testWriteShortMap() throws IOException { - // Use LinkedHashMap to get predictable iteration order - Map m = new LinkedHashMap(); - for(int i = 0; i < 15; i++) m.put(i, i + 1); - w.writeMap(m); - // SHORT_MAP tag, size, entries as uint7 - checkContents("B" + "F" + "0001" + "0102" + "0203" + "0304" + "0405" - + "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C" - + "0C0D" + "0D0E" + "0E0F"); - } - - @Test - public void testWriteMap() throws IOException { - // Use LinkedHashMap to get predictable iteration order - Map m = new LinkedHashMap(); - for(int i = 0; i < 16; i++) m.put(i, i + 1); - w.writeMap(m); - // MAP tag, entries as uint7, END tag - checkContents("F4" + "0001" + "0102" + "0203" + "0304" + "0405" - + "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C" - + "0C0D" + "0D0E" + "0E0F" + "0F10" + "F3"); - } - - @Test - public void testWriteDelimitedList() throws IOException { - w.writeListStart(); - w.writeIntAny((byte) 1); // Written as uint7 - w.writeString("foo"); // Written as short string - w.writeIntAny(128L); // Written as an int16 - w.writeListEnd(); - // LIST tag, 1 as uint7, "foo" as short string, 128 as int16, - // END tag - checkContents("F5" + "01" + "83666F6F" + "FC0080" + "F3"); - } - - @Test - public void testWriteDelimitedMap() throws IOException { - w.writeMapStart(); - w.writeString("foo"); // Written as short string - w.writeIntAny(123); // Written as a uint7 - w.writeBytes(new byte[] {}); // Written as short bytes - w.writeNull(); - w.writeMapEnd(); - // MAP tag, "foo" as short string, 123 as uint7, - // byte[] {} as short bytes, NULL tag, END tag - checkContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3"); - } - - @Test - public void testWriteNestedMapsAndLists() throws IOException { - Map m = new LinkedHashMap(); - m.put("foo", Integer.valueOf(123)); - List l = new ArrayList(); - l.add(Byte.valueOf((byte) 1)); - Map m1 = new LinkedHashMap(); - m1.put(m, l); - w.writeMap(m1); - // SHORT_MAP tag, length 1, SHORT_MAP tag, length 1, - // "foo" as short string, 123 as uint7, SHORT_LIST tag, length 1, - // 1 as uint7 - checkContents("B" + "1" + "B" + "1" + "83666F6F" + "7B" + "A1" + "01"); - } - - @Test - public void testWriteNull() throws IOException { - w.writeNull(); - checkContents("F2"); - } - - @Test - public void testWriteShortStructId() throws IOException { - w.writeStructId(0); - w.writeStructId(31); - // SHORT_STRUCT tag (3 bits), 0 (5 bits), SHORT_STRUCT tag (3 bits), - // 31 (5 bits) - checkContents("C0" + "DF"); - } - - @Test - public void testWriteStructId() throws IOException { - w.writeStructId(32); - w.writeStructId(255); - // STRUCT tag, 32 as uint8, STRUCT tag, 255 as uint8 - checkContents("F1" + "20" + "F1" + "FF"); - } - - private void checkContents(String hex) throws IOException { - out.flush(); - out.close(); - byte[] expected = StringUtils.fromHexString(hex); - assertTrue(StringUtils.toHexString(out.toByteArray()), - Arrays.equals(expected, out.toByteArray())); - } -} diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java deleted file mode 100644 index 4bec12fc6..000000000 --- a/test/net/sf/briar/transport/ConnectionReaderImplTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; -import net.sf.briar.BriarTestCase; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -public class ConnectionReaderImplTest extends BriarTestCase { - - private static final int FRAME_LENGTH = 1024; - private static final int MAX_PAYLOAD_LENGTH = - FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; - - @Test - public void testEmptyFramesAreSkipped() throws Exception { - Mockery context = new Mockery(); - final FrameReader reader = context.mock(FrameReader.class); - context.checking(new Expectations() {{ - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(0)); // Empty frame - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(2)); // Non-empty frame with two payload bytes - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(0)); // Empty frame - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(-1)); // No more frames - }}); - ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH); - assertEquals(0, c.read()); // Skip the first empty frame, read a byte - assertEquals(0, c.read()); // Read another byte - assertEquals(-1, c.read()); // Skip the second empty frame, reach EOF - assertEquals(-1, c.read()); // Still at EOF - context.assertIsSatisfied(); - } - - @Test - public void testEmptyFramesAreSkippedWithBuffer() throws Exception { - Mockery context = new Mockery(); - final FrameReader reader = context.mock(FrameReader.class); - context.checking(new Expectations() {{ - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(0)); // Empty frame - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(2)); // Non-empty frame with two payload bytes - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(0)); // Empty frame - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(-1)); // No more frames - }}); - ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH); - byte[] buf = new byte[MAX_PAYLOAD_LENGTH]; - // Skip the first empty frame, read the two payload bytes - assertEquals(2, c.read(buf)); - // Skip the second empty frame, reach EOF - assertEquals(-1, c.read(buf)); - // Still at EOF - assertEquals(-1, c.read(buf)); - context.assertIsSatisfied(); - } - - @Test - public void testMultipleReadsPerFrame() throws Exception { - Mockery context = new Mockery(); - final FrameReader reader = context.mock(FrameReader.class); - context.checking(new Expectations() {{ - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(-1)); // No more frames - }}); - ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH); - byte[] buf = new byte[MAX_PAYLOAD_LENGTH / 2]; - // Read the first half of the payload - assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf)); - // Read the second half of the payload - assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf)); - // Reach EOF - assertEquals(-1, c.read(buf, 0, buf.length)); - context.assertIsSatisfied(); - } - - @Test - public void testMultipleReadsPerFrameWithOffsets() throws Exception { - Mockery context = new Mockery(); - final FrameReader reader = context.mock(FrameReader.class); - context.checking(new Expectations() {{ - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame - oneOf(reader).readFrame(with(any(byte[].class))); - will(returnValue(-1)); // No more frames - }}); - ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH); - byte[] buf = new byte[MAX_PAYLOAD_LENGTH]; - // Read the first half of the payload - assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf, MAX_PAYLOAD_LENGTH / 2, - MAX_PAYLOAD_LENGTH / 2)); - // Read the second half of the payload - assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf, 123, - MAX_PAYLOAD_LENGTH / 2)); - // Reach EOF - assertEquals(-1, c.read(buf, 0, buf.length)); - context.assertIsSatisfied(); - } -} diff --git a/test/net/sf/briar/transport/ConnectionRegistryImplTest.java b/test/net/sf/briar/transport/ConnectionRegistryImplTest.java deleted file mode 100644 index af5e853ac..000000000 --- a/test/net/sf/briar/transport/ConnectionRegistryImplTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.sf.briar.transport; - -import java.util.Arrays; -import java.util.Collections; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.ContactId; -import net.sf.briar.api.protocol.TransportId; -import net.sf.briar.api.transport.ConnectionRegistry; - -import org.junit.Test; - -public class ConnectionRegistryImplTest extends BriarTestCase { - - private final ContactId contactId, contactId1; - private final TransportId transportId, transportId1; - - public ConnectionRegistryImplTest() { - super(); - contactId = new ContactId(1); - contactId1 = new ContactId(2); - transportId = new TransportId(TestUtils.getRandomId()); - transportId1 = new TransportId(TestUtils.getRandomId()); - } - - @Test - public void testRegisterAndUnregister() { - ConnectionRegistry c = new ConnectionRegistryImpl(); - // The registry should be empty - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId)); - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId1)); - // Check that a registered connection shows up - c.registerConnection(contactId, transportId); - assertEquals(Collections.singletonList(contactId), - c.getConnectedContacts(transportId)); - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId1)); - // Register an identical connection - lookup should be unaffected - c.registerConnection(contactId, transportId); - assertEquals(Collections.singletonList(contactId), - c.getConnectedContacts(transportId)); - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId1)); - // Unregister one of the connections - lookup should be unaffected - c.unregisterConnection(contactId, transportId); - assertEquals(Collections.singletonList(contactId), - c.getConnectedContacts(transportId)); - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId1)); - // Unregister the other connection - lookup should be affected - c.unregisterConnection(contactId, transportId); - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId)); - assertEquals(Collections.emptyList(), - c.getConnectedContacts(transportId1)); - // Try to unregister the connection again - exception should be thrown - try { - c.unregisterConnection(contactId, transportId); - fail(); - } catch(IllegalArgumentException expected) {} - // Register both contacts with one transport, one contact with both - c.registerConnection(contactId, transportId); - c.registerConnection(contactId1, transportId); - c.registerConnection(contactId1, transportId1); - assertEquals(Arrays.asList(contactId, contactId1), - c.getConnectedContacts(transportId)); - assertEquals(Collections.singletonList(contactId1), - c.getConnectedContacts(transportId1)); - } -} diff --git a/test/net/sf/briar/transport/ConnectionWindowTest.java b/test/net/sf/briar/transport/ConnectionWindowTest.java deleted file mode 100644 index 808f6a810..000000000 --- a/test/net/sf/briar/transport/ConnectionWindowTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE; -import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; -import static org.junit.Assert.assertArrayEquals; - -import java.util.Collection; - -import net.sf.briar.BriarTestCase; - -import org.junit.Test; - -public class ConnectionWindowTest extends BriarTestCase { - - @Test - public void testWindowSliding() { - ConnectionWindow w = new ConnectionWindow(); - for(int i = 0; i < 100; i++) { - assertFalse(w.isSeen(i)); - w.setSeen(i); - assertTrue(w.isSeen(i)); - } - } - - @Test - public void testWindowJumping() { - ConnectionWindow w = new ConnectionWindow(); - for(int i = 0; i < 100; i += 13) { - assertFalse(w.isSeen(i)); - w.setSeen(i); - assertTrue(w.isSeen(i)); - } - } - - @Test - public void testWindowUpperLimit() { - ConnectionWindow w = new ConnectionWindow(); - // Centre is 0, highest value in window is 15 - w.setSeen(15); - // Centre is 16, highest value in window is 31 - w.setSeen(31); - try { - // Centre is 32, highest value in window is 47 - w.setSeen(48); - fail(); - } catch(IllegalArgumentException expected) {} - // Centre is max - 1, highest value in window is max - byte[] bitmap = new byte[CONNECTION_WINDOW_SIZE / 8]; - w = new ConnectionWindow(MAX_32_BIT_UNSIGNED - 1, bitmap); - assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED - 1)); - assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED)); - // Values greater than max should never be allowed - try { - w.setSeen(MAX_32_BIT_UNSIGNED + 1); - fail(); - } catch(IllegalArgumentException expected) {} - w.setSeen(MAX_32_BIT_UNSIGNED); - assertTrue(w.isSeen(MAX_32_BIT_UNSIGNED)); - // Centre should have moved to max + 1 - assertEquals(MAX_32_BIT_UNSIGNED + 1, w.getCentre()); - // The bit corresponding to max should be set - byte[] expectedBitmap = new byte[CONNECTION_WINDOW_SIZE / 8]; - expectedBitmap[expectedBitmap.length / 2 - 1] = 1; // 00000001 - assertArrayEquals(expectedBitmap, w.getBitmap()); - // Values greater than max should never be allowed even if centre > max - try { - w.setSeen(MAX_32_BIT_UNSIGNED + 1); - fail(); - } catch(IllegalArgumentException expected) {} - } - - @Test - public void testWindowLowerLimit() { - ConnectionWindow w = new ConnectionWindow(); - // Centre is 0, negative values should never be allowed - try { - w.setSeen(-1); - fail(); - } catch(IllegalArgumentException expected) {} - // Slide the window - w.setSeen(15); - // Centre is 16, lowest value in window is 0 - w.setSeen(0); - // Slide the window - w.setSeen(16); - // Centre is 17, lowest value in window is 1 - w.setSeen(1); - try { - w.setSeen(0); - fail(); - } catch(IllegalArgumentException expected) {} - // Slide the window - w.setSeen(25); - // Centre is 26, lowest value in window is 10 - w.setSeen(10); - try { - w.setSeen(9); - fail(); - } catch(IllegalArgumentException expected) {} - // Centre should still be 26 - assertEquals(26, w.getCentre()); - // The bits corresponding to 10, 15, 16 and 25 should be set - byte[] expectedBitmap = new byte[CONNECTION_WINDOW_SIZE / 8]; - expectedBitmap[0] = (byte) 134; // 10000110 - expectedBitmap[1] = 1; // 00000001 - assertArrayEquals(expectedBitmap, w.getBitmap()); - } - - @Test - public void testCannotSetSeenTwice() { - ConnectionWindow w = new ConnectionWindow(); - w.setSeen(15); - try { - w.setSeen(15); - fail(); - } catch(IllegalArgumentException expected) {} - } - - @Test - public void testGetUnseenConnectionNumbers() { - ConnectionWindow w = new ConnectionWindow(); - // Centre is 0; window should cover 0 to 15, inclusive, with none seen - Collection unseen = w.getUnseen(); - assertEquals(16, unseen.size()); - for(int i = 0; i < 16; i++) { - assertTrue(unseen.contains(Long.valueOf(i))); - assertFalse(w.isSeen(i)); - } - w.setSeen(3); - w.setSeen(4); - // Centre is 5; window should cover 0 to 20, inclusive, with two seen - unseen = w.getUnseen(); - assertEquals(19, unseen.size()); - for(int i = 0; i < 21; i++) { - if(i == 3 || i == 4) { - assertFalse(unseen.contains(Long.valueOf(i))); - assertTrue(w.isSeen(i)); - } else { - assertTrue(unseen.contains(Long.valueOf(i))); - assertFalse(w.isSeen(i)); - } - } - w.setSeen(19); - // Centre is 20; window should cover 4 to 35, inclusive, with two seen - unseen = w.getUnseen(); - assertEquals(30, unseen.size()); - for(int i = 4; i < 36; i++) { - if(i == 4 || i == 19) { - assertFalse(unseen.contains(Long.valueOf(i))); - assertTrue(w.isSeen(i)); - } else { - assertTrue(unseen.contains(Long.valueOf(i))); - assertFalse(w.isSeen(i)); - } - } - } -} diff --git a/test/net/sf/briar/transport/ConnectionWriterImplTest.java b/test/net/sf/briar/transport/ConnectionWriterImplTest.java deleted file mode 100644 index 663700326..000000000 --- a/test/net/sf/briar/transport/ConnectionWriterImplTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; -import net.sf.briar.BriarTestCase; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - - -public class ConnectionWriterImplTest extends BriarTestCase { - - private static final int FRAME_LENGTH = 1024; - private static final int MAX_PAYLOAD_LENGTH = - FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; - - @Test - public void testCloseWithoutWritingWritesFinalFrame() throws Exception { - Mockery context = new Mockery(); - final FrameWriter writer = context.mock(FrameWriter.class); - context.checking(new Expectations() {{ - // Write an empty final frame - oneOf(writer).writeFrame(with(any(byte[].class)), with(0), - with(true)); - // Flush the stream - oneOf(writer).flush(); - }}); - ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); - c.close(); - context.assertIsSatisfied(); - } - - @Test - public void testFlushWithoutBufferedDataWritesFrame() throws Exception { - Mockery context = new Mockery(); - final FrameWriter writer = context.mock(FrameWriter.class); - ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); - context.checking(new Expectations() {{ - // Flush the stream - oneOf(writer).flush(); - }}); - c.flush(); - context.assertIsSatisfied(); - } - - @Test - public void testFlushWithBufferedDataWritesFrameAndFlushes() - throws Exception { - Mockery context = new Mockery(); - final FrameWriter writer = context.mock(FrameWriter.class); - ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); - context.checking(new Expectations() {{ - // Write a non-final frame with one payload byte - oneOf(writer).writeFrame(with(any(byte[].class)), with(1), - with(false)); - // Flush the stream - oneOf(writer).flush(); - }}); - c.write(0); - c.flush(); - context.assertIsSatisfied(); - } - - @Test - public void testSingleByteWritesWriteFullFrame() throws Exception { - Mockery context = new Mockery(); - final FrameWriter writer = context.mock(FrameWriter.class); - ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); - context.checking(new Expectations() {{ - // Write a full non-final frame - oneOf(writer).writeFrame(with(any(byte[].class)), - with(MAX_PAYLOAD_LENGTH), with(false)); - }}); - for(int i = 0; i < MAX_PAYLOAD_LENGTH; i++) { - c.write(0); - } - context.assertIsSatisfied(); - } - - @Test - public void testMultiByteWritesWriteFullFrames() throws Exception { - Mockery context = new Mockery(); - final FrameWriter writer = context.mock(FrameWriter.class); - ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); - context.checking(new Expectations() {{ - // Write two full non-final frames - exactly(2).of(writer).writeFrame(with(any(byte[].class)), - with(MAX_PAYLOAD_LENGTH), with(false)); - }}); - // Sanity check - assertEquals(0, MAX_PAYLOAD_LENGTH % 2); - // Write two full payloads using four multi-byte writes - byte[] b = new byte[MAX_PAYLOAD_LENGTH / 2]; - c.write(b); - c.write(b); - c.write(b); - c.write(b); - context.assertIsSatisfied(); - } - - @Test - public void testLargeMultiByteWriteWritesFullFrames() throws Exception { - Mockery context = new Mockery(); - final FrameWriter writer = context.mock(FrameWriter.class); - ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); - context.checking(new Expectations() {{ - // Write two full non-final frames - exactly(2).of(writer).writeFrame(with(any(byte[].class)), - with(MAX_PAYLOAD_LENGTH), with(false)); - // Write a final frame with a one-byte payload - oneOf(writer).writeFrame(with(any(byte[].class)), with(1), - with(true)); - // Flush the stream - oneOf(writer).flush(); - }}); - // Write two full payloads using one large multi-byte write - byte[] b = new byte[MAX_PAYLOAD_LENGTH * 2 + 1]; - c.write(b); - // There should be one byte left in the buffer - c.close(); - context.assertIsSatisfied(); - } -} diff --git a/test/net/sf/briar/transport/IncomingEncryptionLayerTest.java b/test/net/sf/briar/transport/IncomingEncryptionLayerTest.java deleted file mode 100644 index 7638bea2c..000000000 --- a/test/net/sf/briar/transport/IncomingEncryptionLayerTest.java +++ /dev/null @@ -1,183 +0,0 @@ -package net.sf.briar.transport; - -import static javax.crypto.Cipher.ENCRYPT_MODE; -import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; - -import java.io.ByteArrayInputStream; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.FormatException; -import net.sf.briar.api.crypto.AuthenticatedCipher; -import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.crypto.ErasableKey; -import net.sf.briar.crypto.CryptoModule; - -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class IncomingEncryptionLayerTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private static final int FRAME_LENGTH = 1024; - private static final int MAX_PAYLOAD_LENGTH = - FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; - - private final CryptoComponent crypto; - private final AuthenticatedCipher frameCipher; - private final ErasableKey frameKey; - - public IncomingEncryptionLayerTest() { - super(); - Injector i = Guice.createInjector(new CryptoModule()); - crypto = i.getInstance(CryptoComponent.class); - frameCipher = crypto.getFrameCipher(); - frameKey = crypto.generateTestKey(); - } - - @Test - public void testReadValidFrames() throws Exception { - // Generate two valid frames - byte[] frame = generateFrame(0L, FRAME_LENGTH, 123, false, false); - byte[] frame1 = generateFrame(1L, FRAME_LENGTH, 123, false, false); - // Concatenate the frames - byte[] valid = new byte[FRAME_LENGTH * 2]; - System.arraycopy(frame, 0, valid, 0, FRAME_LENGTH); - System.arraycopy(frame1, 0, valid, FRAME_LENGTH, FRAME_LENGTH); - // Read the frames - ByteArrayInputStream in = new ByteArrayInputStream(valid); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - byte[] buf = new byte[FRAME_LENGTH - MAC_LENGTH]; - assertEquals(123, i.readFrame(buf)); - assertEquals(123, i.readFrame(buf)); - } - - @Test - public void testTruncatedFrameThrowsException() throws Exception { - // Generate a valid frame - byte[] frame = generateFrame(0L, FRAME_LENGTH, 123, false, false); - // Chop off the last byte - byte[] truncated = new byte[FRAME_LENGTH - 1]; - System.arraycopy(frame, 0, truncated, 0, FRAME_LENGTH - 1); - // Try to read the frame, which should fail due to truncation - ByteArrayInputStream in = new ByteArrayInputStream(truncated); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - try { - i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testModifiedFrameThrowsException() throws Exception { - // Generate a valid frame - byte[] frame = generateFrame(0L, FRAME_LENGTH, 123, false, false); - // Modify a randomly chosen byte of the frame - frame[(int) (Math.random() * FRAME_LENGTH)] ^= 1; - // Try to read the frame, which should fail due to modification - ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - try { - i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testShortNonFinalFrameThrowsException() throws Exception { - // Generate a short non-final frame - byte[] frame = generateFrame(0L, FRAME_LENGTH - 1, 123, false, false); - // Try to read the frame, which should fail due to invalid length - ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - try { - i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testShortFinalFrameDoesNotThrowException() throws Exception { - // Generate a short final frame - byte[] frame = generateFrame(0L, FRAME_LENGTH - 1, 123, true, false); - // Read the frame - ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - int length = i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]); - assertEquals(123, length); - } - - @Test - public void testInvalidPayloadLengthThrowsException() throws Exception { - // Generate a frame with an invalid payload length - byte[] frame = generateFrame(0L, FRAME_LENGTH, MAX_PAYLOAD_LENGTH + 1, - false, false); - // Try to read the frame, which should fail due to invalid length - ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - try { - i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testNonZeroPaddingThrowsException() throws Exception { - // Generate a frame with bad padding - byte[] frame = generateFrame(0L, FRAME_LENGTH, 123, false, true); - // Try to read the frame, which should fail due to bad padding - ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - try { - i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]); - fail(); - } catch(FormatException expected) {} - } - - @Test - public void testCannotReadBeyondFinalFrame() throws Exception { - // Generate a valid final frame and another valid final frame after it - byte[] frame = generateFrame(0L, FRAME_LENGTH, MAX_PAYLOAD_LENGTH, true, - false); - byte[] frame1 = generateFrame(1L, FRAME_LENGTH, 123, true, false); - // Concatenate the frames - byte[] extraFrame = new byte[FRAME_LENGTH * 2]; - System.arraycopy(frame, 0, extraFrame, 0, FRAME_LENGTH); - System.arraycopy(frame1, 0, extraFrame, FRAME_LENGTH, FRAME_LENGTH); - // Read the final frame, which should first read the tag - ByteArrayInputStream in = new ByteArrayInputStream(extraFrame); - IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - byte[] buf = new byte[FRAME_LENGTH - MAC_LENGTH]; - assertEquals(MAX_PAYLOAD_LENGTH, i.readFrame(buf)); - // The frame after the final frame should not be read - assertEquals(-1, i.readFrame(buf)); - } - - private byte[] generateFrame(long frameNumber, int frameLength, - int payloadLength, boolean finalFrame, boolean badPadding) - throws Exception { - byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH]; - byte[] plaintext = new byte[frameLength - MAC_LENGTH]; - byte[] ciphertext = new byte[frameLength]; - FrameEncoder.encodeIv(iv, frameNumber); - FrameEncoder.encodeAad(aad, frameNumber, plaintext.length); - frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad); - FrameEncoder.encodeHeader(plaintext, finalFrame, payloadLength); - if(badPadding) plaintext[HEADER_LENGTH + payloadLength] = 1; - frameCipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0); - return ciphertext; - } -} diff --git a/test/net/sf/briar/transport/OutgoingEncryptionLayerTest.java b/test/net/sf/briar/transport/OutgoingEncryptionLayerTest.java deleted file mode 100644 index 475e5d9ad..000000000 --- a/test/net/sf/briar/transport/OutgoingEncryptionLayerTest.java +++ /dev/null @@ -1,159 +0,0 @@ -package net.sf.briar.transport; - -import static javax.crypto.Cipher.ENCRYPT_MODE; -import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.ByteArrayOutputStream; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.api.crypto.AuthenticatedCipher; -import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.crypto.ErasableKey; -import net.sf.briar.crypto.CryptoModule; - -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class OutgoingEncryptionLayerTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private static final int FRAME_LENGTH = 1024; - private static final int MAX_PAYLOAD_LENGTH = - FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; - - private final CryptoComponent crypto; - private final AuthenticatedCipher frameCipher; - private final byte[] tag; - - public OutgoingEncryptionLayerTest() { - super(); - Injector i = Guice.createInjector(new CryptoModule()); - crypto = i.getInstance(CryptoComponent.class); - frameCipher = crypto.getFrameCipher(); - tag = new byte[TAG_LENGTH]; - } - - @Test - public void testEncryption() throws Exception { - int payloadLength = 123; - byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH]; - byte[] plaintext = new byte[FRAME_LENGTH - MAC_LENGTH]; - byte[] ciphertext = new byte[FRAME_LENGTH]; - ErasableKey frameKey = crypto.generateTestKey(); - // Calculate the expected ciphertext - FrameEncoder.encodeIv(iv, 0); - FrameEncoder.encodeAad(aad, 0, plaintext.length); - frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad); - FrameEncoder.encodeHeader(plaintext, false, payloadLength); - frameCipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0); - // Check that the actual tag and ciphertext match what's expected - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, frameKey, FRAME_LENGTH, tag); - o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], payloadLength, false); - byte[] actual = out.toByteArray(); - assertEquals(TAG_LENGTH + FRAME_LENGTH, actual.length); - for(int i = 0; i < TAG_LENGTH; i++) assertEquals(tag[i], actual[i]); - for(int i = 0; i < FRAME_LENGTH; i++) { - assertEquals("" + i, ciphertext[i], actual[TAG_LENGTH + i]); - } - } - - @Test - public void testInitiatorClosesConnectionWithoutWriting() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // Initiator's constructor - OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), - FRAME_LENGTH, tag); - // Write an empty final frame without having written any other frames - o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true); - // Nothing should be written to the output stream - assertEquals(0, out.size()); - } - - @Test - public void testResponderClosesConnectionWithoutWriting() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // Responder's constructor - OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), - FRAME_LENGTH); - // Write an empty final frame without having written any other frames - o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true); - // An empty final frame should be written to the output stream - assertEquals(HEADER_LENGTH + MAC_LENGTH, out.size()); - } - - @Test - public void testRemainingCapacityWithTag() throws Exception { - int MAX_PAYLOAD_LENGTH = FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // Initiator's constructor - OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), - FRAME_LENGTH, tag); - // There should be space for nine full frames and one partial frame - byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH]; - assertEquals(10 * MAX_PAYLOAD_LENGTH - TAG_LENGTH, - o.getRemainingCapacity()); - // Write nine frames, each containing a partial payload - for(int i = 0; i < 9; i++) { - o.writeFrame(frame, 123, false); - assertEquals((9 - i) * MAX_PAYLOAD_LENGTH - TAG_LENGTH, - o.getRemainingCapacity()); - } - // Write the final frame, which will not be padded - o.writeFrame(frame, 123, true); - int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH; - assertEquals(MAX_PAYLOAD_LENGTH - TAG_LENGTH - finalFrameLength, - o.getRemainingCapacity()); - } - - @Test - public void testRemainingCapacityWithoutTag() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // Responder's constructor - OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), - FRAME_LENGTH); - // There should be space for ten full frames - assertEquals(10 * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity()); - // Write nine frames, each containing a partial payload - byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH]; - for(int i = 0; i < 9; i++) { - o.writeFrame(frame, 123, false); - assertEquals((9 - i) * MAX_PAYLOAD_LENGTH, - o.getRemainingCapacity()); - } - // Write the final frame, which will not be padded - o.writeFrame(frame, 123, true); - int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH; - assertEquals(MAX_PAYLOAD_LENGTH - finalFrameLength, - o.getRemainingCapacity()); - } - - @Test - public void testRemainingCapacityLimitedByFrameNumbers() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // The connection has plenty of space so we're limited by frame numbers - OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - Long.MAX_VALUE, frameCipher, crypto.generateTestKey(), - FRAME_LENGTH); - // There should be enough frame numbers for 2^32 frames - assertEquals((1L << 32) * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity()); - // Write a frame containing a partial payload - byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH]; - o.writeFrame(frame, 123, false); - // There should be enough frame numbers for 2^32 - 1 frames - assertEquals(((1L << 32) - 1) * MAX_PAYLOAD_LENGTH, - o.getRemainingCapacity()); - } -} diff --git a/test/net/sf/briar/transport/TransportIntegrationTest.java b/test/net/sf/briar/transport/TransportIntegrationTest.java deleted file mode 100644 index daa99b5b0..000000000 --- a/test/net/sf/briar/transport/TransportIntegrationTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MIN_CONNECTION_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.util.Random; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.api.ContactId; -import net.sf.briar.api.crypto.AuthenticatedCipher; -import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.crypto.ErasableKey; -import net.sf.briar.api.protocol.TransportId; -import net.sf.briar.api.transport.ConnectionContext; -import net.sf.briar.api.transport.ConnectionReader; -import net.sf.briar.api.transport.ConnectionWriter; -import net.sf.briar.api.transport.ConnectionWriterFactory; -import net.sf.briar.crypto.CryptoModule; -import net.sf.briar.transport.ConnectionReaderImpl; -import net.sf.briar.transport.ConnectionWriterFactoryImpl; -import net.sf.briar.transport.ConnectionWriterImpl; -import net.sf.briar.transport.IncomingEncryptionLayer; -import net.sf.briar.transport.OutgoingEncryptionLayer; - -import org.junit.Test; - - -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; - -public class TransportIntegrationTest extends BriarTestCase { - - private final int FRAME_LENGTH = 2048; - - private final CryptoComponent crypto; - private final ConnectionWriterFactory connectionWriterFactory; - private final ContactId contactId; - private final TransportId transportId; - private final AuthenticatedCipher frameCipher; - private final Random random; - private final byte[] secret; - private final ErasableKey frameKey; - - public TransportIntegrationTest() { - super(); - Module testModule = new AbstractModule() { - @Override - public void configure() { - bind(ConnectionWriterFactory.class).to( - ConnectionWriterFactoryImpl.class); - } - }; - Injector i = Guice.createInjector(testModule, new CryptoModule()); - crypto = i.getInstance(CryptoComponent.class); - connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class); - contactId = new ContactId(234); - transportId = new TransportId(TestUtils.getRandomId()); - frameCipher = crypto.getFrameCipher(); - random = new Random(); - // Since we're sending frames to ourselves, we only need outgoing keys - secret = new byte[32]; - random.nextBytes(secret); - frameKey = crypto.deriveFrameKey(secret, 0L, true, true); - } - - @Test - public void testInitiatorWriteAndRead() throws Exception { - testWriteAndRead(true); - } - - @Test - public void testResponderWriteAndRead() throws Exception { - testWriteAndRead(false); - } - - private void testWriteAndRead(boolean initiator) throws Exception { - // Generate two random frames - byte[] frame = new byte[1234]; - random.nextBytes(frame); - byte[] frame1 = new byte[321]; - random.nextBytes(frame1); - // Copy the frame key - the copy will be erased - ErasableKey frameCopy = frameKey.copy(); - // Write the frames - ByteArrayOutputStream out = new ByteArrayOutputStream(); - FrameWriter encryptionOut = new OutgoingEncryptionLayer(out, - Long.MAX_VALUE, frameCipher, frameCopy, FRAME_LENGTH); - ConnectionWriter writer = new ConnectionWriterImpl(encryptionOut, - FRAME_LENGTH); - OutputStream out1 = writer.getOutputStream(); - out1.write(frame); - out1.flush(); - out1.write(frame1); - out1.flush(); - byte[] output = out.toByteArray(); - assertEquals(FRAME_LENGTH * 2, output.length); - // Read the tag and the frames back - ByteArrayInputStream in = new ByteArrayInputStream(output); - FrameReader encryptionIn = new IncomingEncryptionLayer(in, frameCipher, - frameKey, FRAME_LENGTH); - ConnectionReader reader = new ConnectionReaderImpl(encryptionIn, - FRAME_LENGTH); - InputStream in1 = reader.getInputStream(); - byte[] recovered = new byte[frame.length]; - int offset = 0; - while(offset < recovered.length) { - int read = in1.read(recovered, offset, recovered.length - offset); - if(read == -1) break; - offset += read; - } - assertEquals(recovered.length, offset); - assertArrayEquals(frame, recovered); - byte[] recovered1 = new byte[frame1.length]; - offset = 0; - while(offset < recovered1.length) { - int read = in1.read(recovered1, offset, recovered1.length - offset); - if(read == -1) break; - offset += read; - } - assertEquals(recovered1.length, offset); - assertArrayEquals(frame1, recovered1); - } - - @Test - public void testOverheadWithTag() throws Exception { - ByteArrayOutputStream out = - new ByteArrayOutputStream(MIN_CONNECTION_LENGTH); - ConnectionContext ctx = new ConnectionContext(contactId, transportId, - secret, 0L, true); - ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out, - MIN_CONNECTION_LENGTH, ctx, false, true); - // Check that the connection writer thinks there's room for a packet - long capacity = w.getRemainingCapacity(); - assertTrue(capacity > MAX_PACKET_LENGTH); - assertTrue(capacity < MIN_CONNECTION_LENGTH); - // Check that there really is room for a packet - byte[] payload = new byte[MAX_PACKET_LENGTH]; - w.getOutputStream().write(payload); - w.getOutputStream().close(); - long used = out.size(); - assertTrue(used > MAX_PACKET_LENGTH); - assertTrue(used <= MIN_CONNECTION_LENGTH); - } - - @Test - public void testOverheadWithoutTag() throws Exception { - ByteArrayOutputStream out = - new ByteArrayOutputStream(MIN_CONNECTION_LENGTH); - ConnectionContext ctx = new ConnectionContext(contactId, transportId, - secret, 0L, true); - ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out, - MIN_CONNECTION_LENGTH, ctx, false, false); - // Check that the connection writer thinks there's room for a packet - long capacity = w.getRemainingCapacity(); - assertTrue(capacity > MAX_PACKET_LENGTH); - assertTrue(capacity < MIN_CONNECTION_LENGTH); - // Check that there really is room for a packet - byte[] payload = new byte[MAX_PACKET_LENGTH]; - w.getOutputStream().write(payload); - w.getOutputStream().close(); - long used = out.size(); - assertTrue(used > MAX_PACKET_LENGTH); - assertTrue(used <= MIN_CONNECTION_LENGTH); - } -} diff --git a/test/net/sf/briar/util/ByteUtilsTest.java b/test/net/sf/briar/util/ByteUtilsTest.java deleted file mode 100644 index c672bc0e2..000000000 --- a/test/net/sf/briar/util/ByteUtilsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.sf.briar.util; - -import net.sf.briar.BriarTestCase; - -import org.junit.Test; - -public class ByteUtilsTest extends BriarTestCase { - - @Test - public void testReadUint16() { - byte[] b = StringUtils.fromHexString("000000"); - assertEquals(0, ByteUtils.readUint16(b, 1)); - b = StringUtils.fromHexString("000001"); - assertEquals(1, ByteUtils.readUint16(b, 1)); - b = StringUtils.fromHexString("00FFFF"); - assertEquals(65535, ByteUtils.readUint16(b, 1)); - } - - @Test - public void testReadUint32() { - byte[] b = StringUtils.fromHexString("0000000000"); - assertEquals(0L, ByteUtils.readUint32(b, 1)); - b = StringUtils.fromHexString("0000000001"); - assertEquals(1L, ByteUtils.readUint32(b, 1)); - b = StringUtils.fromHexString("00FFFFFFFF"); - assertEquals(4294967295L, ByteUtils.readUint32(b, 1)); - } - - - @Test - public void testWriteUint16() { - byte[] b = new byte[3]; - ByteUtils.writeUint16(0, b, 1); - assertEquals("000000", StringUtils.toHexString(b)); - ByteUtils.writeUint16(1, b, 1); - assertEquals("000001", StringUtils.toHexString(b)); - ByteUtils.writeUint16(65535, b, 1); - assertEquals("00FFFF", StringUtils.toHexString(b)); - } - - @Test - public void testWriteUint32() { - byte[] b = new byte[5]; - ByteUtils.writeUint32(0L, b, 1); - assertEquals("0000000000", StringUtils.toHexString(b)); - ByteUtils.writeUint32(1L, b, 1); - assertEquals("0000000001", StringUtils.toHexString(b)); - ByteUtils.writeUint32(4294967295L, b, 1); - assertEquals("00FFFFFFFF", StringUtils.toHexString(b)); - } - - @Test - public void testReadUint() { - byte[] b = new byte[1]; - b[0] = (byte) 128; - for(int i = 0; i < 8; i++) { - assertEquals(1 << i, ByteUtils.readUint(b, i + 1)); - } - b = new byte[2]; - for(int i = 0; i < 65535; i++) { - ByteUtils.writeUint16(i, b, 0); - assertEquals(i, ByteUtils.readUint(b, 16)); - assertEquals(i >> 1, ByteUtils.readUint(b, 15)); - } - } -} diff --git a/test/net/sf/briar/util/FileUtilsTest.java b/test/net/sf/briar/util/FileUtilsTest.java deleted file mode 100644 index 251d78a6e..000000000 --- a/test/net/sf/briar/util/FileUtilsTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package net.sf.briar.util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Scanner; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.util.FileUtils.Callback; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class FileUtilsTest extends BriarTestCase { - - private final File testDir = TestUtils.getTestDirectory(); - - @Before - public void setUp() { - testDir.mkdirs(); - } - - @Test - public void testCreateTempFile() throws IOException { - File temp = FileUtils.createTempFile(); - assertTrue(temp.exists()); - assertTrue(temp.isFile()); - assertEquals(0L, temp.length()); - temp.delete(); - } - - @Test - public void testCopy() throws IOException { - File src = new File(testDir, "src"); - File dest = new File(testDir, "dest"); - TestUtils.createFile(src, "Foo bar\r\nBar foo\r\n"); - long length = src.length(); - - FileUtils.copy(src, dest); - - assertEquals(length, dest.length()); - Scanner in = new Scanner(dest); - assertTrue(in.hasNextLine()); - assertEquals("Foo bar", in.nextLine()); - assertTrue(in.hasNextLine()); - assertEquals("Bar foo", in.nextLine()); - assertFalse(in.hasNext()); - in.close(); - } - - @Test - public void testCopyFromStream() throws IOException { - File src = new File(testDir, "src"); - File dest = new File(testDir, "dest"); - TestUtils.createFile(src, "Foo bar\r\nBar foo\r\n"); - long length = src.length(); - InputStream is = new FileInputStream(src); - is.skip(4); - - FileUtils.copy(is, dest); - - assertEquals(length - 4, dest.length()); - Scanner in = new Scanner(dest); - assertTrue(in.hasNextLine()); - assertEquals("bar", in.nextLine()); - assertTrue(in.hasNextLine()); - assertEquals("Bar foo", in.nextLine()); - assertFalse(in.hasNext()); - in.close(); - } - - @Test - public void testCopyRecursively() throws IOException { - final File dest1 = new File(testDir, "dest/abc/def/1"); - final File dest2 = new File(testDir, "dest/abc/def/2"); - final File dest3 = new File(testDir, "dest/abc/3"); - Mockery context = new Mockery(); - final Callback callback = context.mock(Callback.class); - context.checking(new Expectations() {{ - oneOf(callback).processingFile(dest1); - oneOf(callback).processingFile(dest2); - oneOf(callback).processingFile(dest3); - }}); - - copyRecursively(callback); - - context.assertIsSatisfied(); - } - - @Test - public void testCopyRecursivelyNoCallback() throws IOException { - copyRecursively(null); - } - - private void copyRecursively(Callback callback) throws IOException { - TestUtils.createFile(new File(testDir, "abc/def/1"), "one one one"); - TestUtils.createFile(new File(testDir, "abc/def/2"), "two two two"); - TestUtils.createFile(new File(testDir, "abc/3"), "three three three"); - - File dest = new File(testDir, "dest"); - dest.mkdir(); - - FileUtils.copyRecursively(new File(testDir, "abc"), dest, callback); - - File dest1 = new File(testDir, "dest/abc/def/1"); - assertTrue(dest1.exists()); - assertTrue(dest1.isFile()); - assertEquals("one one one".length(), dest1.length()); - File dest2 = new File(testDir, "dest/abc/def/2"); - assertTrue(dest2.exists()); - assertTrue(dest2.isFile()); - assertEquals("two two two".length(), dest2.length()); - File dest3 = new File(testDir, "dest/abc/3"); - assertTrue(dest3.exists()); - assertTrue(dest3.isFile()); - assertEquals("three three three".length(), dest3.length()); - } - - @Test - public void testDeleteFile() throws IOException { - File foo = new File(testDir, "foo"); - foo.createNewFile(); - assertTrue(foo.exists()); - - FileUtils.delete(foo); - - assertFalse(foo.exists()); - } - - @Test - public void testDeleteDirectory() throws IOException { - File f1 = new File(testDir, "abc/def/1"); - File f2 = new File(testDir, "abc/def/2"); - File f3 = new File(testDir, "abc/3"); - File abc = new File(testDir, "abc"); - File def = new File(testDir, "abc/def"); - TestUtils.createFile(f1, "one one one"); - TestUtils.createFile(f2, "two two two"); - TestUtils.createFile(f3, "three three three"); - - assertTrue(f1.exists()); - assertTrue(f2.exists()); - assertTrue(f3.exists()); - assertTrue(abc.exists()); - assertTrue(def.exists()); - - FileUtils.delete(def); - - assertFalse(f1.exists()); - assertFalse(f2.exists()); - assertTrue(f3.exists()); - assertTrue(abc.exists()); - assertFalse(def.exists()); - } - - @After - public void tearDown() { - TestUtils.deleteTestDirectory(testDir); - } -} diff --git a/test/net/sf/briar/util/StringUtilsTest.java b/test/net/sf/briar/util/StringUtilsTest.java deleted file mode 100644 index d4465e062..000000000 --- a/test/net/sf/briar/util/StringUtilsTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.sf.briar.util; - -import static org.junit.Assert.assertArrayEquals; -import net.sf.briar.BriarTestCase; - -import org.junit.Test; - -public class StringUtilsTest extends BriarTestCase { - - @Test - public void testHead() { - String head = StringUtils.head("123456789", 5); - assertEquals("12345...", head); - } - - @Test - public void testTail() { - String tail = StringUtils.tail("987654321", 5); - assertEquals("...54321", tail); - } - - @Test - public void testToHexString() { - byte[] b = new byte[] {1, 2, 3, 127, -128}; - String s = StringUtils.toHexString(b); - assertEquals("0102037F80", s); - } - - @Test - public void testFromHexString() { - try { - StringUtils.fromHexString("12345"); - fail(); - } catch(IllegalArgumentException expected) {} - try { - StringUtils.fromHexString("ABCDEFGH"); - fail(); - } catch(IllegalArgumentException expected) {} - byte[] b = StringUtils.fromHexString("0102037F80"); - assertArrayEquals(new byte[] {1, 2, 3, 127, -128}, b); - b = StringUtils.fromHexString("0a0b0c0d0e0f"); - assertArrayEquals(new byte[] {10, 11, 12, 13, 14, 15}, b); - } -} diff --git a/test/net/sf/briar/util/ZipUtilsTest.java b/test/net/sf/briar/util/ZipUtilsTest.java deleted file mode 100644 index c08d4a370..000000000 --- a/test/net/sf/briar/util/ZipUtilsTest.java +++ /dev/null @@ -1,202 +0,0 @@ -package net.sf.briar.util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Scanner; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestUtils; -import net.sf.briar.util.ZipUtils.Callback; - -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class ZipUtilsTest extends BriarTestCase { - - private final File testDir = TestUtils.getTestDirectory(); - - private final File f1 = new File(testDir, "abc/def/1"); - private final File f2 = new File(testDir, "abc/def/2"); - private final File f3 = new File(testDir, "abc/3"); - - @Before - public void setUp() { - testDir.mkdirs(); - } - - @Test - public void testCopyToZip() throws IOException { - File src = new File(testDir, "src"); - File dest = new File(testDir, "dest"); - TestUtils.createFile(src, "foo bar baz"); - ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dest)); - - ZipUtils.copyToZip("abc/def", src, zip); - zip.flush(); - zip.close(); - - Map expected = Collections.singletonMap("abc/def", - "foo bar baz"); - checkZipEntries(dest, expected); - } - - private void checkZipEntries(File f, Map expected) - throws IOException { - Map found = new HashMap(); - assertTrue(f.exists()); - assertTrue(f.isFile()); - ZipInputStream unzip = new ZipInputStream(new FileInputStream(f)); - ZipEntry entry; - while((entry = unzip.getNextEntry()) != null) { - String name = entry.getName(); - Scanner s = new Scanner(unzip); - assertTrue(s.hasNextLine()); - String contents = s.nextLine(); - assertFalse(s.hasNextLine()); - unzip.closeEntry(); - found.put(name, contents); - } - unzip.close(); - assertEquals(expected.size(), found.size()); - for(String name : expected.keySet()) { - String contents = found.get(name); - assertNotNull(contents); - assertEquals(expected.get(name), contents); - } - } - - @Test - public void testCopyToZipRecursively() throws IOException { - Mockery context = new Mockery(); - final Callback callback = context.mock(Callback.class); - context.checking(new Expectations() {{ - oneOf(callback).processingFile(f1); - oneOf(callback).processingFile(f2); - oneOf(callback).processingFile(f3); - }}); - - copyRecursively(callback); - - context.assertIsSatisfied(); - } - - @Test - public void testCopyToZipRecursivelyNoCallback() throws IOException { - copyRecursively(null); - } - - private void copyRecursively(Callback callback) throws IOException { - TestUtils.createFile(f1, "one one one"); - TestUtils.createFile(f2, "two two two"); - TestUtils.createFile(f3, "three three three"); - File src = new File(testDir, "abc"); - File dest = new File(testDir, "dest"); - ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dest)); - - ZipUtils.copyToZipRecursively("ghi", src, zip, callback); - zip.flush(); - zip.close(); - - Map expected = new HashMap(); - expected.put("ghi/def/1", "one one one"); - expected.put("ghi/def/2", "two two two"); - expected.put("ghi/3", "three three three"); - checkZipEntries(dest, expected); - } - - @Test - public void testUnzipStream() throws IOException { - Mockery context = new Mockery(); - final Callback callback = context.mock(Callback.class); - context.checking(new Expectations() {{ - oneOf(callback).processingFile(f1); - oneOf(callback).processingFile(f2); - oneOf(callback).processingFile(f3); - }}); - - unzipStream(null, callback); - - context.assertIsSatisfied(); - - assertTrue(f1.exists()); - assertTrue(f1.isFile()); - assertEquals("one one one".length(), f1.length()); - assertTrue(f2.exists()); - assertTrue(f2.isFile()); - assertEquals("two two two".length(), f2.length()); - assertTrue(f3.exists()); - assertTrue(f3.isFile()); - assertEquals("three three three".length(), f3.length()); - } - - @Test - public void testUnzipStreamWithRegex() throws IOException { - Mockery context = new Mockery(); - final Callback callback = context.mock(Callback.class); - context.checking(new Expectations() {{ - oneOf(callback).processingFile(f1); - oneOf(callback).processingFile(f2); - }}); - - unzipStream("^abc/def/.*", callback); - - context.assertIsSatisfied(); - - assertTrue(f1.exists()); - assertTrue(f1.isFile()); - assertEquals("one one one".length(), f1.length()); - assertTrue(f2.exists()); - assertTrue(f2.isFile()); - assertEquals("two two two".length(), f2.length()); - assertFalse(f3.exists()); - } - - @Test - public void testUnzipStreamNoCallback() throws IOException { - unzipStream(null, null); - - assertTrue(f1.exists()); - assertTrue(f1.isFile()); - assertEquals("one one one".length(), f1.length()); - assertTrue(f2.exists()); - assertTrue(f2.isFile()); - assertEquals("two two two".length(), f2.length()); - assertTrue(f3.exists()); - assertTrue(f3.isFile()); - assertEquals("three three three".length(), f3.length()); - } - - private void unzipStream(String regex, Callback callback) - throws IOException { - TestUtils.createFile(f1, "one one one"); - TestUtils.createFile(f2, "two two two"); - TestUtils.createFile(f3, "three three three"); - File src = new File(testDir, "abc"); - File dest = new File(testDir, "dest"); - ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dest)); - ZipUtils.copyToZipRecursively(src.getName(), src, zip, null); - zip.flush(); - zip.close(); - TestUtils.delete(src); - - InputStream in = new FileInputStream(dest); - ZipUtils.unzipStream(in, testDir, regex, callback); - } - - @After - public void tearDown() { - TestUtils.deleteTestDirectory(testDir); - } -}