Moved desktop-specific code into a separate project (other task #34).

This commit is contained in:
akwizgran
2013-06-27 16:05:31 +01:00
parent 26d25cdbba
commit 473cec8735
48 changed files with 153 additions and 15 deletions

View File

@@ -2,7 +2,6 @@ package net.sf.briar.lifecycle;
import net.sf.briar.api.lifecycle.LifecycleManager;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.util.OsUtils;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
@@ -12,8 +11,7 @@ public class LifecycleModule extends AbstractModule {
protected void configure() {
bind(LifecycleManager.class).to(
LifecycleManagerImpl.class).in(Singleton.class);
if(OsUtils.isWindows())
bind(ShutdownManager.class).to(WindowsShutdownManagerImpl.class);
else bind(ShutdownManager.class).to(ShutdownManagerImpl.class);
bind(ShutdownManager.class).to(
ShutdownManagerImpl.class).in(Singleton.class);
}
}

View File

@@ -1,176 +0,0 @@
package net.sf.briar.lifecycle;
import static com.sun.jna.Library.OPTION_FUNCTION_MAPPER;
import static com.sun.jna.Library.OPTION_TYPE_MAPPER;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import net.sf.briar.util.OsUtils;
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;
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: this
WindowsShutdownManagerImpl() {
// Use the Unicode versions of Win32 API calls
Map<String, Object> m = new HashMap<String, Object>();
m.put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
m.put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
options = Collections.unmodifiableMap(m);
}
@Override
public synchronized int addShutdownHook(Runnable r) {
if(!initialised) initialise();
return super.addShutdownHook(r);
}
@Override
protected Thread createThread(Runnable r) {
return new StartOnce(r);
}
// Locking: this
private void initialise() {
if(OsUtils.isWindows()) {
new EventLoop().start();
} else {
if(LOG.isLoggable(WARNING))
LOG.warning("Windows shutdown manager used on non-Windows OS");
}
initialised = true;
}
// Package access for testing
synchronized void runShutdownHooks() {
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) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while running shutdown hooks");
interrupted = true;
}
}
if(interrupted) Thread.currentThread().interrupt();
}
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() {
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);
if(LOG.isLoggable(INFO))
LOG.info("Registered 64-bit callback");
} catch(UnsatisfiedLinkError e) {
// Use SetWindowLong if SetWindowLongPtr isn't available
user32.SetWindowLong(hwnd, GWL_WNDPROC, proc);
if(LOG.isLoggable(INFO))
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 static 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 static interface WindowProc extends StdCallCallback {
public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
}
}

View File

@@ -1,62 +0,0 @@
package net.sf.briar.plugins;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Executor;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPluginConfig;
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
import net.sf.briar.api.plugins.simplex.SimplexPluginConfig;
import net.sf.briar.api.plugins.simplex.SimplexPluginFactory;
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
import net.sf.briar.plugins.bluetooth.BluetoothPluginFactory;
import net.sf.briar.plugins.file.RemovableDrivePluginFactory;
import net.sf.briar.plugins.modem.ModemPluginFactory;
import net.sf.briar.plugins.tcp.LanTcpPluginFactory;
import net.sf.briar.plugins.tcp.WanTcpPluginFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
public class JavaSePluginsModule extends AbstractModule {
public void configure() {}
@Provides
SimplexPluginConfig getSimplexPluginConfig(
@PluginExecutor Executor pluginExecutor) {
SimplexPluginFactory removable =
new RemovableDrivePluginFactory(pluginExecutor);
final Collection<SimplexPluginFactory> factories =
Arrays.asList(removable);
return new SimplexPluginConfig() {
public Collection<SimplexPluginFactory> getFactories() {
return factories;
}
};
}
@Provides
DuplexPluginConfig getDuplexPluginConfig(
@PluginExecutor Executor pluginExecutor,
CryptoComponent crypto, ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager) {
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(
pluginExecutor, crypto.getSecureRandom());
DuplexPluginFactory modem = new ModemPluginFactory(pluginExecutor,
reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(pluginExecutor);
DuplexPluginFactory wan = new WanTcpPluginFactory(pluginExecutor,
shutdownManager);
final Collection<DuplexPluginFactory> factories =
Arrays.asList(bluetooth, modem, lan, wan);
return new DuplexPluginConfig() {
public Collection<DuplexPluginFactory> getFactories() {
return factories;
}
};
}
}

View File

@@ -1,385 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static javax.bluetooth.DiscoveryAgent.GIAC;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger;
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 net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.PseudoRandom;
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.util.LatchedReference;
import net.sf.briar.util.OsUtils;
import net.sf.briar.util.StringUtils;
class BluetoothPlugin implements DuplexPlugin {
// Share an ID with the Android Bluetooth plugin
static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("d99c9313c04417dcf22fc60d12a187ea"
+ "00a539fd260f08a13a0d8a900cde5e49"
+ "1b4df2ffd42e40c408f2db7868f518aa");
static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
private static final int UUID_BYTES = 16;
private final Executor pluginExecutor;
private final Clock clock;
private final SecureRandom secureRandom;
private final DuplexPluginCallback callback;
private final int maxFrameLength;
private final long maxLatency, pollingInterval;
private final Semaphore discoverySemaphore = new Semaphore(1);
private volatile boolean running = false;
private volatile StreamConnectionNotifier socket = null;
private volatile LocalDevice localDevice = null;
BluetoothPlugin(Executor pluginExecutor, Clock clock,
SecureRandom secureRandom, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval) {
this.pluginExecutor = pluginExecutor;
this.clock = clock;
this.secureRandom = secureRandom;
this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency;
this.pollingInterval = pollingInterval;
}
public TransportId getId() {
return ID;
}
public String getName() {
return "BLUETOOTH_PLUGIN_NAME";
}
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public boolean start() throws IOException {
// 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;
pluginExecutor.execute(new Runnable() {
public void run() {
bind();
}
});
return true;
}
private void bind() {
if(!running) return;
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put("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;
acceptContactConnections(ss);
}
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
private String getUuid() {
String uuid = callback.getLocalProperties().get("uuid");
if(uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put("uuid", uuid);
callback.mergeLocalProperties(p);
}
return uuid;
}
private void tryToClose(StreamConnectionNotifier ss) {
try {
if(ss != null) ss.close();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
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.log(INFO, e.toString(), e);
tryToClose(ss);
return;
}
callback.incomingConnectionCreated(wrapSocket(s));
if(!running) return;
}
}
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new BluetoothTransportConnection(this, s);
}
public void stop() {
running = false;
tryToClose(socket);
}
public boolean shouldPoll() {
return true;
}
public long getPollingInterval() {
return pollingInterval;
}
public void poll(final Collection<ContactId> connected) {
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");
if(StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get("uuid");
if(StringUtils.isNullOrEmpty(uuid)) continue;
pluginExecutor.execute(new Runnable() {
public void run() {
if(!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
if(s != null)
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
});
}
}
private StreamConnection connect(String url) {
try {
return (StreamConnection) Connector.open(url);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
public DuplexTransportConnection createConnection(ContactId c) {
if(!running) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if(p == null) return null;
String address = p.get("address");
if(StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get("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);
}
public boolean supportsInvitations() {
return true;
}
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout) {
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;
}
// Start the background threads
LatchedReference<StreamConnection> socketLatch =
new LatchedReference<StreamConnection>();
new DiscoveryThread(socketLatch, uuid, timeout).start();
new BluetoothListenerThread(socketLatch, ss).start();
// Wait for an incoming or outgoing connection
try {
StreamConnection s = socketLatch.waitForReference(timeout);
if(s != null) return new BluetoothTransportConnection(this, s);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while exchanging invitations");
Thread.currentThread().interrupt();
} finally {
// Closing the socket will terminate the listener thread
tryToClose(ss);
}
return null;
}
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 DiscoveryThread extends Thread {
private final LatchedReference<StreamConnection> socketLatch;
private final String uuid;
private final long timeout;
private DiscoveryThread(LatchedReference<StreamConnection> socketLatch,
String uuid, long timeout) {
this.socketLatch = socketLatch;
this.uuid = uuid;
this.timeout = timeout;
}
@Override
public void run() {
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
long now = clock.currentTimeMillis();
long end = now + timeout;
while(now < end && running && !socketLatch.isSet()) {
if(!discoverySemaphore.tryAcquire()) {
if(LOG.isLoggable(INFO))
LOG.info("Another device discovery is in progress");
return;
}
try {
InvitationListener listener =
new InvitationListener(discoveryAgent, uuid);
discoveryAgent.startInquiry(GIAC, listener);
String url = listener.waitForUrl();
if(url == null) continue;
StreamConnection s = connect(url);
if(s == null) continue;
if(LOG.isLoggable(INFO)) LOG.info("Outgoing connection");
if(!socketLatch.set(s)) {
if(LOG.isLoggable(INFO))
LOG.info("Closing redundant connection");
tryToClose(s);
}
return;
} catch(BluetoothStateException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return;
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for URL");
Thread.currentThread().interrupt();
return;
} finally {
discoverySemaphore.release();
}
}
}
private void tryToClose(StreamConnection s) {
try {
s.close();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
private static class BluetoothListenerThread extends Thread {
private final LatchedReference<StreamConnection> socketLatch;
private final StreamConnectionNotifier serverSocket;
private BluetoothListenerThread(
LatchedReference<StreamConnection> socketLatch,
StreamConnectionNotifier serverSocket) {
this.socketLatch = socketLatch;
this.serverSocket = serverSocket;
}
@Override
public void run() {
if(LOG.isLoggable(INFO))
LOG.info("Listening for invitation connections");
// Listen until a connection is received or the socket is closed
try {
StreamConnection s = serverSocket.acceptAndOpen();
if(LOG.isLoggable(INFO)) LOG.info("Incoming connection");
if(!socketLatch.set(s)) {
if(LOG.isLoggable(INFO))
LOG.info("Closing redundant connection");
s.close();
}
} catch(IOException e) {
// This is expected when the socket is closed
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
}
}
}
}

View File

@@ -1,38 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
public class BluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024;
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private final Executor pluginExecutor;
private final SecureRandom secureRandom;
private final Clock clock;
public BluetoothPluginFactory(Executor pluginExecutor,
SecureRandom secureRandom) {
this.pluginExecutor = pluginExecutor;
this.secureRandom = secureRandom;
clock = new SystemClock();
}
public TransportId getId() {
return BluetoothPlugin.ID;
}
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new BluetoothPlugin(pluginExecutor, clock, secureRandom,
callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
}
}

View File

@@ -1,46 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.io.StreamConnection;
import net.sf.briar.api.plugins.Plugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
class BluetoothTransportConnection implements DuplexTransportConnection {
private final Plugin plugin;
private final StreamConnection stream;
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
this.plugin = plugin;
this.stream = stream;
}
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() throws IOException {
return stream.openInputStream();
}
public OutputStream getOutputStream() throws IOException {
return stream.openOutputStream();
}
public boolean shouldFlush() {
return true;
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
stream.close();
}
}

View File

@@ -1,105 +0,0 @@
package net.sf.briar.plugins.bluetooth;
import static java.util.logging.Level.WARNING;
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;
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;
}
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);
}
}
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<String>();
findNestedClassIds(record.getAttributeValue(0x1), uuids);
for(String u : uuids) {
if(uuid.equalsIgnoreCase(u)) {
// The UUID matches - store the URL
url = serviceUrl;
finished.countDown();
return;
}
}
}
}
public void inquiryCompleted(int discoveryType) {
if(searches.decrementAndGet() == 0) finished.countDown();
}
public void serviceSearchCompleted(int transaction, int response) {
if(searches.decrementAndGet() == 0) finished.countDown();
}
// UUIDs are sometimes buried in nested data elements
private void findNestedClassIds(Object o, Collection<String> ids) {
o = getDataElementValue(o);
if(o instanceof Enumeration<?>) {
for(Object o1 : Collections.list((Enumeration<?>) o))
findNestedClassIds(o1, ids);
} else if(o instanceof UUID) {
ids.add(o.toString());
}
}
private Object getDataElementValue(Object o) {
if(o instanceof DataElement) {
// Bluecove throws an exception if the type is unknown
try {
return ((DataElement) o).getValue();
} catch(ClassCastException e) {
return null;
}
}
return null;
}
}

View File

@@ -1,22 +0,0 @@
package net.sf.briar.plugins.file;
class LinuxRemovableDriveFinder extends UnixRemovableDriveFinder {
@Override
protected String getMountCommand() {
return "/bin/mount";
}
@Override
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

@@ -1,9 +0,0 @@
package net.sf.briar.plugins.file;
class LinuxRemovableDriveMonitor extends UnixRemovableDriveMonitor {
@Override
protected String[] getPathsToWatch() {
return new String[] { "/mnt", "/media" };
}
}

View File

@@ -1,22 +0,0 @@
package net.sf.briar.plugins.file;
class MacRemovableDriveFinder extends UnixRemovableDriveFinder {
@Override
protected String getMountCommand() {
return "/sbin/mount";
}
@Override
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

@@ -1,9 +0,0 @@
package net.sf.briar.plugins.file;
class MacRemovableDriveMonitor extends UnixRemovableDriveMonitor {
@Override
protected String[] getPathsToWatch() {
return new String[] { "/Volumes" };
}
}

View File

@@ -1,66 +0,0 @@
package net.sf.briar.plugins.file;
import static java.util.logging.Level.INFO;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
private static final Logger LOG =
Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
private final Executor pluginExecutor;
private final RemovableDriveFinder finder;
private final long pollingInterval;
private final Object pollingLock = new Object();
private volatile boolean running = false;
private volatile Callback callback = null;
public PollingRemovableDriveMonitor(Executor pluginExecutor,
RemovableDriveFinder finder, long pollingInterval) {
this.pluginExecutor = pluginExecutor;
this.finder = finder;
this.pollingInterval = pollingInterval;
}
public void start(Callback callback) throws IOException {
this.callback = callback;
running = true;
pluginExecutor.execute(this);
}
public void stop() throws IOException {
running = false;
synchronized(pollingLock) {
pollingLock.notifyAll();
}
}
public void run() {
try {
Collection<File> drives = finder.findRemovableDrives();
while(running) {
synchronized(pollingLock) {
pollingLock.wait(pollingInterval);
}
if(!running) return;
Collection<File> newDrives = finder.findRemovableDrives();
for(File f : newDrives) {
if(!drives.contains(f)) callback.driveInserted(f);
}
drives = newDrives;
}
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting to poll");
Thread.currentThread().interrupt();
} catch(IOException e) {
callback.exceptionThrown(e);
}
}
}

View File

@@ -1,10 +0,0 @@
package net.sf.briar.plugins.file;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
interface RemovableDriveFinder {
Collection<File> findRemovableDrives() throws IOException;
}

View File

@@ -1,18 +0,0 @@
package net.sf.briar.plugins.file;
import java.io.File;
import java.io.IOException;
interface RemovableDriveMonitor {
void start(Callback c) throws IOException;
void stop() throws IOException;
interface Callback {
void driveInserted(File root);
void exceptionThrown(IOException e);
}
}

View File

@@ -1,132 +0,0 @@
package net.sf.briar.plugins.file;
import static java.util.logging.Level.WARNING;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.util.StringUtils;
class RemovableDrivePlugin extends FilePlugin
implements RemovableDriveMonitor.Callback {
static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("7c81bf5c9b1cd557685548c85f976bbd"
+ "e633d2418ea2e230e5710fb43c6f8cc0"
+ "68abca3a9d0edb13bcea13b851725c5d");
static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(RemovableDrivePlugin.class.getName());
private final RemovableDriveFinder finder;
private final RemovableDriveMonitor monitor;
RemovableDrivePlugin(Executor pluginExecutor,
SimplexPluginCallback callback, RemovableDriveFinder finder,
RemovableDriveMonitor monitor, int maxFrameLength,
long maxLatency) {
super(pluginExecutor, callback, maxFrameLength, maxLatency);
this.finder = finder;
this.monitor = monitor;
}
public TransportId getId() {
return ID;
}
public String getName() {
return "REMOVABLE_DRIVE_PLUGIN_NAME";
}
public boolean start() throws IOException {
running = true;
monitor.start(this);
return true;
}
public void stop() throws IOException {
running = false;
monitor.stop();
}
public boolean shouldPoll() {
return false;
}
public long getPollingInterval() {
throw new UnsupportedOperationException();
}
public void poll(Collection<ContactId> connected) {
throw new UnsupportedOperationException();
}
@Override
protected File chooseOutputDirectory() {
try {
List<File> drives =
new ArrayList<File>(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<File>();
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 Collections.unmodifiableList(matches);
}
public void driveInserted(File root) {
File[] files = root.listFiles();
if(files != null) {
for(File f : files) if(f.isFile()) createReaderFromFile(f);
}
}
public void exceptionThrown(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}

View File

@@ -1,53 +0,0 @@
package net.sf.briar.plugins.file;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
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;
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
private static final long MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
private static final long POLLING_INTERVAL = 10 * 1000; // 10 seconds
private final Executor pluginExecutor;
public RemovableDrivePluginFactory(Executor pluginExecutor) {
this.pluginExecutor = pluginExecutor;
}
public TransportId getId() {
return RemovableDrivePlugin.ID;
}
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(pluginExecutor, finder,
POLLING_INTERVAL);
} else if(OsUtils.isWindows()) {
finder = new WindowsRemovableDriveFinder();
monitor = new PollingRemovableDriveMonitor(pluginExecutor, finder,
POLLING_INTERVAL);
} else {
return null;
}
return new RemovableDrivePlugin(pluginExecutor, callback, finder,
monitor, MAX_FRAME_LENGTH, MAX_LATENCY);
}
}

View File

@@ -1,40 +0,0 @@
package net.sf.briar.plugins.file;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
abstract class UnixRemovableDriveFinder implements RemovableDriveFinder {
protected abstract String getMountCommand();
protected abstract String parseMountPoint(String line);
protected abstract boolean isRemovableDriveMountPoint(String path);
public List<File> findRemovableDrives() throws IOException {
List<File> drives = new ArrayList<File>();
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(isRemovableDriveMountPoint(path)) {
File f = new File(path);
if(f.exists() && f.isDirectory()) drives.add(f);
}
}
}
} finally {
s.close();
}
return Collections.unmodifiableList(drives);
}
}

View File

@@ -1,72 +0,0 @@
package net.sf.briar.plugins.file;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyListener;
abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
JNotifyListener {
// Locking: this
private final List<Integer> watches = new ArrayList<Integer>();
private boolean started = false; // Locking: this
private Callback callback = null; // Locking: this
protected abstract String[] getPathsToWatch();
public void start(Callback callback) throws IOException {
List<Integer> watches = new ArrayList<Integer>();
int mask = JNotify.FILE_CREATED;
for(String path : getPathsToWatch()) {
if(new File(path).exists())
watches.add(JNotify.addWatch(path, mask, false, this));
}
synchronized(this) {
assert !started;
assert this.callback == null;
started = true;
this.callback = callback;
this.watches.addAll(watches);
}
}
public void stop() throws IOException {
List<Integer> watches;
synchronized(this) {
assert started;
assert callback != null;
started = false;
callback = null;
watches = new ArrayList<Integer>(this.watches);
this.watches.clear();
}
for(Integer w : watches) JNotify.removeWatch(w);
}
public void fileCreated(int wd, String rootPath, String name) {
Callback callback;
synchronized(this) {
callback = this.callback;
}
if(callback != null)
callback.driveInserted(new File(rootPath + "/" + name));
}
public void fileDeleted(int wd, String rootPath, String name) {
throw new UnsupportedOperationException();
}
public void fileModified(int wd, String rootPath, String name) {
throw new UnsupportedOperationException();
}
public void fileRenamed(int wd, String rootPath, String oldName,
String newName) {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,31 +0,0 @@
package net.sf.briar.plugins.file;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.sun.jna.platform.win32.Kernel32;
class WindowsRemovableDriveFinder implements RemovableDriveFinder {
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364939.aspx
private static final int DRIVE_REMOVABLE = 2;
public Collection<File> findRemovableDrives() throws IOException {
File[] roots = File.listRoots();
if(roots == null) throw new IOException();
List<File> drives = new ArrayList<File>();
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.toString());
}
}
return Collections.unmodifiableList(drives);
}
}

View File

@@ -1,286 +0,0 @@
package net.sf.briar.plugins.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<String, Country>();
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

@@ -1,45 +0,0 @@
package net.sf.briar.plugins.modem;
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.
*/
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

@@ -1,6 +0,0 @@
package net.sf.briar.plugins.modem;
interface ModemFactory {
Modem createModem(Modem.Callback callback, String portName);
}

View File

@@ -1,26 +0,0 @@
package net.sf.briar.plugins.modem;
import java.util.concurrent.Executor;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
class ModemFactoryImpl implements ModemFactory {
private final Executor executor;
private final ReliabilityLayerFactory reliabilityFactory;
private final Clock clock;
ModemFactoryImpl(Executor executor,
ReliabilityLayerFactory reliabilityFactory) {
this.executor = executor;
this.reliabilityFactory = reliabilityFactory;
clock = new SystemClock();
}
public Modem createModem(Modem.Callback callback, String portName) {
return new ModemImpl(executor, reliabilityFactory, clock, callback,
new SerialPortImpl(portName));
}
}

View File

@@ -1,408 +0,0 @@
package net.sf.briar.plugins.modem;
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;
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.logging.Logger;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.reliability.ReliabilityLayer;
import net.sf.briar.api.reliability.ReliabilityLayerFactory;
import net.sf.briar.api.reliability.WriteHandler;
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 executor;
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 int lineLen = 0;
private ReliabilityLayer reliability = null; // Locking: this
private boolean initialised = false, connected = false; // Locking: this
ModemImpl(Executor executor, ReliabilityLayerFactory reliabilityFactory,
Clock clock, Callback callback, SerialPort port) {
this.executor = executor;
this.reliabilityFactory = reliabilityFactory;
this.clock = clock;
this.callback = callback;
this.port = port;
stateChange = new Semaphore(1);
line = new byte[MAX_LINE_LENGTH];
}
public boolean start() throws IOException {
if(LOG.isLoggable(INFO)) 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 {
synchronized(this) {
long now = clock.currentTimeMillis();
long end = now + OK_TIMEOUT;
while(now < end && !initialised) {
wait(end - now);
now = clock.currentTimeMillis();
}
success = initialised;
}
} 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(SerialPort port) {
try {
port.closePort();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
public void stop() throws IOException {
if(LOG.isLoggable(INFO)) LOG.info("Stopping");
// Wake any threads that are waiting to connect
synchronized(this) {
initialised = false;
connected = false;
notifyAll();
}
// 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;
synchronized(this) {
if(this.reliability == null) {
if(LOG.isLoggable(INFO))
LOG.info("Not hanging up - already on the hook");
return;
}
reliability = this.reliability;
this.reliability = null;
connected = false;
}
reliability.stop();
if(LOG.isLoggable(INFO)) 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;
}
}
public boolean dial(String number) throws IOException {
if(!stateChange.tryAcquire()) {
if(LOG.isLoggable(INFO))
LOG.info("Not dialling - state change in progress");
return false;
}
try {
ReliabilityLayer reliability =
reliabilityFactory.createReliabilityLayer(this);
synchronized(this) {
if(!initialised) {
if(LOG.isLoggable(INFO))
LOG.info("Not dialling - modem not initialised");
return false;
}
if(this.reliability != null) {
if(LOG.isLoggable(INFO))
LOG.info("Not dialling - call in progress");
return false;
}
this.reliability = reliability;
}
reliability.start();
if(LOG.isLoggable(INFO)) 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 {
synchronized(this) {
long now = clock.currentTimeMillis();
long end = now + CONNECT_TIMEOUT;
while(now < end && initialised && !connected) {
wait(end - now);
now = clock.currentTimeMillis();
}
if(connected) return true;
}
} catch(InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
throw new IOException("Interrupted while dialling");
}
hangUpInner();
return false;
} finally {
stateChange.release();
}
}
public InputStream getInputStream() throws IOException {
ReliabilityLayer reliability;
synchronized(this) {
reliability = this.reliability;
}
if(reliability == null) throw new IOException("Not connected");
return reliability.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
ReliabilityLayer reliability;
synchronized(this) {
reliability = this.reliability;
}
if(reliability == null) throw new IOException("Not connected");
return reliability.getOutputStream();
}
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();
}
}
public void handleWrite(byte[] b) throws IOException {
try {
port.writeBytes(b);
} catch(IOException e) {
tryToClose(port);
throw e;
}
}
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) {
if(LOG.isLoggable(INFO)) 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;
synchronized(this) {
reliability = this.reliability;
}
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') {
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")) {
synchronized(this) {
connected = true;
notifyAll();
}
// 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")) {
synchronized(this) {
connected = false;
notifyAll();
}
} else if(s.equals("OK")) {
synchronized(this) {
initialised = true;
notifyAll();
}
} else if(s.equals("RING")) {
executor.execute(new Runnable() {
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()) {
if(LOG.isLoggable(INFO))
LOG.info("Not answering - state change in progress");
return;
}
try {
ReliabilityLayer reliability =
reliabilityFactory.createReliabilityLayer(this);
synchronized(this) {
if(!initialised) {
if(LOG.isLoggable(INFO))
LOG.info("Not answering - modem not initialised");
return;
}
if(this.reliability != null) {
if(LOG.isLoggable(INFO))
LOG.info("Not answering - call in progress");
return;
}
this.reliability = reliability;
}
reliability.start();
if(LOG.isLoggable(INFO)) 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 {
synchronized(this) {
long now = clock.currentTimeMillis();
long end = now + CONNECT_TIMEOUT;
while(now < end && initialised && !connected) {
wait(end - now);
now = clock.currentTimeMillis();
}
success = connected;
}
} 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

@@ -1,273 +0,0 @@
package net.sf.briar.plugins.modem;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.crypto.PseudoRandom;
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.util.StringUtils;
class ModemPlugin implements DuplexPlugin, Modem.Callback {
static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("8f573867bedf54884b5868ee5d902832" +
"ee5e522da84d0d431712bd672fbd2f79" +
"262d27b93879b94ee9afbb80e7fc87fb");
static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(ModemPlugin.class.getName());
private final Executor pluginExecutor;
private final ModemFactory modemFactory;
private final SerialPortList serialPortList;
private final DuplexPluginCallback callback;
private final int maxFrameLength;
private final long maxLatency, pollingInterval;
private final boolean shuffle; // Used to disable shuffling for testing
private volatile boolean running = false;
private volatile Modem modem = null;
ModemPlugin(Executor pluginExecutor, ModemFactory modemFactory,
SerialPortList serialPortList, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval,
boolean shuffle) {
this.pluginExecutor = pluginExecutor;
this.modemFactory = modemFactory;
this.serialPortList = serialPortList;
this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency;
this.pollingInterval = pollingInterval;
this.shuffle = shuffle;
}
public TransportId getId() {
return ID;
}
public String getName() {
return "MODEM_PLUGIN_NAME";
}
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public boolean start() {
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;
}
public void stop() {
running = false;
if(modem != null) {
try {
modem.stop();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
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;
}
public boolean shouldPoll() {
return true;
}
public long getPollingInterval() {
return pollingInterval;
}
public void poll(Collection<ContactId> connected) {
if(!connected.isEmpty()) return; // One at a time please
pluginExecutor.execute(new Runnable() {
public void run() {
poll();
}
});
}
private void poll() {
if(!running) return;
// Get the ISO 3166 code for the caller's country
String callerIso = callback.getLocalProperties().get("iso3166");
if(StringUtils.isNullOrEmpty(callerIso)) return;
// Call contacts one at a time in a random order
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
List<ContactId> contacts = new ArrayList<ContactId>(remote.keySet());
if(shuffle) Collections.shuffle(contacts);
Iterator<ContactId> it = contacts.iterator();
while(it.hasNext() && running) {
ContactId c = it.next();
// Get the ISO 3166 code for the callee's country
TransportProperties properties = remote.get(c);
if(properties == null) continue;
String calleeIso = properties.get("iso3166");
if(StringUtils.isNullOrEmpty(calleeIso)) continue;
// Get the callee's phone number
String number = properties.get("number");
if(StringUtils.isNullOrEmpty(number)) continue;
// Convert the number into direct dialling form
number = CountryCodes.translate(number, callerIso, calleeIso);
if(number == null) continue;
// Dial the number
try {
if(!modem.dial(number)) continue;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if(resetModem()) continue;
break;
}
if(LOG.isLoggable(INFO)) LOG.info("Outgoing call connected");
ModemTransportConnection conn = new ModemTransportConnection();
callback.outgoingConnectionCreated(c, conn);
try {
conn.waitForDisposal();
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while polling");
Thread.currentThread().interrupt();
break;
}
}
}
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();
}
public boolean supportsInvitations() {
return false;
}
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout) {
throw new UnsupportedOperationException();
}
public void incomingCallConnected() {
if(LOG.isLoggable(INFO)) LOG.info("Incoming call connected");
callback.incomingConnectionCreated(new ModemTransportConnection());
}
private class ModemTransportConnection
implements DuplexTransportConnection {
private final CountDownLatch finished = new CountDownLatch(1);
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public InputStream getInputStream() throws IOException {
return modem.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
return modem.getOutputStream();
}
public boolean shouldFlush() {
return true;
}
public void dispose(boolean exception, boolean recognised) {
if(LOG.isLoggable(INFO)) 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();
finished.countDown();
}
private void waitForDisposal() throws InterruptedException {
finished.await();
}
}
}

View File

@@ -1,41 +0,0 @@
package net.sf.briar.plugins.modem;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
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 net.sf.briar.api.reliability.ReliabilityLayerFactory;
import net.sf.briar.util.StringUtils;
public class ModemPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024;
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
private static final long POLLING_INTERVAL = 60 * 60 * 1000; // 1 hour
private final Executor pluginExecutor;
private final ModemFactory modemFactory;
private final SerialPortList serialPortList;
public ModemPluginFactory(Executor pluginExecutor,
ReliabilityLayerFactory reliabilityFactory) {
this.pluginExecutor = pluginExecutor;
modemFactory = new ModemFactoryImpl(pluginExecutor, reliabilityFactory);
serialPortList = new SerialPortListImpl();
}
public TransportId getId() {
return ModemPlugin.ID;
}
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
// This plugin is not enabled by default
String enabled = callback.getConfig().get("enabled");
if(StringUtils.isNullOrEmpty(enabled)) return null;
return new ModemPlugin(pluginExecutor, modemFactory, serialPortList,
callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL,
true);
}
}

View File

@@ -1,23 +0,0 @@
package net.sf.briar.plugins.modem;
import java.io.IOException;
import jssc.SerialPortEventListener;
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

@@ -1,73 +0,0 @@
package net.sf.briar.plugins.modem;
import java.io.IOException;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;
class SerialPortImpl implements SerialPort {
private final jssc.SerialPort port;
SerialPortImpl(String portName) {
port = new jssc.SerialPort(portName);
}
public void openPort() throws IOException {
try {
if(!port.openPort()) throw new IOException("Failed to open port");
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
}
public void closePort() throws IOException {
try {
if(!port.closePort()) throw new IOException("Failed to close port");
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
}
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.toString());
}
}
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.toString());
}
}
public void addEventListener(SerialPortEventListener l) throws IOException {
try {
port.addEventListener(l);
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
}
public byte[] readBytes() throws IOException {
try {
return port.readBytes();
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
}
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.toString());
}
}
}

View File

@@ -1,6 +0,0 @@
package net.sf.briar.plugins.modem;
interface SerialPortList {
String[] getPortNames();
}

View File

@@ -1,8 +0,0 @@
package net.sf.briar.plugins.modem;
class SerialPortListImpl implements SerialPortList {
public String[] getPortNames() {
return jssc.SerialPortList.getPortNames();
}
}