From 6cc8463209de195e72d2aecfe9f3a580b4a419fb Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Sat, 24 Nov 2012 13:35:23 +0000
Subject: [PATCH] First pass at a modem plugin (unfinished).

---
 src/net/sf/briar/plugins/modem/Modem.java     |   2 +-
 .../sf/briar/plugins/modem/ModemFactory.java  |   6 +
 .../briar/plugins/modem/ModemFactoryImpl.java |  16 ++
 .../sf/briar/plugins/modem/ModemPlugin.java   | 150 ++++++++++++++++--
 .../plugins/modem/ModemPluginFactory.java     |   4 +-
 5 files changed, 165 insertions(+), 13 deletions(-)
 create mode 100644 src/net/sf/briar/plugins/modem/ModemFactory.java
 create mode 100644 src/net/sf/briar/plugins/modem/ModemFactoryImpl.java

diff --git a/src/net/sf/briar/plugins/modem/Modem.java b/src/net/sf/briar/plugins/modem/Modem.java
index e758ef8b82..42c3693bf3 100644
--- a/src/net/sf/briar/plugins/modem/Modem.java
+++ b/src/net/sf/briar/plugins/modem/Modem.java
@@ -6,7 +6,7 @@ import java.io.OutputStream;
 
 /**
  * A modem that can be used for multiple sequential incoming and outgoing
- * calls. If the modem or its input or output streams throw an exception it
+ * calls. If the modem or its input or output streams throw any exceptions they
  * cannot continue to be used.
  */
 interface Modem {
diff --git a/src/net/sf/briar/plugins/modem/ModemFactory.java b/src/net/sf/briar/plugins/modem/ModemFactory.java
new file mode 100644
index 0000000000..36328c49c2
--- /dev/null
+++ b/src/net/sf/briar/plugins/modem/ModemFactory.java
@@ -0,0 +1,6 @@
+package net.sf.briar.plugins.modem;
+
+interface ModemFactory {
+
+	Modem createModem(Modem.Callback callback, String portName);
+}
diff --git a/src/net/sf/briar/plugins/modem/ModemFactoryImpl.java b/src/net/sf/briar/plugins/modem/ModemFactoryImpl.java
new file mode 100644
index 0000000000..1abf52b11c
--- /dev/null
+++ b/src/net/sf/briar/plugins/modem/ModemFactoryImpl.java
@@ -0,0 +1,16 @@
+package net.sf.briar.plugins.modem;
+
+import java.util.concurrent.Executor;
+
+class ModemFactoryImpl implements ModemFactory {
+
+	private final Executor executor;
+
+	ModemFactoryImpl(Executor executor) {
+		this.executor = executor;
+	}
+
+	public Modem createModem(Modem.Callback callback, String portName) {
+		return new ModemImpl(executor, callback, portName);
+	}
+}
diff --git a/src/net/sf/briar/plugins/modem/ModemPlugin.java b/src/net/sf/briar/plugins/modem/ModemPlugin.java
index bb4065dbf2..e51dd7b035 100644
--- a/src/net/sf/briar/plugins/modem/ModemPlugin.java
+++ b/src/net/sf/briar/plugins/modem/ModemPlugin.java
@@ -1,20 +1,34 @@
 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.concurrent.Semaphore;
 import java.util.logging.Logger;
 
+import jssc.SerialPortList;
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.crypto.PseudoRandom;
-import net.sf.briar.api.plugins.PluginCallback;
 import net.sf.briar.api.plugins.PluginExecutor;
 import net.sf.briar.api.plugins.duplex.DuplexPlugin;
+import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
 import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
 import net.sf.briar.api.protocol.TransportId;
 import net.sf.briar.util.StringUtils;
 
-class ModemPlugin implements DuplexPlugin {
+class ModemPlugin implements DuplexPlugin, Modem.Callback {
 
 	static final byte[] TRANSPORT_ID =
 			StringUtils.fromHexString("8f573867bedf54884b5868ee5d902832" +
@@ -26,14 +40,22 @@ class ModemPlugin implements DuplexPlugin {
 			Logger.getLogger(ModemPlugin.class.getName());
 
 	private final Executor pluginExecutor;
-	private final PluginCallback callback;
+	private final ModemFactory modemFactory;
+	private final DuplexPluginCallback callback;
 	private final long pollingInterval;
+	private final Semaphore polling;
+
+	private volatile boolean running = false;
+	private volatile Modem modem = null;
 
 	ModemPlugin(@PluginExecutor Executor pluginExecutor,
-			PluginCallback callback, long pollingInterval) {
+			ModemFactory modemFactory, DuplexPluginCallback callback,
+			long pollingInterval) {
 		this.pluginExecutor = pluginExecutor;
+		this.modemFactory = modemFactory;
 		this.callback = callback;
 		this.pollingInterval = pollingInterval;
+		polling = new Semaphore(1);
 	}
 
 	public TransportId getId() {
@@ -44,13 +66,22 @@ class ModemPlugin implements DuplexPlugin {
 		return "MODEM_PLUGIN_NAME";
 	}
 
-	public boolean start() throws IOException {
-		// FIXME
+	public boolean start() {
+		for(String portName : SerialPortList.getPortNames()) {
+			modem = modemFactory.createModem(this, portName);
+			try {
+				modem.init();
+				running = true;
+				return true;
+			} catch(IOException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			}
+		}
 		return false;
 	}
 
-	public void stop() throws IOException {
-		// FIXME
+	public void stop() {
+		running = false;
 	}
 
 	public boolean shouldPoll() {
@@ -62,12 +93,68 @@ class ModemPlugin implements DuplexPlugin {
 	}
 
 	public void poll(Collection<ContactId> connected) {
-		// FIXME
+		if(!connected.isEmpty()) return; // One at a time please
+		pluginExecutor.execute(new Runnable() {
+			public void run() {
+				poll();
+			}
+		});
+	}
+
+	private void poll() {
+		if(!running) return;
+		if(!polling.tryAcquire()) {
+			if(LOG.isLoggable(INFO))
+				LOG.info("Previous poll still in progress");
+			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());
+		Collections.shuffle(contacts);
+		Iterator<ContactId> it = contacts.iterator();
+		while(it.hasNext() && running) {
+			ContactId c = it.next();
+			String number = remote.get(c).get("number");
+			if(number == null) continue;
+			try {
+				if(!modem.dial(number)) continue;
+			} catch(IOException e) {
+				// FIXME: Race condition with stop()
+				running = false;
+				if(start()) continue;
+				else break;
+			}
+			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;
+			}
+		}
+		polling.release();
 	}
 
 	public DuplexTransportConnection createConnection(ContactId c) {
-		// FIXME
-		return null;
+		if(!running) return null;
+		final Map<ContactId, TransportProperties> remote =
+				callback.getRemoteProperties();
+		String number = remote.get(c).get("number");
+		if(number == null) return null;
+		try {
+			if(!modem.dial(number)) return null;
+		} catch(IOException e) {
+			// FIXME: Race condition with stop()
+			running = false;
+			start();
+			return null;
+		}
+		return new ModemTransportConnection();
 	}
 
 	public boolean supportsInvitations() {
@@ -83,4 +170,45 @@ class ModemPlugin implements DuplexPlugin {
 			long timeout) {
 		throw new UnsupportedOperationException();
 	}
+
+	public void incomingCallConnected() {
+		callback.incomingConnectionCreated(new ModemTransportConnection());
+	}
+
+	private class ModemTransportConnection
+	implements DuplexTransportConnection {
+
+		private final CountDownLatch finished = new CountDownLatch(1);
+
+		public InputStream getInputStream() {
+			return modem.getInputStream();
+		}
+
+		public OutputStream getOutputStream() {
+			return modem.getOutputStream();
+		}
+
+		public boolean shouldFlush() {
+			return true;
+		}
+
+		public void dispose(boolean exception, boolean recognised) {
+			try {
+				modem.hangUp();
+			} catch(IOException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+				exception = true;
+			}
+			if(exception) {
+				// FIXME: Race condition with stop()
+				running = false;
+				start();
+			}
+			finished.countDown();
+		}
+
+		private void waitForDisposal() throws InterruptedException {
+			finished.await();
+		}
+	}
 }
diff --git a/src/net/sf/briar/plugins/modem/ModemPluginFactory.java b/src/net/sf/briar/plugins/modem/ModemPluginFactory.java
index 3b7e81588b..558edcc393 100644
--- a/src/net/sf/briar/plugins/modem/ModemPluginFactory.java
+++ b/src/net/sf/briar/plugins/modem/ModemPluginFactory.java
@@ -28,6 +28,8 @@ public class ModemPluginFactory implements DuplexPluginFactory {
 		// This plugin is not enabled by default
 		String enabled = callback.getConfig().get("enabled");
 		if(StringUtils.isNullOrEmpty(enabled)) return null;
-		return new ModemPlugin(pluginExecutor, callback, POLLING_INTERVAL);
+		ModemFactory modemFactory = new ModemFactoryImpl(pluginExecutor);
+		return new ModemPlugin(pluginExecutor, modemFactory, callback,
+				POLLING_INTERVAL);
 	}
 }
-- 
GitLab