mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Merged changes from the afsnit repo.
The project is now built as an Android project (via Eclipse or ant). Tests have been moved to a separate project so they can exist outside the Android build process. A basic Android app structure has been created. A Bluetooth plugin for Android has been added, and the Bluetooth plugin for J2SE has been modified to use the same techniques.
This commit is contained in:
2
src/.gitignore
vendored
2
src/.gitignore
vendored
@@ -1 +1 @@
|
||||
/build
|
||||
build
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
<project name='api' default='depend'>
|
||||
<import file='../build-common.xml'/>
|
||||
<project name='prototype' default='compile'>
|
||||
<fileset id='prototype-jars' dir='../libs'>
|
||||
<include name='*.jar'/>
|
||||
</fileset>
|
||||
<path id='android-jar'>
|
||||
<pathelement location='../android.jar'/>
|
||||
</path>
|
||||
<path id='prototype-classes'>
|
||||
<pathelement location='../build'/>
|
||||
</path>
|
||||
<target name='clean'>
|
||||
<delete dir='../build'/>
|
||||
</target>
|
||||
<target name='compile'>
|
||||
<mkdir dir='../build'/>
|
||||
<javac srcdir='net/sf/briar' destdir='../build' source='1.5'
|
||||
includeantruntime='false' debug='off'>
|
||||
<classpath>
|
||||
<fileset refid='prototype-jars'/>
|
||||
<path refid='android-jar'/>
|
||||
<path refid='prototype-classes'/>
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
</project>
|
||||
|
||||
19
src/net/sf/briar/HelloWorldActivity.java
Normal file
19
src/net/sf/briar/HelloWorldActivity.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package net.sf.briar;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class HelloWorldActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
TextView text = new TextView(this);
|
||||
text.setText("Hello world");
|
||||
setContentView(text);
|
||||
Intent intent = new Intent("net.sf.briar.AfsnitService");
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
59
src/net/sf/briar/HelloWorldModule.java
Normal file
59
src/net/sf/briar/HelloWorldModule.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package net.sf.briar;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.sf.briar.api.crypto.Password;
|
||||
import net.sf.briar.api.db.DatabaseConfig;
|
||||
import net.sf.briar.api.ui.UiCallback;
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class HelloWorldModule extends AbstractModule {
|
||||
|
||||
private final DatabaseConfig config;
|
||||
private final UiCallback callback;
|
||||
|
||||
public HelloWorldModule(final Context appContext) {
|
||||
final Password password = new Password() {
|
||||
|
||||
public char[] getPassword() {
|
||||
return "foo bar".toCharArray();
|
||||
}
|
||||
};
|
||||
config = new DatabaseConfig() {
|
||||
|
||||
public File getDataDirectory() {
|
||||
return appContext.getDir("db", MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public Password getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public long getMaxSize() {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
};
|
||||
callback = new UiCallback() {
|
||||
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showMessage(String... message) {}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(DatabaseConfig.class).toInstance(config);
|
||||
bind(UiCallback.class).toInstance(callback);
|
||||
}
|
||||
}
|
||||
95
src/net/sf/briar/HelloWorldService.java
Normal file
95
src/net/sf/briar/HelloWorldService.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package net.sf.briar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.android.AndroidModule;
|
||||
import net.sf.briar.api.crypto.KeyManager;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.plugins.PluginManager;
|
||||
import net.sf.briar.clock.ClockModule;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
import net.sf.briar.db.DatabaseModule;
|
||||
import net.sf.briar.lifecycle.LifecycleModule;
|
||||
import net.sf.briar.plugins.PluginsModule;
|
||||
import net.sf.briar.protocol.ProtocolModule;
|
||||
import net.sf.briar.protocol.duplex.DuplexProtocolModule;
|
||||
import net.sf.briar.protocol.simplex.SimplexProtocolModule;
|
||||
import net.sf.briar.serial.SerialModule;
|
||||
import net.sf.briar.transport.TransportModule;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class HelloWorldService extends Service implements Runnable {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(HelloWorldService.class.getName());
|
||||
|
||||
private DatabaseComponent db = null;
|
||||
private KeyManager keyManager = null;
|
||||
private PluginManager pluginManager = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Thread t = new Thread(this);
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Injector i = Guice.createInjector(
|
||||
new HelloWorldModule(getApplicationContext()),
|
||||
new AndroidModule(), new ClockModule(), new CryptoModule(),
|
||||
new DatabaseModule(), new LifecycleModule(),
|
||||
new PluginsModule(), new ProtocolModule(),
|
||||
new DuplexProtocolModule(), new SimplexProtocolModule(),
|
||||
new SerialModule(), new TransportModule());
|
||||
db = i.getInstance(DatabaseComponent.class);
|
||||
keyManager = i.getInstance(KeyManager.class);
|
||||
pluginManager = i.getInstance(PluginManager.class);
|
||||
try {
|
||||
// Start...
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Starting");
|
||||
db.open(false);
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Database opened");
|
||||
keyManager.start();
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Key manager started");
|
||||
int pluginsStarted = pluginManager.start(this);
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info(pluginsStarted + " plugins started");
|
||||
// ...sleep...
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch(InterruptedException ignored) {}
|
||||
// ...and stop
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Shutting down");
|
||||
int pluginsStopped = pluginManager.stop();
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info(pluginsStopped + " plugins stopped");
|
||||
keyManager.stop();
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Key manager stopped");
|
||||
db.close();
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Database closed");
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/net/sf/briar/android/AndroidExecutorImpl.java
Normal file
89
src/net/sf/briar/android/AndroidExecutorImpl.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package net.sf.briar.android;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
class AndroidExecutorImpl implements AndroidExecutor {
|
||||
|
||||
private static final int SHUTDOWN = 0, RUNNABLE = 1, CALLABLE = 2;
|
||||
|
||||
private final Runnable loop;
|
||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||
private final CountDownLatch startLatch = new CountDownLatch(1);
|
||||
|
||||
private volatile Handler handler = null;
|
||||
|
||||
@Inject
|
||||
AndroidExecutorImpl() {
|
||||
loop = new Runnable() {
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
handler = new FutureTaskHandler();
|
||||
startLatch.countDown();
|
||||
Looper.loop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void startIfNecessary() {
|
||||
if(started.getAndSet(true)) return;
|
||||
new Thread(loop).start();
|
||||
try {
|
||||
startLatch.await();
|
||||
} catch(InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public Future<Void> submit(Runnable r) {
|
||||
startIfNecessary();
|
||||
Future<Void> f = new FutureTask<Void>(r, null);
|
||||
Message m = Message.obtain(handler, RUNNABLE, f);
|
||||
handler.sendMessage(m);
|
||||
return f;
|
||||
}
|
||||
|
||||
public <V> Future<V> submit(Callable<V> c) {
|
||||
startIfNecessary();
|
||||
Future<V> f = new FutureTask<V>(c);
|
||||
Message m = Message.obtain(handler, RUNNABLE, f);
|
||||
handler.sendMessage(m);
|
||||
return f;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if(handler != null) {
|
||||
Message m = Message.obtain(handler, SHUTDOWN);
|
||||
handler.sendMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FutureTaskHandler extends Handler {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message m) {
|
||||
switch(m.what) {
|
||||
case SHUTDOWN:
|
||||
Looper.myLooper().quit();
|
||||
break;
|
||||
case RUNNABLE:
|
||||
case CALLABLE:
|
||||
((FutureTask<?>) m.obj).run();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
src/net/sf/briar/android/AndroidModule.java
Normal file
13
src/net/sf/briar/android/AndroidModule.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package net.sf.briar.android;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class AndroidModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(AndroidExecutor.class).to(AndroidExecutorImpl.class);
|
||||
}
|
||||
}
|
||||
17
src/net/sf/briar/api/android/AndroidExecutor.java
Normal file
17
src/net/sf/briar/api/android/AndroidExecutor.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package net.sf.briar.api.android;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Enables background threads to make Android API calls that must be made from
|
||||
* a thread with a message queue.
|
||||
*/
|
||||
public interface AndroidExecutor {
|
||||
|
||||
Future<Void> submit(Runnable r);
|
||||
|
||||
<V> Future<V> submit(Callable<V> c);
|
||||
|
||||
void shutdown();
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package net.sf.briar.api.crypto;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.db.ContactTransport;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ConnectionContext;
|
||||
import net.sf.briar.api.transport.ContactTransport;
|
||||
|
||||
public interface KeyManager {
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
|
||||
import net.sf.briar.api.protocol.Transport;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.protocol.TransportUpdate;
|
||||
import net.sf.briar.api.transport.ContactTransport;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
|
||||
/**
|
||||
* Encapsulates the database implementation and exposes high-level operations
|
||||
@@ -152,6 +154,19 @@ public interface DatabaseComponent {
|
||||
long incrementConnectionCounter(ContactId c, TransportId t, long period)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given configuration with existing configuration for the
|
||||
* given transport.
|
||||
*/
|
||||
void mergeConfig(TransportId t, TransportConfig c) throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given properties with the existing local properties for the
|
||||
* given transport.
|
||||
*/
|
||||
void mergeLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException;
|
||||
|
||||
/** Processes an acknowledgement from the given contact. */
|
||||
void receiveAck(ContactId c, Ack a) throws DbException;
|
||||
|
||||
@@ -179,12 +194,6 @@ public interface DatabaseComponent {
|
||||
/** Removes a contact (and all associated state) from the database. */
|
||||
void removeContact(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the configuration for the given transport, replacing any existing
|
||||
* configuration for that transport.
|
||||
*/
|
||||
void setConfig(TransportId t, TransportConfig c) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the connection reordering window for the given contact transport
|
||||
* in the given rotation period.
|
||||
@@ -192,13 +201,6 @@ public interface DatabaseComponent {
|
||||
void setConnectionWindow(ContactId c, TransportId t, long period,
|
||||
long centre, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the local transport properties for the given transport, replacing
|
||||
* any existing properties for that transport.
|
||||
*/
|
||||
void setLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException;
|
||||
|
||||
/** Records the user's rating for the given author. */
|
||||
void setRating(AuthorId a, Rating r) throws DbException;
|
||||
|
||||
|
||||
14
src/net/sf/briar/api/db/DatabaseConfig.java
Normal file
14
src/net/sf/briar/api/db/DatabaseConfig.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.sf.briar.api.crypto.Password;
|
||||
|
||||
public interface DatabaseConfig {
|
||||
|
||||
File getDataDirectory();
|
||||
|
||||
Password getPassword();
|
||||
|
||||
long getMaxSize();
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
|
||||
/** Annotation for injecting the directory where the database is stored. */
|
||||
@BindingAnnotation
|
||||
@Target({ PARAMETER })
|
||||
@Retention(RUNTIME)
|
||||
public @interface DatabaseDirectory {}
|
||||
@@ -1,15 +0,0 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
|
||||
/** Annotation for injecting the maximum size in bytes of the database. */
|
||||
@BindingAnnotation
|
||||
@Target({ PARAMETER })
|
||||
@Retention(RUNTIME)
|
||||
public @interface DatabaseMaxSize {}
|
||||
@@ -1,18 +0,0 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
|
||||
/**
|
||||
* Annotation for injecting the password from which the database encryption
|
||||
* key is derived.
|
||||
*/
|
||||
@BindingAnnotation
|
||||
@Target({ PARAMETER })
|
||||
@Retention(RUNTIME)
|
||||
public @interface DatabasePassword {}
|
||||
7
src/net/sf/briar/api/db/DbClosedException.java
Normal file
7
src/net/sf/briar/api/db/DbClosedException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package net.sf.briar.api.db;
|
||||
|
||||
/** Thrown when a database operation is attempted and the database is closed. */
|
||||
public class DbClosedException extends DbException {
|
||||
|
||||
private static final long serialVersionUID = -3679248177625310653L;
|
||||
}
|
||||
@@ -21,11 +21,13 @@ public interface PluginCallback {
|
||||
/** Returns the plugin's remote transport properties. */
|
||||
Map<ContactId, TransportProperties> getRemoteProperties();
|
||||
|
||||
/** Stores the plugin's configuration. */
|
||||
void setConfig(TransportConfig c);
|
||||
/** Merges the given configuration with the plugin's configuration. */
|
||||
void mergeConfig(TransportConfig c);
|
||||
|
||||
/** Stores the plugin's local transport properties. */
|
||||
void setLocalProperties(TransportProperties p);
|
||||
/**
|
||||
* Merges the given properties with the plugin's local transport properties.
|
||||
*/
|
||||
void mergeLocalProperties(TransportProperties p);
|
||||
|
||||
/**
|
||||
* Presents the user with a choice among two or more named options and
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.sf.briar.api.plugins;
|
||||
import java.util.Collection;
|
||||
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import android.content.Context;
|
||||
|
||||
public interface PluginManager {
|
||||
|
||||
@@ -11,7 +12,7 @@ public interface PluginManager {
|
||||
* started. This method must not be called until the database has been
|
||||
* opened.
|
||||
*/
|
||||
int start();
|
||||
int start(Context context);
|
||||
|
||||
/**
|
||||
* Stops the plugins and returns the number of plugins successfully stopped.
|
||||
|
||||
@@ -2,8 +2,13 @@ package net.sf.briar.api.plugins.duplex;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public interface DuplexPluginFactory {
|
||||
|
||||
DuplexPlugin createPlugin(Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
DuplexPluginCallback callback);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@ package net.sf.briar.api.plugins.simplex;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public interface SimplexPluginFactory {
|
||||
|
||||
SimplexPlugin createPlugin(Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SimplexPluginCallback callback);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package net.sf.briar.api.transport;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.sf.briar.api.db;
|
||||
package net.sf.briar.api.transport;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.sf.briar.api.db;
|
||||
package net.sf.briar.api.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE;
|
||||
import net.sf.briar.api.ContactId;
|
||||
@@ -8,11 +8,8 @@ import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.Rating;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.db.ContactTransport;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.BatchId;
|
||||
import net.sf.briar.api.protocol.Group;
|
||||
@@ -21,6 +18,8 @@ import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
import net.sf.briar.api.protocol.Transport;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ContactTransport;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
|
||||
/**
|
||||
* A low-level interface to the database (DatabaseComponent provides a
|
||||
@@ -475,6 +474,24 @@ interface Database<T> {
|
||||
long incrementConnectionCounter(T txn, ContactId c, TransportId t,
|
||||
long period) throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given configuration with the existing configuration for the
|
||||
* given transport.
|
||||
* <p>
|
||||
* Locking: transport write.
|
||||
*/
|
||||
void mergeConfig(T txn, TransportId t, TransportConfig config)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given properties with the existing local properties for the
|
||||
* given transport.
|
||||
* <p>
|
||||
* Locking: transport write.
|
||||
*/
|
||||
void mergeLocalProperties(T txn, TransportId t, TransportProperties p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes an outstanding batch that has been acknowledged. Any messages in
|
||||
* the batch that are still considered outstanding (Status.SENT) with
|
||||
@@ -544,15 +561,6 @@ interface Database<T> {
|
||||
*/
|
||||
void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the configuration for the given transport, replacing any existing
|
||||
* configuration for that transport.
|
||||
* <p>
|
||||
* Locking: transport write.
|
||||
*/
|
||||
void setConfig(T txn, TransportId t, TransportConfig config)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the connection reordering window for the given contact transport in
|
||||
* the given rotation period.
|
||||
@@ -569,15 +577,6 @@ interface Database<T> {
|
||||
*/
|
||||
void setExpiryTime(T txn, ContactId c, long expiry) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the local transport properties for the given transport, replacing
|
||||
* any existing properties for that transport.
|
||||
* <p>
|
||||
* Locking: transport write.
|
||||
*/
|
||||
void setLocalProperties(T txn, TransportId t, TransportProperties p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the user's rating for the given author.
|
||||
* <p>
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.TimerTask;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.db.DbClosedException;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
|
||||
class DatabaseCleanerImpl extends TimerTask implements DatabaseCleaner {
|
||||
@@ -32,6 +33,8 @@ class DatabaseCleanerImpl extends TimerTask implements DatabaseCleaner {
|
||||
if(callback.shouldCheckFreeSpace()) {
|
||||
callback.checkFreeSpaceAndClean();
|
||||
}
|
||||
} catch(DbClosedException e) {
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Database closed, exiting");
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
throw new Error(e); // Kill the application
|
||||
|
||||
@@ -27,14 +27,11 @@ import net.sf.briar.api.Rating;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.db.ContactTransport;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.NoSuchContactException;
|
||||
import net.sf.briar.api.db.NoSuchContactTransportException;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.db.event.BatchReceivedEvent;
|
||||
import net.sf.briar.api.db.event.ContactAddedEvent;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
@@ -62,6 +59,8 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
|
||||
import net.sf.briar.api.protocol.Transport;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.protocol.TransportUpdate;
|
||||
import net.sf.briar.api.transport.ContactTransport;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -1006,6 +1005,47 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeConfig(TransportId t, TransportConfig c)
|
||||
throws DbException {
|
||||
transportLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
db.mergeConfig(txn, t, c);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
transportLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException {
|
||||
boolean changed = false;
|
||||
transportLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!p.equals(db.getLocalProperties(txn, t))) {
|
||||
db.mergeLocalProperties(txn, t, p);
|
||||
db.setTransportsModified(txn, clock.currentTimeMillis());
|
||||
changed = true;
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
transportLock.writeLock().unlock();
|
||||
}
|
||||
// Call the listeners outside the lock
|
||||
if(changed) callListeners(new LocalTransportsUpdatedEvent());
|
||||
}
|
||||
|
||||
public void receiveAck(ContactId c, Ack a) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
try {
|
||||
@@ -1263,23 +1303,6 @@ DatabaseCleaner.Callback {
|
||||
callListeners(new ContactRemovedEvent(c));
|
||||
}
|
||||
|
||||
public void setConfig(TransportId t, TransportConfig c)
|
||||
throws DbException {
|
||||
transportLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
db.setConfig(txn, t, c);
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
transportLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnectionWindow(ContactId c, TransportId t, long period,
|
||||
long centre, byte[] bitmap) throws DbException {
|
||||
contactLock.readLock().lock();
|
||||
@@ -1304,30 +1327,6 @@ DatabaseCleaner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException {
|
||||
boolean changed = false;
|
||||
transportLock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if(!p.equals(db.getLocalProperties(txn, t))) {
|
||||
db.setLocalProperties(txn, t, p);
|
||||
db.setTransportsModified(txn, clock.currentTimeMillis());
|
||||
changed = true;
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch(DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
transportLock.writeLock().unlock();
|
||||
}
|
||||
// Call the listeners outside the lock
|
||||
if(changed) callListeners(new LocalTransportsUpdatedEvent());
|
||||
}
|
||||
|
||||
public void setRating(AuthorId a, Rating r) throws DbException {
|
||||
boolean changed;
|
||||
messageLock.writeLock().lock();
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package net.sf.briar.db;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.crypto.Password;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DatabaseDirectory;
|
||||
import net.sf.briar.api.db.DatabaseConfig;
|
||||
import net.sf.briar.api.db.DatabaseExecutor;
|
||||
import net.sf.briar.api.db.DatabaseMaxSize;
|
||||
import net.sf.briar.api.db.DatabasePassword;
|
||||
import net.sf.briar.api.lifecycle.ShutdownManager;
|
||||
import net.sf.briar.api.protocol.GroupFactory;
|
||||
import net.sf.briar.api.protocol.PacketFactory;
|
||||
@@ -46,10 +42,9 @@ public class DatabaseModule extends AbstractModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
Database<Connection> getDatabase(@DatabaseDirectory File dir,
|
||||
@DatabasePassword Password password, @DatabaseMaxSize long maxSize,
|
||||
Database<Connection> getDatabase(DatabaseConfig config,
|
||||
GroupFactory groupFactory, Clock clock) {
|
||||
return new H2Database(dir, password, maxSize, groupFactory, clock);
|
||||
return new H2Database(config, groupFactory, clock);
|
||||
}
|
||||
|
||||
@Provides @Singleton
|
||||
|
||||
@@ -10,13 +10,10 @@ import java.util.Properties;
|
||||
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.crypto.Password;
|
||||
import net.sf.briar.api.db.DatabaseDirectory;
|
||||
import net.sf.briar.api.db.DatabaseMaxSize;
|
||||
import net.sf.briar.api.db.DatabasePassword;
|
||||
import net.sf.briar.api.db.DatabaseConfig;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.protocol.GroupFactory;
|
||||
|
||||
import org.apache.commons.io.FileSystemUtils;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -29,22 +26,19 @@ class H2Database extends JdbcDatabase {
|
||||
private static final String SECRET_TYPE = "BINARY(32)";
|
||||
|
||||
private final File home;
|
||||
private final Password password;
|
||||
private final String url;
|
||||
private final Password password;
|
||||
private final long maxSize;
|
||||
|
||||
@Inject
|
||||
H2Database(@DatabaseDirectory File dir,
|
||||
@DatabasePassword Password password,
|
||||
@DatabaseMaxSize long maxSize,
|
||||
GroupFactory groupFactory, Clock clock) {
|
||||
H2Database(DatabaseConfig config, GroupFactory groupFactory, Clock clock) {
|
||||
super(groupFactory, clock, HASH_TYPE, BINARY_TYPE, COUNTER_TYPE,
|
||||
SECRET_TYPE);
|
||||
home = new File(dir, "db");
|
||||
this.password = password;
|
||||
home = new File(config.getDataDirectory(), "db");
|
||||
url = "jdbc:h2:split:" + home.getPath()
|
||||
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
|
||||
this.maxSize = maxSize;
|
||||
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
|
||||
password = config.getPassword();
|
||||
maxSize = config.getMaxSize();
|
||||
}
|
||||
|
||||
public void open(boolean resume) throws DbException, IOException {
|
||||
@@ -63,8 +57,7 @@ class H2Database extends JdbcDatabase {
|
||||
public long getFreeSpace() throws DbException {
|
||||
try {
|
||||
File dir = home.getParentFile();
|
||||
String path = dir.getAbsolutePath();
|
||||
long free = FileSystemUtils.freeSpaceKb(path) * 1024L;
|
||||
long free = FileUtils.getFreeSpace(dir);
|
||||
long used = getDiskSpace(dir);
|
||||
long quota = maxSize - used;
|
||||
long min = Math.min(free, quota);
|
||||
|
||||
@@ -28,11 +28,9 @@ import net.sf.briar.api.Rating;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.clock.Clock;
|
||||
import net.sf.briar.api.db.ContactTransport;
|
||||
import net.sf.briar.api.db.DbClosedException;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.MessageHeader;
|
||||
import net.sf.briar.api.db.Status;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.protocol.AuthorId;
|
||||
import net.sf.briar.api.protocol.BatchId;
|
||||
import net.sf.briar.api.protocol.Group;
|
||||
@@ -42,6 +40,8 @@ import net.sf.briar.api.protocol.Message;
|
||||
import net.sf.briar.api.protocol.MessageId;
|
||||
import net.sf.briar.api.protocol.Transport;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ContactTransport;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
/**
|
||||
@@ -376,7 +376,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Connection startTransaction() throws DbException {
|
||||
Connection txn = null;
|
||||
synchronized(connections) {
|
||||
if(closed) throw new DbException();
|
||||
if(closed) throw new DbClosedException();
|
||||
txn = connections.poll();
|
||||
}
|
||||
try {
|
||||
@@ -1207,6 +1207,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
transports.add(t);
|
||||
}
|
||||
t.put(rs.getString(2), rs.getString(3));
|
||||
lastId = id;
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -2340,28 +2341,52 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public void setConfig(Connection txn, TransportId t, TransportConfig c)
|
||||
public void mergeConfig(Connection txn, TransportId t, TransportConfig c)
|
||||
throws DbException {
|
||||
mergeStringMap(txn, t, c, "transportConfigs");
|
||||
}
|
||||
|
||||
public void mergeLocalProperties(Connection txn, TransportId t,
|
||||
TransportProperties p) throws DbException {
|
||||
mergeStringMap(txn, t, p, "transportProperties");
|
||||
}
|
||||
|
||||
private void mergeStringMap(Connection txn, TransportId t,
|
||||
Map<String, String> m, String tableName) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Delete any existing config for the given transport
|
||||
String sql = "DELETE FROM transportConfigs WHERE transportId = ?";
|
||||
// Update any properties that already exist
|
||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||
+ " WHERE transportId = ? AND key = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, t.getBytes());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
// Store the new config
|
||||
sql = "INSERT INTO transportConfigs (transportId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, t.getBytes());
|
||||
for(Entry<String, String> e : c.entrySet()) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setString(3, e.getValue());
|
||||
ps.setBytes(2, t.getBytes());
|
||||
for(Entry<String, String> e : m.entrySet()) {
|
||||
ps.setString(1, e.getValue());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if(batchAffected.length != c.size()) throw new DbStateException();
|
||||
if(batchAffected.length != m.size()) throw new DbStateException();
|
||||
for(int i = 0; i < batchAffected.length; i++) {
|
||||
if(batchAffected[i] > 1) throw new DbStateException();
|
||||
}
|
||||
// Insert any properties that don't already exist
|
||||
sql = "INSERT INTO " + tableName + " (transportId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, t.getBytes());
|
||||
int updateIndex = 0, inserted = 0;
|
||||
for(Entry<String, String> e : m.entrySet()) {
|
||||
if(batchAffected[updateIndex] == 0) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setString(3, e.getValue());
|
||||
ps.addBatch();
|
||||
inserted++;
|
||||
}
|
||||
updateIndex++;
|
||||
}
|
||||
batchAffected = ps.executeBatch();
|
||||
if(batchAffected.length != inserted) throw new DbStateException();
|
||||
for(int i = 0; i < batchAffected.length; i++) {
|
||||
if(batchAffected[i] != 1) throw new DbStateException();
|
||||
}
|
||||
@@ -2411,39 +2436,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalProperties(Connection txn, TransportId t,
|
||||
TransportProperties p) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Delete any existing properties for the given transport
|
||||
String sql = "DELETE FROM transportProperties"
|
||||
+ " WHERE transportId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, t.getBytes());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
// Store the new properties
|
||||
sql = "INSERT INTO transportProperties (transportId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, t.getBytes());
|
||||
for(Entry<String, String> e : p.entrySet()) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setString(3, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if(batchAffected.length != p.size()) throw new DbStateException();
|
||||
for(int i = 0; i < batchAffected.length; i++) {
|
||||
if(batchAffected[i] != 1) throw new DbStateException();
|
||||
}
|
||||
ps.close();
|
||||
} catch(SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Rating setRating(Connection txn, AuthorId a, Rating r)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.sf.briar.api.db;
|
||||
package net.sf.briar.db;
|
||||
|
||||
/** The status of a message with respect to a particular contact. */
|
||||
public enum Status {
|
||||
enum Status {
|
||||
/** The message has not been sent, received, or acked. */
|
||||
NEW,
|
||||
/** The message has been sent, but not received or acked. */
|
||||
@@ -1,8 +1,5 @@
|
||||
package net.sf.briar.plugins;
|
||||
|
||||
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -18,6 +15,7 @@ import java.util.logging.Logger;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.plugins.Plugin;
|
||||
@@ -36,6 +34,8 @@ import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ConnectionDispatcher;
|
||||
import net.sf.briar.api.ui.UiCallback;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -44,17 +44,25 @@ class PluginManagerImpl implements PluginManager {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PluginManagerImpl.class.getName());
|
||||
|
||||
private static final String[] SIMPLEX_PLUGIN_FACTORIES = new String[] {
|
||||
private static final String[] ANDROID_SIMPLEX_FACTORIES = new String[0];
|
||||
|
||||
private static final String[] ANDROID_DUPLEX_FACTORIES = new String[] {
|
||||
"net.sf.briar.plugins.droidtooth.DroidtoothPluginFactory",
|
||||
"net.sf.briar.plugins.socket.SimpleSocketPluginFactory"
|
||||
};
|
||||
|
||||
private static final String[] J2SE_SIMPLEX_FACTORIES = new String[] {
|
||||
"net.sf.briar.plugins.file.RemovableDrivePluginFactory"
|
||||
};
|
||||
|
||||
private static final String[] DUPLEX_PLUGIN_FACTORIES = new String[] {
|
||||
private static final String[] J2SE_DUPLEX_FACTORIES = new String[] {
|
||||
"net.sf.briar.plugins.bluetooth.BluetoothPluginFactory",
|
||||
"net.sf.briar.plugins.socket.SimpleSocketPluginFactory",
|
||||
"net.sf.briar.plugins.tor.TorPluginFactory"
|
||||
};
|
||||
|
||||
private final ExecutorService pluginExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final Poller poller;
|
||||
private final ConnectionDispatcher dispatcher;
|
||||
@@ -64,9 +72,11 @@ class PluginManagerImpl implements PluginManager {
|
||||
|
||||
@Inject
|
||||
PluginManagerImpl(@PluginExecutor ExecutorService pluginExecutor,
|
||||
DatabaseComponent db, Poller poller,
|
||||
ConnectionDispatcher dispatcher, UiCallback uiCallback) {
|
||||
AndroidExecutor androidExecutor, DatabaseComponent db,
|
||||
Poller poller, ConnectionDispatcher dispatcher,
|
||||
UiCallback uiCallback) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.db = db;
|
||||
this.poller = poller;
|
||||
this.dispatcher = dispatcher;
|
||||
@@ -75,17 +85,17 @@ class PluginManagerImpl implements PluginManager {
|
||||
duplexPlugins = new ArrayList<DuplexPlugin>();
|
||||
}
|
||||
|
||||
public synchronized int start() {
|
||||
public synchronized int start(Context appContext) {
|
||||
Set<TransportId> ids = new HashSet<TransportId>();
|
||||
// Instantiate and start the simplex plugins
|
||||
for(String s : SIMPLEX_PLUGIN_FACTORIES) {
|
||||
for(String s : getSimplexPluginFactoryNames()) {
|
||||
try {
|
||||
Class<?> c = Class.forName(s);
|
||||
SimplexPluginFactory factory =
|
||||
(SimplexPluginFactory) c.newInstance();
|
||||
SimplexCallback callback = new SimplexCallback();
|
||||
SimplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
||||
callback);
|
||||
androidExecutor, appContext, callback);
|
||||
if(plugin == null) {
|
||||
if(LOG.isLoggable(Level.INFO)) {
|
||||
LOG.info(factory.getClass().getSimpleName()
|
||||
@@ -111,14 +121,14 @@ class PluginManagerImpl implements PluginManager {
|
||||
}
|
||||
}
|
||||
// Instantiate and start the duplex plugins
|
||||
for(String s : DUPLEX_PLUGIN_FACTORIES) {
|
||||
for(String s : getDuplexPluginFactoryNames()) {
|
||||
try {
|
||||
Class<?> c = Class.forName(s);
|
||||
DuplexPluginFactory factory =
|
||||
(DuplexPluginFactory) c.newInstance();
|
||||
DuplexCallback callback = new DuplexCallback();
|
||||
DuplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
||||
callback);
|
||||
androidExecutor, appContext, callback);
|
||||
if(plugin == null) {
|
||||
if(LOG.isLoggable(Level.INFO)) {
|
||||
LOG.info(factory.getClass().getSimpleName()
|
||||
@@ -152,8 +162,20 @@ class PluginManagerImpl implements PluginManager {
|
||||
return simplexPlugins.size() + duplexPlugins.size();
|
||||
}
|
||||
|
||||
private String[] getSimplexPluginFactoryNames() {
|
||||
if(OsUtils.isAndroid()) return ANDROID_SIMPLEX_FACTORIES;
|
||||
return J2SE_SIMPLEX_FACTORIES;
|
||||
}
|
||||
|
||||
private String[] getDuplexPluginFactoryNames() {
|
||||
if(OsUtils.isAndroid()) return ANDROID_DUPLEX_FACTORIES;
|
||||
return J2SE_DUPLEX_FACTORIES;
|
||||
}
|
||||
|
||||
public synchronized int stop() {
|
||||
int stopped = 0;
|
||||
// Stop the poller
|
||||
poller.stop();
|
||||
// Stop the simplex plugins
|
||||
for(SimplexPlugin plugin : simplexPlugins) {
|
||||
try {
|
||||
@@ -163,7 +185,6 @@ class PluginManagerImpl implements PluginManager {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
simplexPlugins.clear();
|
||||
// Stop the duplex plugins
|
||||
for(DuplexPlugin plugin : duplexPlugins) {
|
||||
try {
|
||||
@@ -173,11 +194,9 @@ class PluginManagerImpl implements PluginManager {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
duplexPlugins.clear();
|
||||
// Stop the poller
|
||||
poller.stop();
|
||||
// Shut down the executor service
|
||||
// Shut down the executors
|
||||
pluginExecutor.shutdown();
|
||||
androidExecutor.shutdown();
|
||||
// Return the number of plugins successfully stopped
|
||||
return stopped;
|
||||
}
|
||||
@@ -232,38 +251,19 @@ class PluginManagerImpl implements PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void setConfig(TransportConfig c) {
|
||||
public void mergeConfig(TransportConfig c) {
|
||||
assert id != null;
|
||||
try {
|
||||
db.setConfig(id, c);
|
||||
db.mergeConfig(id, c);
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalProperties(TransportProperties p) {
|
||||
public void mergeLocalProperties(TransportProperties p) {
|
||||
assert id != null;
|
||||
if(p.size() > MAX_PROPERTIES_PER_TRANSPORT) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning("Plugin " + id + " set too many properties");
|
||||
return;
|
||||
}
|
||||
for(String s : p.keySet()) {
|
||||
if(s.length() > MAX_PROPERTY_LENGTH) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning("Plugin " + id + " set long key: " + s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for(String s : p.values()) {
|
||||
if(s.length() > MAX_PROPERTY_LENGTH) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning("Plugin " + id + " set long value: " + s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
db.setLocalProperties(id, p);
|
||||
db.mergeLocalProperties(id, p);
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
abstract class AbstractListener implements DiscoveryListener {
|
||||
|
||||
protected final DiscoveryAgent discoveryAgent;
|
||||
protected final AtomicInteger searches = new AtomicInteger(1);
|
||||
protected final CountDownLatch finished = new CountDownLatch(1);
|
||||
|
||||
protected AbstractListener(DiscoveryAgent discoveryAgent) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
}
|
||||
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if(searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if(searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
protected Object getDataElementValue(Object o) {
|
||||
if(o instanceof DataElement) {
|
||||
// Bluecove throws an exception if the type is unknown
|
||||
try {
|
||||
return ((DataElement) o).getValue();
|
||||
} catch(ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void findNestedClassIds(Object o, Collection<String> ids) {
|
||||
o = getDataElementValue(o);
|
||||
if(o instanceof Enumeration<?>) {
|
||||
for(Object o1 : Collections.list((Enumeration<?>) o)) {
|
||||
findNestedClassIds(o1, ids);
|
||||
}
|
||||
} else if(o instanceof UUID) {
|
||||
ids.add(o.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -38,28 +36,29 @@ import net.sf.briar.util.StringUtils;
|
||||
|
||||
class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
// Share an ID with the Android Bluetooth plugin
|
||||
public static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
|
||||
+ "00a539fd260f08a13a0d8a900cde5e49"
|
||||
+ "1b4df2ffd42e40c408f2db7868f518aa");
|
||||
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
|
||||
+ "00a539fd260f08a13a0d8a900cde5e49"
|
||||
+ "1b4df2ffd42e40c408f2db7868f518aa");
|
||||
|
||||
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final Clock clock;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final long pollingInterval;
|
||||
private final Object discoveryLock = new Object();
|
||||
private final Object localPropertiesLock = new Object();
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Collection<StreamConnectionNotifier> sockets; // Locking: this
|
||||
|
||||
private boolean running = false; // Locking: this
|
||||
private LocalDevice localDevice = null; // Locking: this
|
||||
private StreamConnectionNotifier socket = null; // Locking: this
|
||||
|
||||
// Non-null if running has ever been true
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
BluetoothPlugin(@PluginExecutor Executor pluginExecutor, Clock clock,
|
||||
DuplexPluginCallback callback, long pollingInterval) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
@@ -67,7 +66,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
this.callback = callback;
|
||||
this.pollingInterval = pollingInterval;
|
||||
scheduler = Executors.newScheduledThreadPool(0);
|
||||
sockets = new ArrayList<StreamConnectionNotifier>();
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
@@ -76,7 +74,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
public void start() throws IOException {
|
||||
// Initialise the Bluetooth stack
|
||||
LocalDevice localDevice;
|
||||
try {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch(UnsatisfiedLinkError e) {
|
||||
@@ -86,10 +83,9 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Address " + localDevice.getBluetoothAddress());
|
||||
LOG.info("Local address " + localDevice.getBluetoothAddress());
|
||||
synchronized(this) {
|
||||
running = true;
|
||||
this.localDevice = localDevice;
|
||||
}
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
@@ -102,8 +98,11 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
makeDeviceDiscoverable();
|
||||
String url = "btspp://localhost:" + getUuid() + ";name=RFCOMM";
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", localDevice.getBluetoothAddress());
|
||||
callback.mergeLocalProperties(p);
|
||||
String url = makeUrl("localhost", getUuid());
|
||||
StreamConnectionNotifier scn;
|
||||
try {
|
||||
scn = (StreamConnectionNotifier) Connector.open(url);
|
||||
@@ -121,44 +120,13 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
acceptContactConnections(scn);
|
||||
}
|
||||
|
||||
private String getUuid() {
|
||||
// FIXME: Avoid making alien calls with a lock held
|
||||
synchronized(localPropertiesLock) {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String uuid = p.get("uuid");
|
||||
if(uuid == null) {
|
||||
// Generate a (weakly) random UUID and store it
|
||||
byte[] b = new byte[16];
|
||||
new Random().nextBytes(b);
|
||||
uuid = generateUuid(b);
|
||||
p.put("uuid", uuid);
|
||||
callback.setLocalProperties(p);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private void makeDeviceDiscoverable() {
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
LocalDevice localDevice;
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
localDevice = this.localDevice;
|
||||
}
|
||||
try {
|
||||
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
// Advertise the address to contacts if the device is discoverable
|
||||
if(localDevice.getDiscoverable() != DiscoveryAgent.NOT_DISCOVERABLE) {
|
||||
// FIXME: Avoid making alien calls with a lock held
|
||||
synchronized(localPropertiesLock) {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
p.put("address", localDevice.getBluetoothAddress());
|
||||
callback.setLocalProperties(p);
|
||||
}
|
||||
}
|
||||
// FIXME: Get the UUID from the local transport properties
|
||||
private String getUuid() {
|
||||
return UUID.nameUUIDFromBytes(new byte[0]).toString();
|
||||
}
|
||||
|
||||
private void tryToClose(StreamConnectionNotifier scn) {
|
||||
@@ -181,7 +149,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return;
|
||||
}
|
||||
BluetoothTransportConnection conn =
|
||||
new BluetoothTransportConnection(s);
|
||||
new BluetoothTransportConnection(s);
|
||||
callback.incomingConnectionCreated(conn);
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
@@ -192,9 +160,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
public void stop() {
|
||||
synchronized(this) {
|
||||
running = false;
|
||||
localDevice = null;
|
||||
for(StreamConnectionNotifier scn : sockets) tryToClose(scn);
|
||||
sockets.clear();
|
||||
if(socket != null) {
|
||||
tryToClose(socket);
|
||||
socket = null;
|
||||
@@ -215,75 +180,31 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
connectAndCallBack(connected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void connectAndCallBack(Collection<ContactId> connected) {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
Map<ContactId, String> discovered = discoverContactUrls(remote);
|
||||
for(Entry<ContactId, String> e : discovered.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
// Don't create redundant connections
|
||||
if(connected.contains(c)) continue;
|
||||
String url = e.getValue();
|
||||
DuplexTransportConnection d = connect(c, url);
|
||||
if(d != null) callback.outgoingConnectionCreated(c, d);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<ContactId, String> discoverContactUrls(
|
||||
Map<ContactId, TransportProperties> remote) {
|
||||
LocalDevice localDevice;
|
||||
synchronized(this) {
|
||||
if(!running) return Collections.emptyMap();
|
||||
localDevice = this.localDevice;
|
||||
}
|
||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
Map<String, ContactId> addresses = new HashMap<String, ContactId>();
|
||||
Map<ContactId, String> uuids = new HashMap<ContactId, String>();
|
||||
callback.getRemoteProperties();
|
||||
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportProperties p = e.getValue();
|
||||
String address = p.get("address");
|
||||
String uuid = p.get("uuid");
|
||||
final ContactId c = e.getKey();
|
||||
if(connected.contains(c)) continue;
|
||||
final String address = e.getValue().get("address");
|
||||
final String uuid = e.getValue().get("uuid");
|
||||
if(address != null && uuid != null) {
|
||||
addresses.put(address, c);
|
||||
uuids.put(c, uuid);
|
||||
}
|
||||
}
|
||||
if(addresses.isEmpty()) return Collections.emptyMap();
|
||||
ContactListener listener = new ContactListener(discoveryAgent,
|
||||
Collections.unmodifiableMap(addresses),
|
||||
Collections.unmodifiableMap(uuids));
|
||||
// FIXME: Avoid making alien calls with a lock held
|
||||
synchronized(discoveryLock) {
|
||||
try {
|
||||
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
|
||||
return listener.waitForUrls();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
return Collections.emptyMap();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Interrupted while waiting for URLs");
|
||||
Thread.currentThread().interrupt();
|
||||
return Collections.emptyMap();
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
synchronized(BluetoothPlugin.this) {
|
||||
if(!running) return;
|
||||
}
|
||||
String url = makeUrl(address, uuid);
|
||||
DuplexTransportConnection conn = connect(url);
|
||||
if(conn != null)
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection connect(ContactId c, String url) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
private DuplexTransportConnection connect(String url) {
|
||||
try {
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
return new BluetoothTransportConnection(s);
|
||||
@@ -297,12 +218,13 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
if(!remote.containsKey(c)) return null;
|
||||
remote = Collections.singletonMap(c, remote.get(c));
|
||||
String url = discoverContactUrls(remote).get(c);
|
||||
return url == null ? null : connect(c, url);
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if(p == null) return null;
|
||||
String address = p.get("address");
|
||||
String uuid = p.get("uuid");
|
||||
if(address == null || uuid == null) return null;
|
||||
String url = makeUrl(address, uuid);
|
||||
return connect(url);
|
||||
}
|
||||
|
||||
public boolean supportsInvitations() {
|
||||
@@ -311,43 +233,40 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
return createInvitationConnection(r, timeout);
|
||||
}
|
||||
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
return createInvitationConnection(r, timeout);
|
||||
}
|
||||
|
||||
private DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
// Use the invitation code to generate the UUID
|
||||
String uuid = generateUuid(r.nextBytes(16));
|
||||
// The invitee's device may not be discoverable, so both parties must
|
||||
// try to initiate connections
|
||||
final ConnectionCallback c = new ConnectionCallback(uuid, timeout);
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
createInvitationConnection(c);
|
||||
// Discover nearby devices and connect to any with the right UUID
|
||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
long end = clock.currentTimeMillis() + timeout;
|
||||
String url = null;
|
||||
while(url == null && clock.currentTimeMillis() < end) {
|
||||
InvitationListener listener =
|
||||
new InvitationListener(discoveryAgent, uuid);
|
||||
// FIXME: Avoid making alien calls with a lock held
|
||||
synchronized(discoveryLock) {
|
||||
try {
|
||||
discoveryAgent.startInquiry(GIAC, listener);
|
||||
url = listener.waitForUrl();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.toString());
|
||||
return null;
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Interrupted while waiting for URL");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
bindInvitationSocket(c);
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
StreamConnection s = c.waitForConnection();
|
||||
return s == null ? null : new BluetoothTransportConnection(s);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
if(url == null) return null;
|
||||
return connect(url);
|
||||
}
|
||||
|
||||
private String generateUuid(byte[] b) {
|
||||
@@ -355,95 +274,59 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return uuid.toString().replaceAll("-", "");
|
||||
}
|
||||
|
||||
private void createInvitationConnection(ConnectionCallback c) {
|
||||
LocalDevice localDevice;
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
localDevice = this.localDevice;
|
||||
}
|
||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
// Try to discover the other party until the invitation times out
|
||||
long end = clock.currentTimeMillis() + c.getTimeout();
|
||||
String url = null;
|
||||
while(url == null && clock.currentTimeMillis() < end) {
|
||||
InvitationListener listener = new InvitationListener(discoveryAgent,
|
||||
c.getUuid());
|
||||
// FIXME: Avoid making alien calls with a lock held
|
||||
synchronized(discoveryLock) {
|
||||
try {
|
||||
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
|
||||
url = listener.waitForUrl();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.toString());
|
||||
return;
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Interrupted while waiting for URL");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
}
|
||||
if(url == null) return;
|
||||
// Try to connect to the other party
|
||||
try {
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
c.addConnection(s);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void bindInvitationSocket(final ConnectionCallback c) {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
if(!running) return null;
|
||||
}
|
||||
// Use the invitation code to generate the UUID
|
||||
String uuid = generateUuid(r.nextBytes(16));
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
String url = "btspp://localhost:" + c.getUuid() + ";name=RFCOMM";
|
||||
// Bind a socket for accepting the invitation connection
|
||||
final StreamConnectionNotifier scn;
|
||||
try {
|
||||
scn = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
synchronized(this) {
|
||||
if(!running) {
|
||||
tryToClose(scn);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
sockets.add(scn);
|
||||
}
|
||||
// Close the socket when the invitation times out
|
||||
Runnable close = new Runnable() {
|
||||
public void run() {
|
||||
synchronized(this) {
|
||||
sockets.remove(scn);
|
||||
}
|
||||
tryToClose(scn);
|
||||
}
|
||||
};
|
||||
ScheduledFuture<?> future = scheduler.schedule(close,
|
||||
c.getTimeout(), TimeUnit.MILLISECONDS);
|
||||
// Try to accept a connection
|
||||
ScheduledFuture<?> f = scheduler.schedule(close, timeout, MILLISECONDS);
|
||||
// Try to accept a connection and close the socket
|
||||
try {
|
||||
StreamConnection s = scn.acceptAndOpen();
|
||||
// Close the socket and return the connection
|
||||
if(future.cancel(false)) {
|
||||
synchronized(this) {
|
||||
sockets.remove(scn);
|
||||
}
|
||||
tryToClose(scn);
|
||||
}
|
||||
c.addConnection(s);
|
||||
return new BluetoothTransportConnection(s);
|
||||
} catch(IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info(e.toString());
|
||||
tryToClose(scn);
|
||||
return null;
|
||||
} finally {
|
||||
if(f.cancel(false)) tryToClose(scn);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeDeviceDiscoverable() {
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
try {
|
||||
localDevice.setDiscoverable(GIAC);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,20 @@ package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.clock.SystemClock;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
import android.content.Context;
|
||||
|
||||
public class BluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final long POLLING_INTERVAL = 3L * 60L * 1000L; // 3 mins
|
||||
|
||||
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
DuplexPluginCallback callback) {
|
||||
return new BluetoothPlugin(pluginExecutor, new SystemClock(), callback,
|
||||
POLLING_INTERVAL);
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.microedition.io.StreamConnection;
|
||||
|
||||
class ConnectionCallback {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectionCallback.class.getName());
|
||||
|
||||
private final String uuid;
|
||||
private final long timeout;
|
||||
private final long end;
|
||||
|
||||
private StreamConnection connection = null; // Locking: this
|
||||
|
||||
ConnectionCallback(String uuid, long timeout) {
|
||||
this.uuid = uuid;
|
||||
this.timeout = timeout;
|
||||
end = System.currentTimeMillis() + timeout;
|
||||
}
|
||||
|
||||
String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
synchronized StreamConnection waitForConnection()
|
||||
throws InterruptedException {
|
||||
long now = System.currentTimeMillis();
|
||||
while(connection == null && now < end) {
|
||||
wait(end - now);
|
||||
now = System.currentTimeMillis();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
synchronized void addConnection(StreamConnection s) {
|
||||
if(connection == null) {
|
||||
connection = s;
|
||||
notifyAll();
|
||||
} else {
|
||||
// Redundant connection
|
||||
try {
|
||||
s.close();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
|
||||
class ContactListener extends AbstractListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ContactListener.class.getName());
|
||||
|
||||
private final Map<String, ContactId> addresses;
|
||||
private final Map<ContactId, String> uuids;
|
||||
private final Map<ContactId, String> urls;
|
||||
|
||||
ContactListener(DiscoveryAgent discoveryAgent,
|
||||
Map<String, ContactId> addresses, Map<ContactId, String> uuids) {
|
||||
super(discoveryAgent);
|
||||
this.addresses = addresses;
|
||||
this.uuids = uuids;
|
||||
urls = Collections.synchronizedMap(new HashMap<ContactId, String>());
|
||||
}
|
||||
|
||||
Map<ContactId, String> waitForUrls() throws InterruptedException {
|
||||
finished.await();
|
||||
return urls;
|
||||
}
|
||||
|
||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
||||
// Do we recognise the address?
|
||||
ContactId contactId = addresses.get(device.getBluetoothAddress());
|
||||
if(contactId == null) return;
|
||||
// Do we have a UUID for this contact?
|
||||
String uuid = uuids.get(contactId);
|
||||
if(uuid == null) return;
|
||||
UUID[] uuids = new UUID[] { new UUID(uuid, false) };
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(null, uuids, device, this);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
searches.incrementAndGet();
|
||||
}
|
||||
|
||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
||||
for(ServiceRecord record : services) {
|
||||
// Do we recognise the address?
|
||||
RemoteDevice device = record.getHostDevice();
|
||||
ContactId c = addresses.get(device.getBluetoothAddress());
|
||||
if(c == null) continue;
|
||||
// Do we have a UUID for this contact?
|
||||
String uuid = uuids.get(c);
|
||||
if(uuid == null) return;
|
||||
// Does this service have a URL?
|
||||
String serviceUrl = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if(serviceUrl == null) continue;
|
||||
// Does this service have the UUID we're looking for?
|
||||
Collection<String> uuids = new TreeSet<String>();
|
||||
findNestedClassIds(record.getAttributeValue(0x1), uuids);
|
||||
for(String u : uuids) {
|
||||
if(uuid.equalsIgnoreCase(u.toString())) {
|
||||
// The UUID matches - store the URL
|
||||
urls.put(c, serviceUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,37 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
class InvitationListener extends AbstractListener {
|
||||
class InvitationListener implements DiscoveryListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(InvitationListener.class.getName());
|
||||
Logger.getLogger(InvitationListener.class.getName());
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
private final String uuid;
|
||||
|
||||
private volatile String url = null;
|
||||
|
||||
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
|
||||
super(discoveryAgent);
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@@ -61,4 +70,35 @@ class InvitationListener extends AbstractListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if(searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if(searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
// UUIDs are sometimes buried in nested data elements
|
||||
private void findNestedClassIds(Object o, Collection<String> ids) {
|
||||
o = getDataElementValue(o);
|
||||
if(o instanceof Enumeration<?>) {
|
||||
for(Object o1 : Collections.list((Enumeration<?>) o))
|
||||
findNestedClassIds(o1, ids);
|
||||
} else if(o instanceof UUID) {
|
||||
ids.add(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDataElementValue(Object o) {
|
||||
if(o instanceof DataElement) {
|
||||
// Bluecove throws an exception if the type is unknown
|
||||
try {
|
||||
return ((DataElement) o).getValue();
|
||||
} catch(ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
431
src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
Normal file
431
src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
Normal file
@@ -0,0 +1,431 @@
|
||||
package net.sf.briar.plugins.droidtooth;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Build;
|
||||
|
||||
class DroidtoothPlugin implements DuplexPlugin {
|
||||
|
||||
// Share an ID with the J2SE Bluetooth plugin
|
||||
public static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
|
||||
+ "00a539fd260f08a13a0d8a900cde5e49"
|
||||
+ "1b4df2ffd42e40c408f2db7868f518aa");
|
||||
|
||||
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
private static final String FOUND = "android.bluetooth.device.action.FOUND";
|
||||
private static final String DISCOVERY_FINISHED =
|
||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final long pollingInterval;
|
||||
|
||||
private boolean running = false; // Locking: this
|
||||
private BluetoothServerSocket socket = null; // Locking: this
|
||||
|
||||
// Non-null if running has ever been true
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
DroidtoothPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
DuplexPluginCallback callback, long pollingInterval) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
this.callback = callback;
|
||||
this.pollingInterval = pollingInterval;
|
||||
}
|
||||
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
Callable<BluetoothAdapter> c = new Callable<BluetoothAdapter>() {
|
||||
public BluetoothAdapter call() throws Exception {
|
||||
return BluetoothAdapter.getDefaultAdapter();
|
||||
}
|
||||
};
|
||||
Future<BluetoothAdapter> f = androidExecutor.submit(c);
|
||||
try {
|
||||
adapter = f.get();
|
||||
} catch(InterruptedException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(ExecutionException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
if(adapter == null) throw new IOException(); // Bluetooth not supported
|
||||
synchronized(this) {
|
||||
running = true;
|
||||
}
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
bind();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
if(!enableBluetooth()) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
return;
|
||||
}
|
||||
makeDeviceDiscoverable();
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Local address " + adapter.getAddress());
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", adapter.getAddress());
|
||||
callback.mergeLocalProperties(p);
|
||||
// Bind a server socket to accept connections from contacts
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = InsecureBluetooth.listen(adapter, "RFCOMM", getUuid(), false);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
return;
|
||||
}
|
||||
synchronized(this) {
|
||||
if(!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
}
|
||||
acceptContactConnections(ss);
|
||||
}
|
||||
|
||||
private boolean enableBluetooth() {
|
||||
synchronized(this) {
|
||||
if(!running) return false;
|
||||
}
|
||||
if(adapter.isEnabled()) return true;
|
||||
// Try to enable the adapter and wait for the result
|
||||
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
|
||||
BluetoothStateReceiver receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
try {
|
||||
if(!adapter.enable()) return false;
|
||||
return receiver.waitForStateChange();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Interrupted while enabling Bluetooth");
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void makeDeviceDiscoverable() {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
if(adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) return;
|
||||
// Indefinite discoverability can only be set on API Level 8 or higher
|
||||
if(Build.VERSION.SDK_INT < 8) return;
|
||||
Intent intent = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||
intent.putExtra(EXTRA_DISCOVERABLE_DURATION, 0);
|
||||
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
appContext.startActivity(intent);
|
||||
}
|
||||
|
||||
// FIXME: Get the UUID from the local transport properties
|
||||
private UUID getUuid() {
|
||||
return UUID.nameUUIDFromBytes(new byte[0]);
|
||||
}
|
||||
|
||||
private void tryToClose(BluetoothServerSocket ss) {
|
||||
try {
|
||||
ss.close();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptContactConnections(BluetoothServerSocket ss) {
|
||||
while(true) {
|
||||
BluetoothSocket s;
|
||||
try {
|
||||
s = ss.accept();
|
||||
} catch(IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info(e.toString());
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
DroidtoothTransportConnection conn =
|
||||
new DroidtoothTransportConnection(s);
|
||||
callback.incomingConnectionCreated(conn);
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws IOException {
|
||||
synchronized(this) {
|
||||
running = false;
|
||||
if(socket != null) {
|
||||
tryToClose(socket);
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getPollingInterval() {
|
||||
return pollingInterval;
|
||||
}
|
||||
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
synchronized(this) {
|
||||
if(!running) return;
|
||||
}
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
final ContactId c = e.getKey();
|
||||
if(connected.contains(c)) continue;
|
||||
final String address = e.getValue().get("address");
|
||||
final String uuid = e.getValue().get("uuid");
|
||||
if(address != null && uuid != null) {
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
synchronized(DroidtoothPlugin.this) {
|
||||
if(!running) return;
|
||||
}
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if(conn != null)
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection connect(String address, String uuid) {
|
||||
// Validate the address
|
||||
if(!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning("Invalid address " + address);
|
||||
return null;
|
||||
}
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
// Validate the UUID
|
||||
UUID u;
|
||||
try {
|
||||
u = UUID.fromString(uuid);
|
||||
} catch(IllegalArgumentException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning("Invalid UUID " + uuid);
|
||||
return null;
|
||||
}
|
||||
// Try to connect
|
||||
try {
|
||||
BluetoothSocket s = InsecureBluetooth.createSocket(d, u, false);
|
||||
return new DroidtoothTransportConnection(s);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if(p == null) return null;
|
||||
String address = p.get("address");
|
||||
String uuid = p.get("uuid");
|
||||
if(address == null || uuid == null) return null;
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
// Use the same pseudo-random UUID as the contact
|
||||
String uuid = UUID.nameUUIDFromBytes(r.nextBytes(16)).toString();
|
||||
// Register to receive Bluetooth discovery intents
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(FOUND);
|
||||
filter.addAction(DISCOVERY_FINISHED);
|
||||
// Discover nearby devices and connect to any with the right UUID
|
||||
DiscoveryReceiver receiver = new DiscoveryReceiver(uuid);
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
adapter.startDiscovery();
|
||||
try {
|
||||
return receiver.waitForConnection(timeout);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Interrupted while sending invitation");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
// Use the same pseudo-random UUID as the contact
|
||||
UUID uuid = UUID.nameUUIDFromBytes(r.nextBytes(16));
|
||||
// Bind a new server socket to accept the invitation connection
|
||||
final BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = InsecureBluetooth.listen(adapter, "RFCOMM", uuid, false);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
return null;
|
||||
}
|
||||
// Return the first connection received by the socket, if any
|
||||
try {
|
||||
return new DroidtoothTransportConnection(ss.accept((int) timeout));
|
||||
} catch(SocketTimeoutException e) {
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Invitation timed out");
|
||||
return null;
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
return null;
|
||||
} finally {
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
|
||||
private volatile boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if(state == STATE_ON) {
|
||||
enabled = true;
|
||||
finish(ctx);
|
||||
} else if(state == STATE_OFF) {
|
||||
finish(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private void finish(Context ctx) {
|
||||
ctx.getApplicationContext().unregisterReceiver(this);
|
||||
finished.countDown();
|
||||
}
|
||||
|
||||
boolean waitForStateChange() throws InterruptedException {
|
||||
finished.await();
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryReceiver extends BroadcastReceiver {
|
||||
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final String uuid;
|
||||
|
||||
private volatile DuplexTransportConnection connection = null;
|
||||
|
||||
private DiscoveryReceiver(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context ctx, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if(action.equals(DISCOVERY_FINISHED)) {
|
||||
finish(ctx);
|
||||
} else if(action.equals(FOUND)) {
|
||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||
final String address = d.getAddress();
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
synchronized(DroidtoothPlugin.this) {
|
||||
if(!running) return;
|
||||
}
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if(conn != null) {
|
||||
connection = conn;
|
||||
finish(ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void finish(Context ctx) {
|
||||
ctx.getApplicationContext().unregisterReceiver(this);
|
||||
finished.countDown();
|
||||
}
|
||||
|
||||
private DuplexTransportConnection waitForConnection(long timeout)
|
||||
throws InterruptedException {
|
||||
finished.await(timeout, MILLISECONDS);
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.sf.briar.plugins.droidtooth;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
import android.content.Context;
|
||||
|
||||
public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final long POLLING_INTERVAL = 3L * 60L * 1000L; // 3 mins
|
||||
|
||||
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
DuplexPluginCallback callback) {
|
||||
return new DroidtoothPlugin(pluginExecutor, androidExecutor, appContext,
|
||||
callback, POLLING_INTERVAL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.sf.briar.plugins.droidtooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
class DroidtoothTransportConnection implements DuplexTransportConnection {
|
||||
|
||||
private final BluetoothSocket socket;
|
||||
|
||||
DroidtoothTransportConnection(BluetoothSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return socket.getInputStream();
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
}
|
||||
|
||||
public boolean shouldFlush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dispose(boolean exception, boolean recognised)
|
||||
throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
207
src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java
Normal file
207
src/net/sf/briar/plugins/droidtooth/InsecureBluetooth.java
Normal file
@@ -0,0 +1,207 @@
|
||||
package net.sf.briar.plugins.droidtooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
// Based on http://stanford.edu/~tpurtell/InsecureBluetooth.java by T.J. Purtell
|
||||
class InsecureBluetooth {
|
||||
|
||||
static BluetoothServerSocket listen(BluetoothAdapter adapter, String name,
|
||||
UUID uuid, boolean encrypt) throws IOException {
|
||||
try {
|
||||
String className = BluetoothAdapter.class.getName()
|
||||
+ ".RfcommChannelPicker";
|
||||
Class<?> channelPickerClass = null;
|
||||
Class<?>[] children = BluetoothAdapter.class.getDeclaredClasses();
|
||||
for(Class<?> c : children) {
|
||||
if(c.getCanonicalName().equals(className)) {
|
||||
channelPickerClass = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(channelPickerClass == null)
|
||||
throw new IOException("Can't find channel picker class");
|
||||
Constructor<?> constructor =
|
||||
channelPickerClass.getDeclaredConstructor(UUID.class);
|
||||
if(constructor == null)
|
||||
throw new IOException("Can't find channel picker constructor");
|
||||
Object channelPicker = constructor.newInstance(uuid);
|
||||
Method nextChannel = channelPickerClass.getDeclaredMethod(
|
||||
"nextChannel", new Class[0]);
|
||||
nextChannel.setAccessible(true);
|
||||
BluetoothServerSocket socket = null;
|
||||
int channel;
|
||||
while(true) {
|
||||
channel = (Integer) nextChannel.invoke(channelPicker,
|
||||
new Object[0]);
|
||||
if(channel == -1)
|
||||
throw new IOException("No available channels");
|
||||
try {
|
||||
socket = listen(channel, encrypt);
|
||||
break;
|
||||
} catch(InUseException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Field f = adapter.getClass().getDeclaredField("mService");
|
||||
f.setAccessible(true);
|
||||
Object mService = f.get(adapter);
|
||||
Method addRfcommServiceRecord =
|
||||
mService.getClass().getDeclaredMethod(
|
||||
"addRfcommServiceRecord", String.class,
|
||||
ParcelUuid.class, int.class, IBinder.class);
|
||||
addRfcommServiceRecord.setAccessible(true);
|
||||
int handle = (Integer) addRfcommServiceRecord.invoke(mService, name,
|
||||
new ParcelUuid(uuid), channel, new Binder());
|
||||
if(handle == -1) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch(IOException ignored) {}
|
||||
throw new IOException("Can't register SDP record for " + name);
|
||||
}
|
||||
Field f1 = adapter.getClass().getDeclaredField("mHandler");
|
||||
f1.setAccessible(true);
|
||||
Object mHandler = f1.get(adapter);
|
||||
Method setCloseHandler = socket.getClass().getDeclaredMethod(
|
||||
"setCloseHandler", Handler.class, int.class);
|
||||
setCloseHandler.setAccessible(true);
|
||||
setCloseHandler.invoke(socket, mHandler, handle);
|
||||
return socket;
|
||||
} catch(NoSuchMethodException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(NoSuchFieldException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(InstantiationException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(InvocationTargetException e) {
|
||||
if(e.getCause() instanceof IOException) {
|
||||
throw (IOException) e.getCause();
|
||||
} else {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BluetoothServerSocket listen(int port, boolean encrypt,
|
||||
boolean reuse) throws IOException, InUseException {
|
||||
BluetoothServerSocket socket = null;
|
||||
try {
|
||||
Constructor<BluetoothServerSocket> constructor =
|
||||
BluetoothServerSocket.class.getDeclaredConstructor(
|
||||
int.class, boolean.class, boolean.class, int.class);
|
||||
if(constructor == null)
|
||||
throw new IOException("Can't find server socket constructor");
|
||||
constructor.setAccessible(true);
|
||||
Field f = BluetoothSocket.class.getDeclaredField("TYPE_RFCOMM");
|
||||
f.setAccessible(true);
|
||||
int rfcommType = (Integer) f.get(null);
|
||||
Field f1 = BluetoothSocket.class.getDeclaredField("EADDRINUSE");
|
||||
f1.setAccessible(true);
|
||||
int eAddrInUse = (Integer) f1.get(null);
|
||||
socket = constructor.newInstance(rfcommType, false, encrypt, port);
|
||||
Field f2 = socket.getClass().getDeclaredField("mSocket");
|
||||
f2.setAccessible(true);
|
||||
Object mSocket = f2.get(socket);
|
||||
Method bindListen = mSocket.getClass().getDeclaredMethod(
|
||||
"bindListen", new Class[0]);
|
||||
bindListen.setAccessible(true);
|
||||
Object result = bindListen.invoke(mSocket, new Object[0]);
|
||||
int errno = (Integer) result;
|
||||
if(reuse && errno == eAddrInUse) {
|
||||
throw new InUseException();
|
||||
} else if(errno != 0) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch(IOException ignored) {}
|
||||
Method throwErrnoNative = mSocket.getClass().getMethod(
|
||||
"throwErrnoNative", int.class);
|
||||
throwErrnoNative.invoke(mSocket, errno);
|
||||
}
|
||||
return socket;
|
||||
} catch(NoSuchMethodException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(NoSuchFieldException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(InstantiationException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(InvocationTargetException e) {
|
||||
if(e.getCause() instanceof IOException) {
|
||||
throw (IOException) e.getCause();
|
||||
} else {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BluetoothServerSocket listen(int port, boolean encrypt)
|
||||
throws IOException {
|
||||
return listen(port, encrypt, false);
|
||||
}
|
||||
|
||||
private static BluetoothSocket createSocket(BluetoothDevice device,
|
||||
int port, UUID uuid, boolean encrypt) throws IOException {
|
||||
try {
|
||||
BluetoothSocket socket = null;
|
||||
Constructor<BluetoothSocket> constructor =
|
||||
BluetoothSocket.class.getDeclaredConstructor(int.class,
|
||||
int.class, boolean.class, boolean.class,
|
||||
BluetoothDevice.class, int.class, ParcelUuid.class);
|
||||
if(constructor == null)
|
||||
throw new IOException("Can't find socket constructor");
|
||||
|
||||
constructor.setAccessible(true);
|
||||
Field f = BluetoothSocket.class.getDeclaredField("TYPE_RFCOMM");
|
||||
f.setAccessible(true);
|
||||
int typeRfcomm = (Integer) f.get(null);
|
||||
socket = constructor.newInstance(typeRfcomm, -1, false, true,
|
||||
device, port, uuid != null ? new ParcelUuid(uuid) : null);
|
||||
return socket;
|
||||
} catch(NoSuchMethodException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(NoSuchFieldException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(InstantiationException e) {
|
||||
throw new IOException(e.toString());
|
||||
} catch(InvocationTargetException e) {
|
||||
if(e.getCause() instanceof IOException) {
|
||||
throw (IOException) e.getCause();
|
||||
} else {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BluetoothSocket createSocket(BluetoothDevice device, UUID uuid,
|
||||
boolean encrypt) throws IOException {
|
||||
return createSocket(device, -1, uuid, encrypt);
|
||||
}
|
||||
|
||||
static BluetoothSocket createSocket(BluetoothDevice device, int port,
|
||||
boolean encrypt) throws IOException {
|
||||
return createSocket(device, port, null, encrypt);
|
||||
}
|
||||
|
||||
private static class InUseException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -5983642322821496023L;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@ package net.sf.briar.plugins.email;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
|
||||
import android.content.Context;
|
||||
|
||||
public class GmailPluginFactory implements SimplexPluginFactory {
|
||||
|
||||
public SimplexPlugin createPlugin(Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context context,
|
||||
SimplexPluginCallback callback) {
|
||||
return new GmailPlugin(pluginExecutor, callback);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,20 @@ package net.sf.briar.plugins.file;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
import android.content.Context;
|
||||
|
||||
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
||||
|
||||
private static final long POLLING_INTERVAL = 10L * 1000L; // 10 seconds
|
||||
|
||||
public SimplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SimplexPluginCallback callback) {
|
||||
RemovableDriveFinder finder;
|
||||
RemovableDriveMonitor monitor;
|
||||
|
||||
@@ -128,12 +128,12 @@ class SimpleSocketPlugin extends SocketPlugin {
|
||||
throw new IllegalArgumentException();
|
||||
InetSocketAddress i = (InetSocketAddress) s;
|
||||
InetAddress addr = i.getAddress();
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
TransportProperties p = new TransportProperties();
|
||||
if(addr.isLinkLocalAddress() || addr.isSiteLocalAddress())
|
||||
p.put("internal", addr.getHostAddress());
|
||||
else p.put("external", addr.getHostAddress());
|
||||
p.put("port", String.valueOf(i.getPort()));
|
||||
callback.setLocalProperties(p);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
|
||||
public boolean supportsInvitations() {
|
||||
|
||||
@@ -2,16 +2,19 @@ package net.sf.briar.plugins.socket;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
import android.content.Context;
|
||||
|
||||
public class SimpleSocketPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final long POLLING_INTERVAL = 5L * 60L * 1000L; // 5 mins
|
||||
|
||||
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
DuplexPluginCallback callback) {
|
||||
return new SimpleSocketPlugin(pluginExecutor, callback,
|
||||
POLLING_INTERVAL);
|
||||
|
||||
@@ -95,9 +95,9 @@ class TorPlugin implements DuplexPlugin {
|
||||
if(c.containsKey("noHiddenService")) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Not creating hidden service");
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
p.remove("onion");
|
||||
callback.setLocalProperties(p);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("onion", null);
|
||||
callback.mergeLocalProperties(p);
|
||||
return;
|
||||
}
|
||||
// Retrieve the hidden service address, or create one if necessary
|
||||
@@ -107,7 +107,7 @@ class TorPlugin implements DuplexPlugin {
|
||||
if(privateKey == null) {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Creating hidden service address");
|
||||
addr = createHiddenServiceAddress(util, c);
|
||||
addr = createHiddenServiceAddress(util);
|
||||
} else {
|
||||
if(LOG.isLoggable(Level.INFO))
|
||||
LOG.info("Parsing hidden service address");
|
||||
@@ -116,7 +116,7 @@ class TorPlugin implements DuplexPlugin {
|
||||
privateKey, "", false);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
|
||||
addr = createHiddenServiceAddress(util, c);
|
||||
addr = createHiddenServiceAddress(util);
|
||||
}
|
||||
}
|
||||
TorHiddenServicePortPrivateNetAddress addrPort =
|
||||
@@ -141,18 +141,19 @@ class TorPlugin implements DuplexPlugin {
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info("Listening on " + onion);
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
p.put("onion", onion);
|
||||
callback.setLocalProperties(p);
|
||||
callback.mergeLocalProperties(p);
|
||||
acceptContactConnections(ss);
|
||||
}
|
||||
|
||||
private TorHiddenServicePrivateNetAddress createHiddenServiceAddress(
|
||||
TorNetLayerUtil util, TransportConfig c) {
|
||||
TorNetLayerUtil util) {
|
||||
TorHiddenServicePrivateNetAddress addr =
|
||||
util.createNewTorHiddenServicePrivateNetAddress();
|
||||
RSAKeyPair keyPair = addr.getKeyPair();
|
||||
String privateKey = Encryption.getPEMStringFromRSAKeyPair(keyPair);
|
||||
TransportConfig c = new TransportConfig();
|
||||
c.put("privateKey", privateKey);
|
||||
callback.setConfig(c);
|
||||
callback.mergeConfig(c);
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,19 @@ package net.sf.briar.plugins.tor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import net.sf.briar.api.android.AndroidExecutor;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||
import android.content.Context;
|
||||
|
||||
public class TorPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final long POLLING_INTERVAL = 15L * 60L * 1000L; // 15 mins
|
||||
|
||||
public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
DuplexPluginCallback callback) {
|
||||
return new TorPlugin(pluginExecutor, callback, POLLING_INTERVAL);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ConnectionContext;
|
||||
import net.sf.briar.api.transport.ConnectionRecogniser;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
||||
@@ -14,16 +14,16 @@ import java.util.logging.Logger;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.KeyManager;
|
||||
import net.sf.briar.api.db.ContactTransport;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseEvent;
|
||||
import net.sf.briar.api.db.event.DatabaseListener;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ConnectionContext;
|
||||
import net.sf.briar.api.transport.ConnectionRecogniser;
|
||||
import net.sf.briar.api.transport.ContactTransport;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -15,9 +15,9 @@ import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.db.TemporarySecret;
|
||||
import net.sf.briar.api.protocol.TransportId;
|
||||
import net.sf.briar.api.transport.ConnectionContext;
|
||||
import net.sf.briar.api.transport.TemporarySecret;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
/** A connection recogniser for a specific transport. */
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package net.sf.briar.util;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -26,8 +27,8 @@ public class BoundedExecutor implements Executor {
|
||||
public BoundedExecutor(int maxQueued, int minThreads, int maxThreads) {
|
||||
semaphore = new Semaphore(maxQueued + maxThreads);
|
||||
queue = new LinkedBlockingQueue<Runnable>();
|
||||
executor = new ThreadPoolExecutor(minThreads, maxThreads, 60,
|
||||
TimeUnit.SECONDS, queue);
|
||||
executor = new ThreadPoolExecutor(minThreads, maxThreads, 60, SECONDS,
|
||||
queue);
|
||||
}
|
||||
|
||||
public void execute(final Runnable r) {
|
||||
|
||||
@@ -7,6 +7,12 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.CodeSource;
|
||||
|
||||
import org.apache.commons.io.FileSystemUtils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.StatFs;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
@@ -62,7 +68,7 @@ public class FileUtils {
|
||||
* callback is not null it's called once for each file created.
|
||||
*/
|
||||
public static void copyRecursively(File src, File dest, Callback callback)
|
||||
throws IOException {
|
||||
throws IOException {
|
||||
assert dest.exists();
|
||||
assert dest.isDirectory();
|
||||
dest = new File(dest, src.getName());
|
||||
@@ -82,6 +88,20 @@ public class FileUtils {
|
||||
f.delete();
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static long getFreeSpace(File f) throws IOException {
|
||||
if(OsUtils.isAndroid()) {
|
||||
if(Build.VERSION.SDK_INT >= 9) {
|
||||
return f.getUsableSpace();
|
||||
} else {
|
||||
StatFs s = new StatFs(f.getAbsolutePath());
|
||||
return (long) s.getAvailableBlocks() * s.getBlockSize();
|
||||
}
|
||||
} else {
|
||||
return FileSystemUtils.freeSpaceKb(f.getAbsolutePath()) * 1024L;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
||||
void processingFile(File f);
|
||||
|
||||
@@ -4,13 +4,14 @@ public class OsUtils {
|
||||
|
||||
private static final String os = System.getProperty("os.name");
|
||||
private static final String version = System.getProperty("os.version");
|
||||
private static final String vendor = System.getProperty("java.vendor");
|
||||
|
||||
public static boolean isWindows() {
|
||||
return os.indexOf("Windows") != -1;
|
||||
return os != null && os.indexOf("Windows") != -1;
|
||||
}
|
||||
|
||||
public static boolean isMac() {
|
||||
return os.indexOf("Mac OS") != -1;
|
||||
return os != null && os.indexOf("Mac OS") != -1;
|
||||
}
|
||||
|
||||
public static boolean isMacLeopardOrNewer() {
|
||||
@@ -27,6 +28,10 @@ public class OsUtils {
|
||||
}
|
||||
|
||||
public static boolean isLinux() {
|
||||
return os.indexOf("Linux") != -1;
|
||||
return os != null && os.indexOf("Linux") != -1 && !isAndroid();
|
||||
}
|
||||
|
||||
public static boolean isAndroid() {
|
||||
return vendor != null && vendor.indexOf("Android") != -1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user