Updated java.library.path.

This commit is contained in:
akwizgran
2016-11-23 14:58:42 +00:00
parent f6d23b4d1a
commit ad6016d428
1410 changed files with 15690 additions and 12924 deletions

View File

@@ -0,0 +1,20 @@
package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopLifecycleModule extends LifecycleModule {
@Provides
@Singleton
ShutdownManager provideDesktopShutdownManager() {
if (OsUtils.isWindows()) return new WindowsShutdownManagerImpl();
else return new ShutdownManagerImpl();
}
}

View File

@@ -0,0 +1,191 @@
package org.briarproject.bramble.lifecycle;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HINSTANCE;
import com.sun.jna.platform.win32.WinDef.HMENU;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.LPARAM;
import com.sun.jna.platform.win32.WinDef.LRESULT;
import com.sun.jna.platform.win32.WinDef.WPARAM;
import com.sun.jna.platform.win32.WinUser.MSG;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.StdCallLibrary.StdCallCallback;
import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.OsUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static com.sun.jna.Library.OPTION_FUNCTION_MAPPER;
import static com.sun.jna.Library.OPTION_TYPE_MAPPER;
import static java.util.logging.Level.WARNING;
@ThreadSafe
@NotNullByDefault
class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
private static final Logger LOG =
Logger.getLogger(WindowsShutdownManagerImpl.class.getName());
private static final int WM_QUERYENDSESSION = 17;
private static final int GWL_WNDPROC = -4;
private static final int WS_MINIMIZE = 0x20000000;
private final Map<String, Object> options;
private boolean initialised = false; // Locking: lock
WindowsShutdownManagerImpl() {
// Use the Unicode versions of Win32 API calls
Map<String, Object> m = new HashMap<>();
m.put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
m.put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
options = Collections.unmodifiableMap(m);
}
@Override
public int addShutdownHook(Runnable r) {
lock.lock();
try {
if (!initialised) initialise();
return super.addShutdownHook(r);
} finally {
lock.unlock();
}
}
@Override
protected Thread createThread(Runnable r) {
return new StartOnce(r);
}
// Locking: lock
private void initialise() {
if (OsUtils.isWindows()) {
new EventLoop().start();
} else {
LOG.warning("Windows shutdown manager used on non-Windows OS");
}
initialised = true;
}
// Package access for testing
void runShutdownHooks() {
lock.lock();
try {
boolean interrupted = false;
// Start each hook in its own thread
for (Thread hook : hooks.values()) hook.start();
// Wait for all the hooks to finish
for (Thread hook : hooks.values()) {
try {
hook.join();
} catch (InterruptedException e) {
LOG.warning("Interrupted while running shutdown hooks");
interrupted = true;
}
}
if (interrupted) Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
private class EventLoop extends Thread {
private EventLoop() {
super("EventLoop");
}
@Override
public void run() {
try {
// Load user32.dll
final User32 user32 = (User32) Native.loadLibrary("user32",
User32.class, options);
// Create a callback to handle the WM_QUERYENDSESSION message
WindowProc proc = new WindowProc() {
@Override
public LRESULT callback(HWND hwnd, int msg, WPARAM wp,
LPARAM lp) {
if (msg == WM_QUERYENDSESSION) {
// It's safe to delay returning from this message
runShutdownHooks();
}
// Pass the message to the default window procedure
return user32.DefWindowProc(hwnd, msg, wp, lp);
}
};
// Create a native window
HWND hwnd = user32.CreateWindowEx(0, "STATIC", "", WS_MINIMIZE,
0, 0, 0, 0, null, null, null, null);
// Register the callback
try {
// Use SetWindowLongPtr if available (64-bit safe)
user32.SetWindowLongPtr(hwnd, GWL_WNDPROC, proc);
LOG.info("Registered 64-bit callback");
} catch (UnsatisfiedLinkError e) {
// Use SetWindowLong if SetWindowLongPtr isn't available
user32.SetWindowLong(hwnd, GWL_WNDPROC, proc);
LOG.info("Registered 32-bit callback");
}
// Handle events until the window is destroyed
MSG msg = new MSG();
while (user32.GetMessage(msg, null, 0, 0) > 0) {
user32.TranslateMessage(msg);
user32.DispatchMessage(msg);
}
} catch (UnsatisfiedLinkError e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
private static class StartOnce extends Thread {
private final AtomicBoolean called = new AtomicBoolean(false);
private StartOnce(Runnable r) {
super(r, "ShutdownManager");
}
@Override
public void start() {
// Ensure the thread is only started once
if (!called.getAndSet(true)) super.start();
}
}
private interface User32 extends StdCallLibrary {
HWND CreateWindowEx(int styleEx, String className, String windowName,
int style, int x, int y, int width, int height, HWND parent,
HMENU menu, HINSTANCE instance, Pointer param);
LRESULT DefWindowProc(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
LRESULT SetWindowLong(HWND hwnd, int index, WindowProc newProc);
LRESULT SetWindowLongPtr(HWND hwnd, int index, WindowProc newProc);
int GetMessage(MSG msg, HWND hwnd, int filterMin, int filterMax);
boolean TranslateMessage(MSG msg);
LRESULT DispatchMessage(MSG msg);
}
private interface WindowProc extends StdCallCallback {
LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPluginFactory;
import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopPluginModule extends PluginModule {
@Provides
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
SecureRandom random, BackoffFactory backoffFactory,
ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager) {
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(ioExecutor,
random, backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
backoffFactory);
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
backoffFactory, shutdownManager);
SimplexPluginFactory removable =
new RemovableDrivePluginFactory(ioExecutor);
final Collection<SimplexPluginFactory> simplex =
Collections.singletonList(removable);
final Collection<DuplexPluginFactory> duplex =
Arrays.asList(bluetooth, modem, lan, wan);
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@Override
public Collection<DuplexPluginFactory> getDuplexFactories() {
return duplex;
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return simplex;
}
};
return pluginConfig;
}
}

View File

@@ -0,0 +1,537 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.OsUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static javax.bluetooth.DiscoveryAgent.GIAC;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class BluetoothPlugin implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final Semaphore discoverySemaphore = new Semaphore(1);
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile StreamConnectionNotifier socket = null;
private volatile LocalDevice localDevice = null;
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// Initialise the Bluetooth stack
try {
localDevice = LocalDevice.getLocalDevice();
} catch (UnsatisfiedLinkError e) {
// On Linux the user may need to install libbluetooth-dev
if (OsUtils.isLinux())
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
return false;
}
if (LOG.isLoggable(INFO))
LOG.info("Local address " + localDevice.getBluetoothAddress());
running = true;
bind();
return true;
}
private void bind() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
callback.mergeLocalProperties(p);
// Bind a server socket to accept connections from contacts
String url = makeUrl("localhost", getUuid());
StreamConnectionNotifier ss;
try {
ss = (StreamConnectionNotifier) Connector.open(url);
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return;
}
if (!running) {
tryToClose(ss);
return;
}
socket = ss;
backoff.reset();
callback.transportEnabled();
acceptContactConnections(ss);
}
});
}
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
private String getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return uuid;
}
private void tryToClose(@Nullable StreamConnectionNotifier ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections(StreamConnectionNotifier ss) {
while (true) {
StreamConnection s;
try {
s = ss.acceptAndOpen();
} catch (IOException e) {
// This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
return;
}
backoff.reset();
callback.incomingConnectionCreated(wrapSocket(s));
if (!running) return;
}
}
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new BluetoothTransportConnection(this, s);
}
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(final Collection<ContactId> connected) {
if (!running) return;
backoff.increment();
// 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(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
});
}
}
private StreamConnection connect(String url) {
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + url);
try {
StreamConnection s = (StreamConnection) Connector.open(url);
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + url);
return s;
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + url);
return null;
}
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
String url = makeUrl(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
if (!running) return null;
// Use the invitation codes to generate the UUID
byte[] b = r.nextBytes(UUID_BYTES);
String uuid = UUID.nameUUIDFromBytes(b).toString();
String url = makeUrl("localhost", uuid);
// Make the device discoverable if possible
makeDeviceDiscoverable();
// Bind a server socket for receiving invitation connections
final StreamConnectionNotifier ss;
try {
ss = (StreamConnectionNotifier) Connector.open(url);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
if (!running) {
tryToClose(ss);
return null;
}
// Create the background tasks
CompletionService<StreamConnection> complete =
new ExecutorCompletionService<>(ioExecutor);
List<Future<StreamConnection>> futures = new ArrayList<>();
if (alice) {
// Return the first connected socket
futures.add(complete.submit(new ListeningTask(ss)));
futures.add(complete.submit(new DiscoveryTask(uuid)));
} else {
// Return the first socket with readable data
futures.add(complete.submit(new ReadableTask(
new ListeningTask(ss))));
futures.add(complete.submit(new ReadableTask(
new DiscoveryTask(uuid))));
}
StreamConnection chosen = null;
try {
Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS);
if (f == null) return null; // No task completed within the timeout
chosen = f.get();
return new BluetoothTransportConnection(this, chosen);
} catch (InterruptedException e) {
LOG.info("Interrupted while exchanging invitations");
Thread.currentThread().interrupt();
return null;
} catch (ExecutionException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} finally {
// Closing the socket will terminate the listener task
tryToClose(ss);
closeSockets(futures, chosen);
}
}
private void closeSockets(final List<Future<StreamConnection>> futures,
@Nullable final StreamConnection chosen) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
for (Future<StreamConnection> f : futures) {
try {
if (f.cancel(true)) {
LOG.info("Cancelled task");
} else {
StreamConnection s = f.get();
if (s != null && s != chosen) {
LOG.info("Closing unwanted socket");
s.close();
}
}
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
return;
} catch (ExecutionException | IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!running) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
String url = makeUrl("localhost", uuid);
// Make the device discoverable if possible
makeDeviceDiscoverable();
// Bind a server socket for receiving invitation connections
final StreamConnectionNotifier ss;
try {
ss = (StreamConnectionNotifier) Connector.open(url);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
if (!running) {
tryToClose(ss);
return null;
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
String address = localDevice.getBluetoothAddress();
descriptor.add(StringUtils.macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
String url = makeUrl(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
private void makeDeviceDiscoverable() {
// Try to make the device discoverable (requires root on Linux)
try {
localDevice.setDiscoverable(GIAC);
} catch (BluetoothStateException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private class DiscoveryTask implements Callable<StreamConnection> {
private final String uuid;
private DiscoveryTask(String uuid) {
this.uuid = uuid;
}
@Override
public StreamConnection call() throws Exception {
// Repeat discovery until we connect or get interrupted
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
while (true) {
if (!discoverySemaphore.tryAcquire())
throw new Exception("Discovery is already in progress");
try {
InvitationListener listener =
new InvitationListener(discoveryAgent, uuid);
discoveryAgent.startInquiry(GIAC, listener);
String url = listener.waitForUrl();
if (url != null) {
StreamConnection s = connect(url);
if (s != null) {
LOG.info("Outgoing connection");
return s;
}
}
} finally {
discoverySemaphore.release();
}
}
}
}
private static class ListeningTask implements Callable<StreamConnection> {
private final StreamConnectionNotifier serverSocket;
private ListeningTask(StreamConnectionNotifier serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public StreamConnection call() throws Exception {
StreamConnection s = serverSocket.acceptAndOpen();
LOG.info("Incoming connection");
return s;
}
}
private static class ReadableTask implements Callable<StreamConnection> {
private final Callable<StreamConnection> connectionTask;
private ReadableTask(Callable<StreamConnection> connectionTask) {
this.connectionTask = connectionTask;
}
@Override
public StreamConnection call() throws Exception {
StreamConnection s = connectionTask.call();
InputStream in = s.openInputStream();
while (in.available() == 0) {
LOG.info("Waiting for data");
Thread.sleep(1000);
}
LOG.info("Data available");
return s;
}
}
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
private final StreamConnectionNotifier ss;
private BluetoothKeyAgreementListener(BdfList descriptor,
StreamConnectionNotifier ss) {
super(descriptor);
this.ss = ss;
}
@Override
public Callable<KeyAgreementConnection> listen() {
return new Callable<KeyAgreementConnection>() {
@Override
public KeyAgreementConnection call() throws Exception {
StreamConnection s = ss.acceptAndOpen();
if (LOG.isLoggable(INFO))
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new BluetoothTransportConnection(
BluetoothPlugin.this, s), ID);
}
};
}
@Override
public void close() {
try {
ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
}

View File

@@ -0,0 +1,55 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@Immutable
@NotNullByDefault
public class BluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final BackoffFactory backoffFactory;
public BluetoothPluginFactory(Executor ioExecutor,
SecureRandom secureRandom, BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoffFactory = backoffFactory;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback,
MAX_LATENCY);
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.io.StreamConnection;
@NotNullByDefault
class BluetoothTransportConnection extends AbstractDuplexTransportConnection {
private final StreamConnection stream;
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
super(plugin);
this.stream = stream;
}
@Override
protected InputStream getInputStream() throws IOException {
return stream.openInputStream();
}
@Override
protected OutputStream getOutputStream() throws IOException {
return stream.openOutputStream();
}
@Override
protected void closeConnection(boolean exception) throws IOException {
stream.close();
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.bramble.plugin.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.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;
import static java.util.logging.Level.WARNING;
class InvitationListener implements DiscoveryListener {
private static final Logger LOG =
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) {
this.discoveryAgent = discoveryAgent;
this.uuid = uuid;
}
String waitForUrl() throws InterruptedException {
finished.await();
return url;
}
@Override
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
UUID[] uuids = new UUID[] {new UUID(uuid, false)};
// Try to discover the services associated with the UUID
try {
discoveryAgent.searchServices(null, uuids, device, this);
searches.incrementAndGet();
} catch (BluetoothStateException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
for (ServiceRecord record : services) {
// 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<>();
findNestedClassIds(record.getAttributeValue(0x1), uuids);
for (String u : uuids) {
if (uuid.equalsIgnoreCase(u)) {
// The UUID matches - store the URL
url = serviceUrl;
finished.countDown();
return;
}
}
}
}
@Override
public void inquiryCompleted(int discoveryType) {
if (searches.decrementAndGet() == 0) finished.countDown();
}
@Override
public void serviceSearchCompleted(int transaction, int response) {
if (searches.decrementAndGet() == 0) finished.countDown();
}
// UUIDs are sometimes buried in nested data elements
private void findNestedClassIds(Object o, Collection<String> ids) {
o = getDataElementValue(o);
if (o instanceof Enumeration<?>) {
for (Object o1 : Collections.list((Enumeration<?>) o))
findNestedClassIds(o1, ids);
} else if (o instanceof UUID) {
ids.add(o.toString());
}
}
private Object getDataElementValue(Object o) {
if (o instanceof DataElement) {
// Bluecove throws an exception if the type is unknown
try {
return ((DataElement) o).getValue();
} catch (ClassCastException e) {
return null;
}
}
return null;
}
}

View File

@@ -0,0 +1,28 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
class LinuxRemovableDriveFinder extends UnixRemovableDriveFinder {
@Override
protected String getMountCommand() {
return "/bin/mount";
}
@Override
@Nullable
protected String parseMountPoint(String line) {
// The format is "/dev/foo on /bar/baz type bam (opt1,opt2)"
String pattern = "^/dev/[^ ]+ on (.*) type [^ ]+ \\([^)]+\\)$";
String path = line.replaceFirst(pattern, "$1");
return path.equals(line) ? null : path;
}
@Override
protected boolean isRemovableDriveMountPoint(String path) {
return path.startsWith("/mnt/") || path.startsWith("/media/");
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class LinuxRemovableDriveMonitor extends UnixRemovableDriveMonitor {
@Override
protected String[] getPathsToWatch() {
return new String[] {"/mnt", "/media"};
}
}

View File

@@ -0,0 +1,28 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
class MacRemovableDriveFinder extends UnixRemovableDriveFinder {
@Override
protected String getMountCommand() {
return "/sbin/mount";
}
@Override
@Nullable
protected String parseMountPoint(String line) {
// The format is "/dev/foo on /bar/baz (opt1, opt2)"
String pattern = "^/dev/[^ ]+ on (.*) \\([^)]+\\)$";
String path = line.replaceFirst(pattern, "$1");
return path.equals(line) ? null : path;
}
@Override
protected boolean isRemovableDriveMountPoint(String path) {
return path.startsWith("/Volumes/");
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class MacRemovableDriveMonitor extends UnixRemovableDriveMonitor {
@Override
protected String[] getPathsToWatch() {
return new String[] {"/Volumes"};
}
}

View File

@@ -0,0 +1,84 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
private static final Logger LOG =
Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
private final Executor ioExecutor;
private final RemovableDriveFinder finder;
private final int pollingInterval;
private final Lock pollingLock = new ReentrantLock();
private final Condition stopPolling = pollingLock.newCondition();
private volatile boolean running = false;
private volatile Callback callback = null;
PollingRemovableDriveMonitor(Executor ioExecutor,
RemovableDriveFinder finder, int pollingInterval) {
this.ioExecutor = ioExecutor;
this.finder = finder;
this.pollingInterval = pollingInterval;
}
@Override
public void start(Callback callback) throws IOException {
this.callback = callback;
running = true;
ioExecutor.execute(this);
}
@Override
public void stop() throws IOException {
running = false;
pollingLock.lock();
try {
stopPolling.signalAll();
} finally {
pollingLock.unlock();
}
}
@Override
public void run() {
try {
Collection<File> drives = finder.findRemovableDrives();
while (running) {
pollingLock.lock();
try {
stopPolling.await(pollingInterval, MILLISECONDS);
} finally {
pollingLock.unlock();
}
if (!running) return;
Collection<File> newDrives = finder.findRemovableDrives();
for (File f : newDrives) {
if (!drives.contains(f)) callback.driveInserted(f);
}
drives = newDrives;
}
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting to poll");
Thread.currentThread().interrupt();
} catch (IOException e) {
callback.exceptionThrown(e);
}
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
@NotNullByDefault
interface RemovableDriveFinder {
Collection<File> findRemovableDrives() throws IOException;
}

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
@NotNullByDefault
interface RemovableDriveMonitor {
void start(Callback c) throws IOException;
void stop() throws IOException;
interface Callback {
void driveInserted(File root);
void exceptionThrown(IOException e);
}
}

View File

@@ -0,0 +1,132 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
@NotNullByDefault
class RemovableDrivePlugin extends FilePlugin
implements RemovableDriveMonitor.Callback {
static final TransportId ID =
new TransportId("org.briarproject.bramble.file");
private static final Logger LOG =
Logger.getLogger(RemovableDrivePlugin.class.getName());
private final RemovableDriveFinder finder;
private final RemovableDriveMonitor monitor;
RemovableDrivePlugin(Executor ioExecutor, SimplexPluginCallback callback,
RemovableDriveFinder finder, RemovableDriveMonitor monitor,
int maxLatency) {
super(ioExecutor, callback, maxLatency);
this.finder = finder;
this.monitor = monitor;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
monitor.start(this);
return true;
}
@Override
public void stop() throws IOException {
running = false;
monitor.stop();
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(Collection<ContactId> connected) {
throw new UnsupportedOperationException();
}
@Override
protected File chooseOutputDirectory() {
try {
List<File> drives = new ArrayList<>(finder.findRemovableDrives());
if (drives.isEmpty()) return null;
String[] paths = new String[drives.size()];
for (int i = 0; i < paths.length; i++) {
paths[i] = drives.get(i).getPath();
}
int i = callback.showChoice(paths, "REMOVABLE_DRIVE_CHOOSE_DRIVE");
if (i == -1) return null;
return drives.get(i);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
@Override
protected void readerFinished(File f) {
callback.showMessage("REMOVABLE_DRIVE_READ_FINISHED");
}
@Override
protected void writerFinished(File f) {
callback.showMessage("REMOVABLE_DRIVE_WRITE_FINISHED");
}
@Override
protected Collection<File> findFilesByName(String filename) {
List<File> matches = new ArrayList<>();
try {
for (File drive : finder.findRemovableDrives()) {
File[] files = drive.listFiles();
if (files != null) {
for (File f : files) {
if (f.isFile() && filename.equals(f.getName()))
matches.add(f);
}
}
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
return matches;
}
@Override
public void driveInserted(File root) {
File[] files = root.listFiles();
if (files != null) {
for (File f : files) if (f.isFile()) createReaderFromFile(f);
}
}
@Override
public void exceptionThrown(IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.util.OsUtils;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
private static final int MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
private static final int POLLING_INTERVAL = 10 * 1000; // 10 seconds
private final Executor ioExecutor;
public RemovableDrivePluginFactory(Executor ioExecutor) {
this.ioExecutor = ioExecutor;
}
@Override
public TransportId getId() {
return RemovableDrivePlugin.ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Override
public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
RemovableDriveFinder finder;
RemovableDriveMonitor monitor;
if (OsUtils.isLinux()) {
finder = new LinuxRemovableDriveFinder();
monitor = new LinuxRemovableDriveMonitor();
} else if (OsUtils.isMacLeopardOrNewer()) {
finder = new MacRemovableDriveFinder();
monitor = new MacRemovableDriveMonitor();
} else if (OsUtils.isMac()) {
// JNotify requires OS X 10.5 or newer, so we have to poll
finder = new MacRemovableDriveFinder();
monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
POLLING_INTERVAL);
} else if (OsUtils.isWindows()) {
finder = new WindowsRemovableDriveFinder();
monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
POLLING_INTERVAL);
} else {
return null;
}
return new RemovableDrivePlugin(ioExecutor, callback, finder, monitor,
MAX_LATENCY);
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import javax.annotation.Nullable;
@NotNullByDefault
abstract class UnixRemovableDriveFinder implements RemovableDriveFinder {
protected abstract String getMountCommand();
@Nullable
protected abstract String parseMountPoint(String line);
protected abstract boolean isRemovableDriveMountPoint(String path);
@Override
public List<File> findRemovableDrives() throws IOException {
List<File> drives = new ArrayList<>();
Process p = new ProcessBuilder(getMountCommand()).start();
Scanner s = new Scanner(p.getInputStream(), "UTF-8");
try {
while (s.hasNextLine()) {
String line = s.nextLine();
String[] tokens = line.split(" ");
if (tokens.length < 3) continue;
// The general format is "/dev/foo on /bar/baz ..."
if (tokens[0].startsWith("/dev/") && tokens[1].equals("on")) {
// The path may contain spaces so we can't use tokens[2]
String path = parseMountPoint(line);
if (path != null && isRemovableDriveMountPoint(path)) {
File f = new File(path);
if (f.exists() && f.isDirectory()) drives.add(f);
}
}
}
} finally {
s.close();
}
return drives;
}
}

View File

@@ -0,0 +1,128 @@
package org.briarproject.bramble.plugin.file;
import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
JNotifyListener {
//TODO: rationalise this in a further refactor
private static final Lock staticLock = new ReentrantLock();
// The following are locking: staticLock
private static boolean triedLoad = false;
private static Throwable loadError = null;
private final Lock lock = new ReentrantLock();
// The following are locking: lock
private final List<Integer> watches = new ArrayList<>();
private boolean started = false;
private Callback callback = null;
protected abstract String[] getPathsToWatch();
private static Throwable tryLoad() {
try {
Class.forName("net.contentobjects.jnotify.JNotify");
return null;
} catch (UnsatisfiedLinkError e) {
return e;
} catch (ClassNotFoundException e) {
return e;
}
}
private static void checkEnabled() throws IOException {
staticLock.lock();
try {
if (!triedLoad) {
loadError = tryLoad();
triedLoad = true;
}
if (loadError != null) throw new IOException(loadError.toString());
} finally {
staticLock.unlock();
}
}
@Override
public void start(Callback callback) throws IOException {
checkEnabled();
List<Integer> watches = new ArrayList<>();
int mask = JNotify.FILE_CREATED;
for (String path : getPathsToWatch()) {
if (new File(path).exists())
watches.add(JNotify.addWatch(path, mask, false, this));
}
lock.lock();
try {
if (started) throw new AssertionError();
if (this.callback != null) throw new AssertionError();
started = true;
this.callback = callback;
this.watches.addAll(watches);
} finally {
lock.unlock();
}
}
@Override
public void stop() throws IOException {
checkEnabled();
List<Integer> watches;
lock.lock();
try {
if (!started) throw new AssertionError();
if (callback == null) throw new AssertionError();
started = false;
callback = null;
watches = new ArrayList<>(this.watches);
this.watches.clear();
} finally {
lock.unlock();
}
for (Integer w : watches) JNotify.removeWatch(w);
}
@Override
public void fileCreated(int wd, String rootPath, String name) {
Callback callback;
lock.lock();
try {
callback = this.callback;
} finally {
lock.unlock();
}
if (callback != null)
callback.driveInserted(new File(rootPath + "/" + name));
}
@Override
public void fileDeleted(int wd, String rootPath, String name) {
throw new UnsupportedOperationException();
}
@Override
public void fileModified(int wd, String rootPath, String name) {
throw new UnsupportedOperationException();
}
@Override
public void fileRenamed(int wd, String rootPath, String oldName,
String newName) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.plugin.file;
import com.sun.jna.platform.win32.Kernel32;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@NotNullByDefault
class WindowsRemovableDriveFinder implements RemovableDriveFinder {
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364939.aspx
private static final int DRIVE_REMOVABLE = 2;
@Override
public Collection<File> findRemovableDrives() throws IOException {
File[] roots = File.listRoots();
if (roots == null) throw new IOException();
List<File> drives = new ArrayList<>();
for (File root : roots) {
try {
int type = Kernel32.INSTANCE.GetDriveType(root.getPath());
if (type == DRIVE_REMOVABLE) drives.add(root);
} catch (RuntimeException e) {
throw new IOException(e);
}
}
return drives;
}
}

View File

@@ -0,0 +1,285 @@
package org.briarproject.bramble.plugin.modem;
import java.util.HashMap;
import java.util.Map;
class CountryCodes {
private static final Country[] COUNTRIES = {
new Country("AD", "Andorra", "376", "00", ""),
new Country("AE", "United Arab Emirates", "971", "00", "0"),
new Country("AF", "Afghanistan", "93", "00", "0"),
new Country("AG", "Antigua and Barbuda", "1", "011", "1"),
new Country("AI", "Anguilla", "1", "011", "1"),
new Country("AL", "Albania", "355", "00", "0"),
new Country("AM", "Armenia", "374", "00", "8"),
new Country("AN", "Netherlands Antilles", "599", "00", "0"),
new Country("AO", "Angola", "244", "00", "0"),
new Country("AQ", "Antarctica", "672", "", ""),
new Country("AR", "Argentina", "54", "00", "0"),
new Country("AS", "American Samoa", "1", "011", "1"),
new Country("AT", "Austria", "43", "00", "0"),
new Country("AU", "Australia", "61", "0011", "0"),
new Country("AW", "Aruba", "297", "00", ""),
new Country("AZ", "Azerbaijan", "994", "00", "8"),
new Country("BA", "Bosnia and Herzegovina", "387", "00", "0"),
new Country("BB", "Barbados", "1", "011", "1"),
new Country("BD", "Bangladesh", "880", "00", "0"),
new Country("BE", "Belgium", "32", "00", "0"),
new Country("BF", "Burkina Faso", "226", "00", ""),
new Country("BG", "Bulgaria", "359", "00", "0"),
new Country("BH", "Bahrain", "973", "00", ""),
new Country("BI", "Burundi", "257", "00", ""),
new Country("BJ", "Benin", "229", "00", ""),
new Country("BM", "Bermuda", "1", "011", "1"),
new Country("BN", "Brunei Darussalam", "673", "00", "0"),
new Country("BO", "Bolivia", "591", "00", "0"),
new Country("BR", "Brazil", "55", "00", "0"),
new Country("BS", "Bahamas", "1", "011", "1"),
new Country("BT", "Bhutan", "975", "00", ""),
new Country("BV", "Bouvet Island (Norway)", "47", "00", ""),
new Country("BW", "Botswana", "267", "00", ""),
new Country("BY", "Belarus", "375", "8**10", "8"),
new Country("BZ", "Belize", "501", "00", "0"),
new Country("CA", "Canada", "1", "011", "1"),
new Country("CC", "Cocos (Keeling) Islands", "61", "0011", "0"),
new Country("CD", "Congo (Republic)", "243", "00", ""),
new Country("CF", "Central African Republic", "236", "00", ""),
new Country("CG", "Congo (Democratic Republic)", "242", "00", "0"),
new Country("CH", "Switzerland", "41", "00", "0"),
new Country("CI", "Cote D'Ivoire", "225", "00", "0"),
new Country("CK", "Cook Islands", "682", "00", "00"),
new Country("CL", "Chile", "56", "00", "0"),
new Country("CM", "Cameroon", "237", "00", ""),
new Country("CN", "China", "86", "00", "0"),
new Country("CO", "Colombia", "57", "009", "09"),
new Country("CR", "Costa Rica", "506", "00", ""),
new Country("CU", "Cuba", "53", "119", "0"),
new Country("CV", "Cape Verde Islands", "238", "0", ""),
new Country("CX", "Christmas Island", "61", "0011", "0"),
new Country("CY", "Cyprus", "357", "00", ""),
new Country("CZ", "Czech Republic", "420", "00", ""),
new Country("DE", "Germany", "49", "00", "0"),
new Country("DJ", "Djibouti", "253", "00", ""),
new Country("DK", "Denmark", "45", "00", ""),
new Country("DM", "Dominica", "1", "011", "1"),
new Country("DO", "Dominican Republic", "1", "011", "1"),
new Country("DZ", "Algeria", "213", "00", "7"),
new Country("EC", "Ecuador", "593", "00", "0"),
new Country("EE", "Estonia", "372", "00", ""),
new Country("EG", "Egypt", "20", "00", "0"),
new Country("EH", "Western Sahara", "212", "00", "0"),
new Country("ER", "Eritrea", "291", "00", "0"),
new Country("ES", "Spain", "34", "00", ""),
new Country("ET", "Ethiopia", "251", "00", "0"),
new Country("FI", "Finland", "358", "00", "0"),
new Country("FJ", "Fiji", "679", "00", ""),
new Country("FK", "Falkland Islands (Malvinas)", "500", "00", ""),
new Country("FM", "Micronesia", "691", "011", "1"),
new Country("FO", "Faroe Islands", "298", "00", ""),
new Country("FR", "France", "33", "00", ""),
new Country("GA", "Gabonese Republic", "241", "00", ""),
new Country("GB", "United Kingdom", "44", "00", "0"),
new Country("GD", "Grenada", "1", "011", "4"),
new Country("GE", "Georgia", "995", "8**10", "8"),
new Country("GF", "French Guiana", "594", "00", ""),
new Country("GH", "Ghana", "233", "00", ""),
new Country("GI", "Gibraltar", "350", "00", ""),
new Country("GL", "Greenland", "299", "00", ""),
new Country("GM", "Gambia", "220", "00", ""),
new Country("GN", "Guinea", "224", "00", "0"),
new Country("GP", "Guadeloupe", "590", "00", ""),
new Country("GQ", "Equatorial Guinea", "240", "00", ""),
new Country("GR", "Greece", "30", "00", ""),
new Country("GS", "South Georgia and the South Sandwich Islands", "995", "8**10", "8"),
new Country("GT", "Guatemala", "502", "00", ""),
new Country("GU", "Guam", "1", "011", "1"),
new Country("GW", "Guinea-Bissau", "245", "00", ""),
new Country("GY", "Guyana", "592", "001", "0"),
new Country("HK", "Hong Kong", "852", "001", ""),
new Country("HM", "Heard Island and McDonald Islands", "692", "00", "0"),
new Country("HN", "Honduras", "504", "00", "0"),
new Country("HR", "Croatia", "385", "00", "0"),
new Country("HT", "Haiti", "509", "00", "0"),
new Country("HU", "Hungary", "36", "00", "06"),
new Country("ID", "Indonesia", "62", "001", "0"),
new Country("IE", "Ireland", "353", "00", "0"),
new Country("IL", "Israel", "972", "00", "0"),
new Country("IN", "India", "91", "00", "0"),
new Country("IO", "British Indian Ocean Territory", "246", "00", ""),
new Country("IQ", "Iraq", "964", "00", "0"),
new Country("IR", "Iran", "98", "00", "0"),
new Country("IS", "Iceland", "354", "00", "0"),
new Country("IT", "Italy", "39", "00", ""),
new Country("JM", "Jamaica", "1", "011", "1"),
new Country("JO", "Jordan", "962", "00", "0"),
new Country("JP", "Japan", "81", "001", "0"),
new Country("KE", "Kenya", "254", "000", "0"),
new Country("KG", "Kyrgyzstan", "996", "00", "0"),
new Country("KH", "Cambodia", "855", "001", "0"),
new Country("KI", "Kiribati", "686", "00", "0"),
new Country("KM", "Comoros", "269", "00", ""),
new Country("KN", "Saint Kitts and Nevis", "1", "011", "1"),
new Country("KP", "Korea (North)", "850", "00", "0"),
new Country("KR", "Korea (South)", "82", "001", "0"),
new Country("KW", "Kuwait", "965", "00", "0"),
new Country("KY", "Cayman Islands", "1", "011", "1"),
new Country("KZ", "Kazakhstan", "7", "8**10", "8"),
new Country("LA", "Laos", "856", "00", "0"),
new Country("LB", "Lebanon", "961", "00", "0"),
new Country("LC", "Saint Lucia", "1", "011", "1"),
new Country("LI", "Liechtenstein", "423", "00", ""),
new Country("LK", "Sri Lanka", "94", "00", "0"),
new Country("LR", "Liberia", "231", "00", "22"),
new Country("LS", "Lesotho", "266", "00", "0"),
new Country("LT", "Lithuania", "370", "00", "8"),
new Country("LU", "Luxembourg", "352", "00", ""),
new Country("LV", "Latvia", "371", "00", "8"),
new Country("LY", "Libya", "218", "00", "0"),
new Country("MA", "Morocco", "212", "00", ""),
new Country("MC", "Monaco", "377", "00", "0"),
new Country("MD", "Moldova", "373", "00", "0"),
new Country("ME", "Montenegro", "382", "99", "0"),
new Country("MG", "Madagascar", "261", "00", "0"),
new Country("MH", "Marshall Islands", "692", "011", "1"),
new Country("MK", "Macedonia", "389", "00", "0"),
new Country("ML", "Mali", "223", "00", "0"),
new Country("MM", "Myanmar", "95", "00", ""),
new Country("MN", "Mongolia", "976", "001", "0"),
new Country("MO", "Macao", "853", "00", "0"),
new Country("MP", "Northern Mariana Islands", "1", "011", "1"),
new Country("MQ", "Martinique", "596", "00", "0"),
new Country("MR", "Mauritania", "222", "00", "0"),
new Country("MS", "Montserrat", "1", "011", "1"),
new Country("MT", "Malta", "356", "00", "21"),
new Country("MU", "Mauritius", "230", "00", "0"),
new Country("MV", "Maldives", "960", "00", "0"),
new Country("MW", "Malawi", "265", "00", ""),
new Country("MX", "Mexico", "52", "00", "01"),
new Country("MY", "Malaysia", "60", "00", "0"),
new Country("MZ", "Mozambique", "258", "00", "0"),
new Country("NA", "Namibia", "264", "00", "0"),
new Country("NC", "New Caledonia", "687", "00", "0"),
new Country("NE", "Niger", "227", "00", "0"),
new Country("NF", "Norfolk Island", "672", "00", ""),
new Country("NG", "Nigeria", "234", "009", "0"),
new Country("NI", "Nicaragua", "505", "00", "0"),
new Country("NL", "Netherlands", "31", "00", "0"),
new Country("NO", "Norway", "47", "00", ""),
new Country("NP", "Nepal", "977", "00", "0"),
new Country("NR", "Nauru", "674", "00", "0"),
new Country("NU", "Niue", "683", "00", "0"),
new Country("NZ", "New Zealand", "64", "00", "0"),
new Country("OM", "Oman", "968", "00", "0"),
new Country("PA", "Panama", "507", "00", "0"),
new Country("PE", "Peru", "51", "00", "0"),
new Country("PF", "French Polynesia", "689", "00", ""),
new Country("PG", "Papua New Guinea", "675", "05", ""),
new Country("PH", "Philippines", "63", "00", "0"),
new Country("PK", "Pakistan", "92", "00", "0"),
new Country("PL", "Poland", "48", "00", "0"),
new Country("PM", "Saint Pierre and Miquelon", "508", "00", "0"),
new Country("PN", "Pitcairn", "872", "", ""),
new Country("PR", "Puerto Rico", "1", "011", "1"),
new Country("PS", "Palestine", "970", "00", "0"),
new Country("PT", "Portugal", "351", "00", ""),
new Country("PW", "Palau", "680", "011", ""),
new Country("PY", "Paraguay", "595", "002", "0"),
new Country("QA", "Qatar", "974", "00", "0"),
new Country("RE", "Reunion", "262", "00", "0"),
new Country("RO", "Romania", "40", "00", "0"),
new Country("RS", "Serbia", "381", "99", "0"),
new Country("RU", "Russia", "7", "8**10", "8"),
new Country("RW", "Rwanda", "250", "00", "0"),
new Country("SA", "Saudi Arabia", "966", "00", "0"),
new Country("SB", "Solomon Islands", "677", "00", ""),
new Country("SC", "Seychelles", "248", "00", "0"),
new Country("SD", "Sudan", "249", "00", "0"),
new Country("SE", "Sweden", "46", "00", "0"),
new Country("SG", "Singapore", "65", "001", ""),
new Country("SH", "Saint Helena", "290", "00", ""),
new Country("SI", "Slovenia", "386", "00", "0"),
new Country("SJ", "Svalbard and Jan Mayen", "378", "00", "0"),
new Country("SK", "Slovakia", "421", "00", "0"),
new Country("SL", "Sierra Leone", "232", "00", "0"),
new Country("SM", "San Marino", "378", "00", "0"),
new Country("SN", "Senegal", "221", "00", "0"),
new Country("SO", "Somalia", "252", "00", ""),
new Country("SR", "Suriname", "597", "00", ""),
new Country("ST", "Sao Tome and Principe", "239", "00", "0"),
new Country("SV", "El Salvador", "503", "00", ""),
new Country("SY", "Syria", "963", "00", "0"),
new Country("SZ", "Swaziland", "268", "00", ""),
new Country("TC", "Turks and Caicos Islands", "1", "011", "1"),
new Country("TD", "Chad", "235", "15", ""),
new Country("TF", "French Southern Territories", "596", "00", "0"),
new Country("TG", "Togo", "228", "00", ""),
new Country("TH", "Thailand", "66", "001", "0"),
new Country("TJ", "Tajikistan", "992", "8**10", "8"),
new Country("TK", "Tokelau", "690", "00", ""),
new Country("TL", "Timor-Leste", "670", "00", ""),
new Country("TM", "Turkmenistan", "993", "8**10", "8"),
new Country("TN", "Tunisia", "216", "00", "0"),
new Country("TO", "Tonga Islands", "676", "00", ""),
new Country("TR", "Turkey", "90", "00", "0"),
new Country("TT", "Trinidad and Tobago", "1", "011", "1"),
new Country("TV", "Tuvalu", "688", "00", ""),
new Country("TW", "Taiwan", "886", "002", ""),
new Country("TZ", "Tanzania", "255", "000", "0"),
new Country("UA", "Ukraine", "380", "8**10", "8"),
new Country("UG", "Uganda", "256", "000", "0"),
new Country("US", "United States", "1", "011", "1"),
new Country("UY", "Uruguay", "598", "00", "0"),
new Country("UZ", "Uzbekistan", "998", "8**10", "8"),
new Country("VA", "Holy See (Vatican City State)", "379", "00", ""),
new Country("VC", "Saint Vincent and the Grenadines", "1", "011", "1"),
new Country("VE", "Venezuela", "58", "00", "0"),
new Country("VG", "Virgin Islands (British)", "1", "011", "1"),
new Country("VI", "Virgin Islands (U.S.)", "1", "011", "1"),
new Country("VN", "Viet Nam", "84", "00", "0"),
new Country("VU", "Vanuatu", "678", "00", ""),
new Country("WF", "Wallis and Futuna Islands", "681", "19", ""),
new Country("WS", "Samoa (Western)", "685", "0", "0"),
new Country("YE", "Yemen", "967", "00", "0"),
new Country("YT", "Mayotte", "269", "00", ""),
new Country("ZA", "South Africa", "27", "09", "0"),
new Country("ZM", "Zambia", "260", "00", "0"),
new Country("ZW", "Zimbabwe", "263", "110", "0")
};
private static final Map<String, Country> COUNTRY_MAP = new HashMap<>();
static {
for (Country c : COUNTRIES) COUNTRY_MAP.put(c.iso3166, c);
}
static String translate(String number, String callerIso, String calleeIso) {
Country from = COUNTRY_MAP.get(callerIso);
Country to = COUNTRY_MAP.get(calleeIso);
if (from == null || to == null) return null;
// Strip any prefixes and country codes from the number
String plusCountryCode = "+" + to.countryCode;
String iddCountryCode = to.idd + to.countryCode;
if (number.startsWith(plusCountryCode))
number = number.substring(plusCountryCode.length());
else if (number.startsWith(iddCountryCode))
number = number.substring(iddCountryCode.length());
else if (number.startsWith(to.ndd))
number = number.substring(to.ndd.length());
if (from == to) return from.ndd + number; // National
return from.idd + to.countryCode + number; // International
}
private static class Country {
private final String iso3166, countryCode, idd, ndd;
private Country(String iso3166, String englishName, String countryCode,
String idd, String ndd) {
this.iso3166 = iso3166;
this.countryCode = countryCode;
this.idd = idd;
this.ndd = ndd;
}
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A modem that can be used for multiple sequential incoming and outgoing
* calls. If an exception is thrown, a new modem instance must be created.
*/
@NotNullByDefault
interface Modem {
/**
* Call this method after creating the modem and before making any calls.
* If this method returns false the modem cannot be used.
*/
boolean start() throws IOException;
/**
* Call this method when the modem is no longer needed. If a call is in
* progress it will be terminated.
*/
void stop() throws IOException;
/**
* Initiates an outgoing call and returns true if the call connects. If the
* call does not connect the modem is hung up.
*/
boolean dial(String number) throws IOException;
/** Returns a stream for reading from the currently connected call. */
InputStream getInputStream() throws IOException;
/** Returns a stream for writing to the currently connected call. */
OutputStream getOutputStream() throws IOException;
/** Hangs up the modem, ending the currently connected call. */
void hangUp() throws IOException;
interface Callback {
/** Called when an incoming call connects. */
void incomingCallConnected();
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface ModemFactory {
Modem createModem(Modem.Callback callback, String portName);
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class ModemFactoryImpl implements ModemFactory {
private final Executor ioExecutor;
private final ReliabilityLayerFactory reliabilityFactory;
private final Clock clock;
ModemFactoryImpl(Executor ioExecutor,
ReliabilityLayerFactory reliabilityFactory) {
this.ioExecutor = ioExecutor;
this.reliabilityFactory = reliabilityFactory;
clock = new SystemClock();
}
@Override
public Modem createModem(Modem.Callback callback, String portName) {
return new ModemImpl(ioExecutor, reliabilityFactory, clock, callback,
new SerialPortImpl(portName));
}
}

View File

@@ -0,0 +1,467 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.reliability.ReliabilityLayer;
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
import org.briarproject.bramble.api.reliability.WriteHandler;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static jssc.SerialPort.PURGE_RXCLEAR;
import static jssc.SerialPort.PURGE_TXCLEAR;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private static final Logger LOG =
Logger.getLogger(ModemImpl.class.getName());
private static final int MAX_LINE_LENGTH = 256;
private static final int[] BAUD_RATES = {
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200
};
private static final int OK_TIMEOUT = 5 * 1000; // Milliseconds
private static final int CONNECT_TIMEOUT = 2 * 60 * 1000; // Milliseconds
private static final int ESCAPE_SEQUENCE_GUARD_TIME = 1000; // Milliseconds
private final Executor ioExecutor;
private final ReliabilityLayerFactory reliabilityFactory;
private final Clock clock;
private final Callback callback;
private final SerialPort port;
private final Semaphore stateChange;
private final byte[] line;
private final Lock lock = new ReentrantLock();
private final Condition connectedStateChanged = lock.newCondition();
private final Condition initialisedStateChanged = lock.newCondition();
// The following are locking: lock
private ReliabilityLayer reliability = null;
private boolean initialised = false, connected = false;
private int lineLen = 0;
ModemImpl(Executor ioExecutor, ReliabilityLayerFactory reliabilityFactory,
Clock clock, Callback callback, SerialPort port) {
this.ioExecutor = ioExecutor;
this.reliabilityFactory = reliabilityFactory;
this.clock = clock;
this.callback = callback;
this.port = port;
stateChange = new Semaphore(1);
line = new byte[MAX_LINE_LENGTH];
}
@Override
public boolean start() throws IOException {
LOG.info("Starting");
try {
stateChange.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting to start");
}
try {
// Open the serial port
port.openPort();
// Find a suitable baud rate and initialise the modem
try {
boolean foundBaudRate = false;
for (int baudRate : BAUD_RATES) {
if (port.setParams(baudRate, 8, 1, 0)) {
foundBaudRate = true;
break;
}
}
if (!foundBaudRate) {
tryToClose(port);
throw new IOException("No suitable baud rate");
}
port.purgePort(PURGE_RXCLEAR | PURGE_TXCLEAR);
port.addEventListener(this);
port.writeBytes("ATZ\r\n".getBytes("US-ASCII")); // Reset
port.writeBytes("ATE0\r\n".getBytes("US-ASCII")); // Echo off
} catch (IOException e) {
tryToClose(port);
throw e;
}
// Wait for the event thread to receive "OK"
boolean success = false;
try {
lock.lock();
try {
long now = clock.currentTimeMillis();
long end = now + OK_TIMEOUT;
while (now < end && !initialised) {
initialisedStateChanged.await(end - now, MILLISECONDS);
now = clock.currentTimeMillis();
}
success = initialised;
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while initialising");
}
if (success) return true;
tryToClose(port);
return false;
} finally {
stateChange.release();
}
}
private void tryToClose(@Nullable SerialPort port) {
try {
if (port != null) port.closePort();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public void stop() throws IOException {
LOG.info("Stopping");
lock.lock();
try {
// Wake any threads that are waiting to connect
initialised = false;
connected = false;
initialisedStateChanged.signalAll();
connectedStateChanged.signalAll();
} finally {
lock.unlock();
}
// Hang up if necessary and close the port
try {
stateChange.acquire();
} catch (InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting to stop");
}
try {
hangUpInner();
port.closePort();
} finally {
stateChange.release();
}
}
// Locking: stateChange
private void hangUpInner() throws IOException {
ReliabilityLayer reliability;
lock.lock();
try {
if (this.reliability == null) {
LOG.info("Not hanging up - already on the hook");
return;
}
reliability = this.reliability;
this.reliability = null;
connected = false;
} finally {
lock.unlock();
}
reliability.stop();
LOG.info("Hanging up");
try {
clock.sleep(ESCAPE_SEQUENCE_GUARD_TIME);
port.writeBytes("+++".getBytes("US-ASCII"));
clock.sleep(ESCAPE_SEQUENCE_GUARD_TIME);
port.writeBytes("ATH\r\n".getBytes("US-ASCII"));
} catch (InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while hanging up");
} catch (IOException e) {
tryToClose(port);
throw e;
}
}
@Override
public boolean dial(String number) throws IOException {
if (!stateChange.tryAcquire()) {
LOG.info("Not dialling - state change in progress");
return false;
}
try {
ReliabilityLayer reliability =
reliabilityFactory.createReliabilityLayer(this);
lock.lock();
try {
if (!initialised) {
LOG.info("Not dialling - modem not initialised");
return false;
}
if (this.reliability != null) {
LOG.info("Not dialling - call in progress");
return false;
}
this.reliability = reliability;
} finally {
lock.unlock();
}
reliability.start();
LOG.info("Dialling");
try {
String dial = "ATDT" + number + "\r\n";
port.writeBytes(dial.getBytes("US-ASCII"));
} catch (IOException e) {
tryToClose(port);
throw e;
}
// Wait for the event thread to receive "CONNECT"
try {
lock.lock();
try {
long now = clock.currentTimeMillis();
long end = now + CONNECT_TIMEOUT;
while (now < end && initialised && !connected) {
connectedStateChanged.await(end - now, MILLISECONDS);
now = clock.currentTimeMillis();
}
if (connected) return true;
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while dialling");
}
hangUpInner();
return false;
} finally {
stateChange.release();
}
}
@Override
public InputStream getInputStream() throws IOException {
ReliabilityLayer reliability;
lock.lock();
try {
reliability = this.reliability;
} finally {
lock.unlock();
}
if (reliability == null) throw new IOException("Not connected");
return reliability.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
ReliabilityLayer reliability;
lock.lock();
try {
reliability = this.reliability;
} finally {
lock.unlock();
}
if (reliability == null) throw new IOException("Not connected");
return reliability.getOutputStream();
}
@Override
public void hangUp() throws IOException {
try {
stateChange.acquire();
} catch (InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting to hang up");
}
try {
hangUpInner();
} finally {
stateChange.release();
}
}
@Override
public void handleWrite(byte[] b) throws IOException {
try {
port.writeBytes(b);
} catch (IOException e) {
tryToClose(port);
throw e;
}
}
@Override
public void serialEvent(SerialPortEvent ev) {
try {
if (ev.isRXCHAR()) {
byte[] b = port.readBytes();
if (!handleData(b)) handleText(b);
} else if (ev.isDSR() && ev.getEventValue() == 0) {
LOG.info("Remote end hung up");
hangUp();
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Serial event " + ev.getEventType() + " " +
ev.getEventValue());
}
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private boolean handleData(byte[] b) throws IOException {
ReliabilityLayer reliability;
lock.lock();
try {
reliability = this.reliability;
} finally {
lock.unlock();
}
if (reliability == null) return false;
reliability.handleRead(b);
return true;
}
private void handleText(byte[] b) throws IOException {
if (lineLen + b.length > MAX_LINE_LENGTH) {
tryToClose(port);
throw new IOException("Line too long");
}
for (int i = 0; i < b.length; i++) {
line[lineLen] = b[i];
if (b[i] == '\n') {
// FIXME: Use CharsetDecoder to catch invalid ASCII
String s = new String(line, 0, lineLen, "US-ASCII").trim();
lineLen = 0;
if (LOG.isLoggable(INFO)) LOG.info("Modem status: " + s);
if (s.startsWith("CONNECT")) {
lock.lock();
try {
connected = true;
connectedStateChanged.signalAll();
} finally {
lock.unlock();
}
// There might be data in the buffer as well as text
int off = i + 1;
if (off < b.length) {
byte[] data = new byte[b.length - off];
System.arraycopy(b, off, data, 0, data.length);
handleData(data);
}
return;
} else if (s.equals("BUSY") || s.equals("NO DIALTONE")
|| s.equals("NO CARRIER")) {
lock.lock();
try {
connected = false;
connectedStateChanged.signalAll();
} finally {
lock.unlock();
}
} else if (s.equals("OK")) {
lock.lock();
try {
initialised = true;
initialisedStateChanged.signalAll();
} finally {
lock.unlock();
}
} else if (s.equals("RING")) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
try {
answer();
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
} else {
lineLen++;
}
}
}
private void answer() throws IOException {
if (!stateChange.tryAcquire()) {
LOG.info("Not answering - state change in progress");
return;
}
try {
ReliabilityLayer reliability =
reliabilityFactory.createReliabilityLayer(this);
lock.lock();
try {
if (!initialised) {
LOG.info("Not answering - modem not initialised");
return;
}
if (this.reliability != null) {
LOG.info("Not answering - call in progress");
return;
}
this.reliability = reliability;
} finally {
lock.unlock();
}
reliability.start();
LOG.info("Answering");
try {
port.writeBytes("ATA\r\n".getBytes("US-ASCII"));
} catch (IOException e) {
tryToClose(port);
throw e;
}
// Wait for the event thread to receive "CONNECT"
boolean success = false;
try {
lock.lock();
try {
long now = clock.currentTimeMillis();
long end = now + CONNECT_TIMEOUT;
while (now < end && initialised && !connected) {
connectedStateChanged.await(end - now, MILLISECONDS);
now = clock.currentTimeMillis();
}
success = connected;
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while answering");
}
if (success) callback.incomingCallConnected();
else hangUpInner();
} finally {
stateChange.release();
}
}
}

View File

@@ -0,0 +1,231 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class ModemPlugin implements DuplexPlugin, Modem.Callback {
static final TransportId ID =
new TransportId("org.briarproject.bramble.modem");
private static final Logger LOG =
Logger.getLogger(ModemPlugin.class.getName());
private final ModemFactory modemFactory;
private final SerialPortList serialPortList;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile Modem modem = null;
ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList,
DuplexPluginCallback callback, int maxLatency) {
this.modemFactory = modemFactory;
this.serialPortList = serialPortList;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// FIXME: Do we need keepalives for this transport?
return Integer.MAX_VALUE;
}
@Override
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName);
modem = modemFactory.createModem(this, portName);
try {
if (!modem.start()) continue;
if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName);
running = true;
return true;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
return false;
}
@Override
public void stop() {
running = false;
if (modem != null) {
try {
modem.stop();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(Collection<ContactId> connected) {
throw new UnsupportedOperationException();
}
private boolean resetModem() {
if (!running) return false;
for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName);
modem = modemFactory.createModem(this, portName);
try {
if (!modem.start()) continue;
if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName);
return true;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
running = false;
return false;
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null;
// Get the ISO 3166 code for the caller's country
String fromIso = callback.getLocalProperties().get("iso3166");
if (StringUtils.isNullOrEmpty(fromIso)) return null;
// Get the ISO 3166 code for the callee's country
TransportProperties properties = callback.getRemoteProperties().get(c);
if (properties == null) return null;
String toIso = properties.get("iso3166");
if (StringUtils.isNullOrEmpty(toIso)) return null;
// Get the callee's phone number
String number = properties.get("number");
if (StringUtils.isNullOrEmpty(number)) return null;
// Convert the number into direct dialling form
number = CountryCodes.translate(number, fromIso, toIso);
if (number == null) return null;
// Dial the number
try {
if (!modem.dial(number)) return null;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
resetModem();
return null;
}
return new ModemTransportConnection();
}
@Override
public boolean supportsInvitations() {
return false;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsKeyAgreement() {
return false;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
throw new UnsupportedOperationException();
}
@Override
public void incomingCallConnected() {
LOG.info("Incoming call connected");
callback.incomingConnectionCreated(new ModemTransportConnection());
}
private class ModemTransportConnection
extends AbstractDuplexTransportConnection {
private ModemTransportConnection() {
super(ModemPlugin.this);
}
@Override
protected InputStream getInputStream() throws IOException {
return modem.getInputStream();
}
@Override
protected OutputStream getOutputStream() throws IOException {
return modem.getOutputStream();
}
@Override
protected void closeConnection(boolean exception) {
LOG.info("Call disconnected");
try {
modem.hangUp();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
exception = true;
}
if (exception) resetModem();
}
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
import org.briarproject.bramble.util.StringUtils;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ModemPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private final ModemFactory modemFactory;
private final SerialPortList serialPortList;
public ModemPluginFactory(Executor ioExecutor,
ReliabilityLayerFactory reliabilityFactory) {
modemFactory = new ModemFactoryImpl(ioExecutor, reliabilityFactory);
serialPortList = new SerialPortListImpl();
}
@Override
public TransportId getId() {
return ModemPlugin.ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
// This plugin is not enabled by default
String enabled = callback.getSettings().get("enabled");
if (StringUtils.isNullOrEmpty(enabled)) return null;
return new ModemPlugin(modemFactory, serialPortList, callback,
MAX_LATENCY);
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import jssc.SerialPortEventListener;
@NotNullByDefault
interface SerialPort {
void openPort() throws IOException;
void closePort() throws IOException;
boolean setParams(int baudRate, int dataBits, int stopBits, int parityBits)
throws IOException;
void purgePort(int flags) throws IOException;
void addEventListener(SerialPortEventListener l) throws IOException;
byte[] readBytes() throws IOException;
void writeBytes(byte[] b) throws IOException;
}

View File

@@ -0,0 +1,83 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;
@NotNullByDefault
class SerialPortImpl implements SerialPort {
private final jssc.SerialPort port;
SerialPortImpl(String portName) {
port = new jssc.SerialPort(portName);
}
@Override
public void openPort() throws IOException {
try {
if (!port.openPort()) throw new IOException("Failed to open port");
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void closePort() throws IOException {
try {
if (!port.closePort()) throw new IOException("Failed to close port");
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public boolean setParams(int baudRate, int dataBits, int stopBits,
int parityBits) throws IOException {
try {
return port.setParams(baudRate, dataBits, stopBits, parityBits);
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void purgePort(int flags) throws IOException {
try {
if (!port.purgePort(flags))
throw new IOException("Failed to purge port");
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void addEventListener(SerialPortEventListener l) throws IOException {
try {
port.addEventListener(l);
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public byte[] readBytes() throws IOException {
try {
return port.readBytes();
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void writeBytes(byte[] b) throws IOException {
try {
if (!port.writeBytes(b)) throw new IOException("Failed to write");
} catch (SerialPortException e) {
throw new IOException(e);
}
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SerialPortList {
String[] getPortNames();
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class SerialPortListImpl implements SerialPortList {
@Override
public String[] getPortNames() {
return jssc.SerialPortList.getPortNames();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopSeedProviderModule {
@Provides
@Singleton
SeedProvider provideSeedProvider() {
return OsUtils.isLinux() ? new LinuxSeedProvider() : null;
}
}