Rename bramble-j2se to bramble-java.

This commit is contained in:
akwizgran
2018-08-29 15:15:20 +01:00
parent 0d4cf4db68
commit da7cf4af28
40 changed files with 8 additions and 8 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,188 @@
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;
import static org.briarproject.bramble.util.LogUtils.logException;
@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
User32 user32 = (User32) Native.loadLibrary("user32",
User32.class, options);
// Create a callback to handle the WM_QUERYENDSESSION message
WindowProc proc = (hwnd, msg, wp, 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) {
logException(LOG, WARNING, 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,71 @@
package org.briarproject.bramble.network;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class JavaNetworkManager implements NetworkManager, Service {
private static final Logger LOG =
Logger.getLogger(JavaNetworkManager.class.getName());
private final EventBus eventBus;
@Inject
JavaNetworkManager(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void startService() {
eventBus.broadcast(new NetworkStatusEvent(getNetworkStatus()));
}
@Override
public void stopService() {
}
@Override
public NetworkStatus getNetworkStatus() {
boolean connected = false;
try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces != null) {
for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback()) continue;
if (i.isUp() && i.getInetAddresses().hasMoreElements()) {
if (LOG.isLoggable(INFO)) {
LOG.info("Interface " + i.getDisplayName() +
" is up with at least one address.");
}
connected = true;
break;
}
}
}
} catch (SocketException e) {
logException(LOG, WARNING, e);
}
return new NetworkStatus(connected, false);
}
}

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.network;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class JavaNetworkModule {
@Provides
@Singleton
NetworkManager provideNetworkManager(LifecycleManager lifecycleManager,
JavaNetworkManager networkManager) {
lifecycleManager.registerService(networkManager);
return networkManager;
}
}

View File

@@ -0,0 +1,66 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
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.JavaBluetoothPluginFactory;
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.Collection;
import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
@Module
public class DesktopPluginModule extends PluginModule {
@Provides
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
SecureRandom random, BackoffFactory backoffFactory,
ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager, EventBus eventBus) {
DuplexPluginFactory bluetooth =
new JavaBluetoothPluginFactory(ioExecutor, random, eventBus,
backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
backoffFactory);
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
backoffFactory, shutdownManager);
Collection<DuplexPluginFactory> duplex =
asList(bluetooth, modem, lan, wan);
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@Override
public Collection<DuplexPluginFactory> getDuplexFactories() {
return duplex;
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return emptyList();
}
@Override
public boolean shouldPoll() {
return true;
}
};
return pluginConfig;
}
}

View File

@@ -0,0 +1,118 @@
package org.briarproject.bramble.plugin.bluetooth;
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.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isValidMac;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
private static final Logger LOG =
Logger.getLogger(JavaBluetoothPlugin.class.getName());
// Non-null if the plugin started successfully
private volatile LocalDevice localDevice = null;
JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
maxLatency);
}
@Override
void initialiseAdapter() throws IOException {
try {
localDevice = LocalDevice.getLocalDevice();
} catch (UnsatisfiedLinkError | BluetoothStateException e) {
throw new IOException(e);
}
}
@Override
boolean isAdapterEnabled() {
return localDevice != null && LocalDevice.isPowerOn();
}
@Override
void enableAdapter() {
// Nothing we can do on this platform
LOG.info("Could not enable Bluetooth");
}
@Override
void disableAdapterIfEnabledByUs() {
// We didn't enable it so we don't need to disable it
}
@Override
void setEnabledByUs() {
// Irrelevant on this platform
}
@Nullable
@Override
String getBluetoothAddress() {
return localDevice.getBluetoothAddress();
}
@Override
StreamConnectionNotifier openServerSocket(String uuid) throws IOException {
String url = makeUrl("localhost", uuid);
return (StreamConnectionNotifier) Connector.open(url);
}
@Override
void tryToClose(@Nullable StreamConnectionNotifier ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
@Override
DuplexTransportConnection acceptConnection(StreamConnectionNotifier ss)
throws IOException {
return wrapSocket(ss.acceptAndOpen());
}
@Override
boolean isValidAddress(String address) {
return isValidMac(address);
}
@Override
DuplexTransportConnection connectTo(String address, String uuid)
throws IOException {
String url = makeUrl(address, uuid);
return wrapSocket((StreamConnection) Connector.open(url));
}
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new JavaBluetoothTransportConnection(this, connectionLimiter, s);
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus;
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 JavaBluetoothPluginFactory 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;
private final EventBus eventBus;
public JavaBluetoothPluginFactory(Executor ioExecutor,
SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoffFactory = backoffFactory;
this.eventBus = eventBus;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
ioExecutor, secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}
}

View File

@@ -0,0 +1,46 @@
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 JavaBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final StreamConnection stream;
JavaBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
StreamConnection stream) {
super(plugin);
this.stream = stream;
this.connectionManager = connectionManager;
}
@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 {
try {
stream.close();
} finally {
connectionManager.connectionClosed(this);
}
}
}

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,464 @@
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;
import static org.briarproject.bramble.util.LogUtils.logException;
@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) {
logException(LOG, WARNING, 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) {
logException(LOG, WARNING, 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(() -> {
try {
answer();
} catch (IOException e) {
logException(LOG, WARNING, 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,219 @@
package org.briarproject.bramble.plugin.modem;
import org.briarproject.bramble.api.contact.ContactId;
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.PluginException;
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.Map;
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;
import static org.briarproject.bramble.util.LogUtils.logException;
@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 void start() throws PluginException {
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;
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
throw new PluginException();
}
@Override
public void stop() {
running = false;
if (modem != null) {
try {
modem.stop();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(Map<ContactId, TransportProperties> contacts) {
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) {
logException(LOG, WARNING, e);
}
}
running = false;
return false;
}
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
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
String toIso = p.get("iso3166");
if (StringUtils.isNullOrEmpty(toIso)) return null;
// Get the callee's phone number
String number = p.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) {
logException(LOG, WARNING, e);
resetModem();
return null;
}
return new ModemTransportConnection();
}
@Override
public boolean supportsKeyAgreement() {
return false;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
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) {
logException(LOG, WARNING, 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,59 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.util.concurrent.Executor;
import javax.net.SocketFactory;
@NotNullByDefault
class LinuxTorPlugin extends TorPlugin {
LinuxTorPlugin(Executor ioExecutor, NetworkManager networkManager,
LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, Backoff backoff,
DuplexPluginCallback callback, String architecture, int maxLatency,
int maxIdleTime, File torDirectory) {
super(ioExecutor, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, backoff,
callback, architecture, maxLatency, maxIdleTime, torDirectory);
}
@Override
protected int getProcessId() {
try {
// Java 9: ProcessHandle.current().pid()
return Integer.parseInt(
new File("/proc/self").getCanonicalFile().getName());
} catch (IOException e) {
throw new AssertionError(e);
}
}
@Override
protected long getLastUpdateTime() {
CodeSource codeSource =
getClass().getProtectionDomain().getCodeSource();
if (codeSource == null) throw new AssertionError("CodeSource null");
try {
URI path = codeSource.getLocation().toURI();
File file = new File(path);
return file.lastModified();
} catch (URISyntaxException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -0,0 +1,103 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.network.NetworkManager;
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.TorConstants;
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.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import javax.net.SocketFactory;
import static org.briarproject.bramble.util.OsUtils.isLinux;
@Immutable
@NotNullByDefault
public class LinuxTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG =
Logger.getLogger(LinuxTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 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 NetworkManager networkManager;
private final LocationUtils locationUtils;
private final EventBus eventBus;
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
private final ResourceProvider resourceProvider;
private final CircumventionProvider circumventionProvider;
private final Clock clock;
private final File torDirectory;
public LinuxTorPluginFactory(Executor ioExecutor,
NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, Clock clock,
File torDirectory) {
this.ioExecutor = ioExecutor;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.clock = clock;
this.torDirectory = torDirectory;
}
@Override
public TransportId getId() {
return TorConstants.ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = null;
if (isLinux()) {
String arch = System.getProperty("os.arch");
if (arch.equals("amd64")) {
architecture = "linux-x86_64";
}
}
if (architecture == null) {
LOG.info("Tor is not supported on this architecture");
return null;
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
LinuxTorPlugin plugin =
new LinuxTorPlugin(ioExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, backoff, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory);
eventBus.addListener(plugin);
return plugin;
}
}

View File

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

View File

@@ -0,0 +1,27 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.LocationUtils;
import java.util.Locale;
import java.util.logging.Logger;
import javax.inject.Inject;
@NotNullByDefault
class JavaLocationUtils implements LocationUtils {
private static final Logger LOG =
Logger.getLogger(JavaLocationUtils.class.getName());
@Inject
JavaLocationUtils() {
}
@Override
public String getCurrentCountry() {
LOG.info("Using user-defined locale");
return Locale.getDefault().getCountry();
}
}

View File

@@ -0,0 +1,22 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.InputStream;
import javax.inject.Inject;
@NotNullByDefault
class JavaResourceProvider implements ResourceProvider {
@Inject
JavaResourceProvider() {
}
@Override
public InputStream getResourceInputStream(String name, String extension) {
return getClass().getClassLoader()
.getResourceAsStream(name + extension);
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class JavaSystemModule {
@Provides
@Singleton
LocationUtils provideLocationUtils(JavaLocationUtils locationUtils) {
return locationUtils;
}
@Provides
@Singleton
ResourceProvider provideResourceProvider(JavaResourceProvider provider) {
return provider;
}
}