diff --git a/briar-core/.classpath b/briar-core/.classpath index c7e16820b18899e4643415bfc41a04a22e45c467..770f9ed479c44e2b2926b092662f8571347c05c5 100644 --- a/briar-core/.classpath +++ b/briar-core/.classpath @@ -12,7 +12,7 @@ <classpathentry kind="lib" path="libs/bluecove-2.1.1-SNAPSHOT-briar.jar"/> <classpathentry kind="lib" path="libs/bluecove-gpl-2.1.1-SNAPSHOT.jar"/> <classpathentry kind="lib" path="libs/javax.inject.jar"/> - <classpathentry kind="lib" path="libs/jtorctl.jar" sourcepath="libs/source/jtorctl-source.jar"/> + <classpathentry kind="lib" path="/home/someone/workspace/prototype/briar-core/libs/jtorctl-briar.jar" sourcepath="libs/source/jtorctl-briar-source.jar"/> <classpathentry kind="lib" path="libs/jsocks.jar" sourcepath="libs/source/jsocks-source.jar"/> <classpathentry combineaccessrules="false" kind="src" path="/briar-api"/> <classpathentry kind="lib" path="/briar-api/libs/android.jar"/> diff --git a/briar-core/libs/jtorctl-briar.jar b/briar-core/libs/jtorctl-briar.jar new file mode 100644 index 0000000000000000000000000000000000000000..6e1dadeb3f2818337c46f4784c6e21c6f85006a3 Binary files /dev/null and b/briar-core/libs/jtorctl-briar.jar differ diff --git a/briar-core/libs/jtorctl.jar b/briar-core/libs/jtorctl.jar deleted file mode 100644 index cf757254a690075e14efd36cb7e437d86abeb413..0000000000000000000000000000000000000000 Binary files a/briar-core/libs/jtorctl.jar and /dev/null differ diff --git a/briar-core/libs/source/jtorctl-briar-source.jar b/briar-core/libs/source/jtorctl-briar-source.jar new file mode 100644 index 0000000000000000000000000000000000000000..082613caffc87c78850e27def7ea5aeb73f83fbf Binary files /dev/null and b/briar-core/libs/source/jtorctl-briar-source.jar differ diff --git a/briar-core/libs/source/jtorctl-source.jar b/briar-core/libs/source/jtorctl-source.jar deleted file mode 100644 index 1550912ea87917dc236fd11a23cd1301cc14b9fc..0000000000000000000000000000000000000000 Binary files a/briar-core/libs/source/jtorctl-source.jar and /dev/null differ diff --git a/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java b/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java index cdf0999a822ac36c8b008a545cf431e495b44231..14e63cf70ba9322c451e3e6c2e6868f9faccefbe 100644 --- a/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java +++ b/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java @@ -73,6 +73,8 @@ class TorPlugin implements DuplexPlugin, EventHandler { private volatile Process tor = null; private volatile int pid = -1; private volatile ServerSocket socket = null; + private volatile Socket controlSocket = null; + private volatile TorControlConnection controlConnection = null; TorPlugin(Executor pluginExecutor, Context appContext, ShutdownManager shutdownManager, DuplexPluginCallback callback, @@ -113,9 +115,8 @@ class TorPlugin implements DuplexPlugin, EventHandler { return false; } // Try to connect to an existing Tor process if there is one - Socket s; try { - s = new Socket("127.0.0.1", CONTROL_PORT); // FIXME: Never closed + controlSocket = new Socket("127.0.0.1", CONTROL_PORT); if(LOG.isLoggable(INFO)) LOG.info("Tor is already running"); } catch(IOException e) { // Install the binary, GeoIP database and config file if necessary @@ -168,7 +169,7 @@ class TorPlugin implements DuplexPlugin, EventHandler { return false; } // Now we should be able to connect to the new process - s = new Socket("127.0.0.1", CONTROL_PORT); // FIXME: Never closed + controlSocket = new Socket("127.0.0.1", CONTROL_PORT); } // Read the PID of the Tor process so we can kill it if necessary try { @@ -187,12 +188,11 @@ class TorPlugin implements DuplexPlugin, EventHandler { } }); // Open a control connection and authenticate using the cookie file - TorControlConnection control = new TorControlConnection(s); - control.launchThread(true); - control.authenticate(read(cookieFile)); + controlConnection = new TorControlConnection(controlSocket); + controlConnection.authenticate(read(cookieFile)); // Register to receive events from the Tor process - control.setEventHandler(this); - control.setEvents(Arrays.asList("NOTICE", "WARN", "ERR")); + controlConnection.setEventHandler(this); + controlConnection.setEvents(Arrays.asList("NOTICE", "WARN", "ERR")); running = true; // Bind a server socket to receive incoming hidden service connections pluginExecutor.execute(new Runnable() { @@ -306,6 +306,11 @@ class TorPlugin implements DuplexPlugin, EventHandler { } } + private void listFiles(File f) { + if(f.isDirectory()) for(File f1 : f.listFiles()) listFiles(f1); + else if(LOG.isLoggable(INFO)) LOG.info(f.getAbsolutePath()); + } + private byte[] read(File f) throws IOException { byte[] b = new byte[(int) f.length()]; FileInputStream in = new FileInputStream(f); @@ -376,17 +381,12 @@ class TorPlugin implements DuplexPlugin, EventHandler { CountDownLatch latch = new CountDownLatch(1); FileObserver obs = new WriteObserver(hostnameFile, latch); obs.startWatching(); - // Open a control connection and update the Tor config + // Use the control connection to update the Tor config List<String> config = Arrays.asList( "HiddenServiceDir " + torDirectory.getAbsolutePath(), "HiddenServicePort 80 127.0.0.1:" + port); - // FIXME: Socket isn't closed - Socket s = new Socket("127.0.0.1", CONTROL_PORT); - TorControlConnection control = new TorControlConnection(s); - control.launchThread(true); - control.authenticate(read(cookieFile)); - control.setConf(config); - control.saveConf(); + controlConnection.setConf(config); + controlConnection.saveConf(); // Wait for the hostname file to be created/updated if(!latch.await(HOSTNAME_TIMEOUT, MILLISECONDS)) { if(LOG.isLoggable(WARNING)) @@ -437,12 +437,14 @@ class TorPlugin implements DuplexPlugin, EventHandler { if(socket != null) tryToClose(socket); try { if(LOG.isLoggable(INFO)) LOG.info("Stopping Tor"); - // FIXME: Socket isn't closed - Socket s = new Socket("127.0.0.1", CONTROL_PORT); - TorControlConnection control = new TorControlConnection(s); - control.launchThread(true); - control.authenticate(read(cookieFile)); - control.shutdownTor("TERM"); + if(controlSocket == null) + controlSocket = new Socket("127.0.0.1", CONTROL_PORT); + if(controlConnection == null) { + controlConnection = new TorControlConnection(controlSocket); + controlConnection.authenticate(read(cookieFile)); + } + controlConnection.shutdownTor("TERM"); + controlSocket.close(); } catch(IOException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(INFO)) LOG.info("Killing Tor"); @@ -515,38 +517,21 @@ class TorPlugin implements DuplexPlugin, EventHandler { throw new UnsupportedOperationException(); } - private void listFiles(File f) { - if(f.isDirectory()) for(File f1 : f.listFiles()) listFiles(f1); - else if(LOG.isLoggable(INFO)) LOG.info(f.getAbsolutePath()); - } - - public void circuitStatus(String status, String circID, String path) { - if(LOG.isLoggable(INFO)) LOG.info("Circuit status"); - } + public void circuitStatus(String status, String circID, String path) {} - public void streamStatus(String status, String streamID, String target) { - if(LOG.isLoggable(INFO)) LOG.info("Stream status"); - } + public void streamStatus(String status, String streamID, String target) {} - public void orConnStatus(String status, String orName) { - if(LOG.isLoggable(INFO)) LOG.info("OR connection status"); - } + public void orConnStatus(String status, String orName) {} - public void bandwidthUsed(long read, long written) { - if(LOG.isLoggable(INFO)) LOG.info("Bandwidth used"); - } + public void bandwidthUsed(long read, long written) {} - public void newDescriptors(List<String> orList) { - if(LOG.isLoggable(INFO)) LOG.info("New descriptors"); - } + public void newDescriptors(List<String> orList) {} public void message(String severity, String msg) { - if(LOG.isLoggable(INFO)) LOG.info("Message: " + severity + " " + msg); + if(LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); } - public void unrecognized(String type, String msg) { - if(LOG.isLoggable(INFO)) LOG.info("Unrecognized"); - } + public void unrecognized(String type, String msg) {} private static class WriteObserver extends FileObserver { diff --git a/jtorctl.patch b/jtorctl.patch new file mode 100644 index 0000000000000000000000000000000000000000..f50af04820966b8b75d2c76994732307dda28c57 --- /dev/null +++ b/jtorctl.patch @@ -0,0 +1,1686 @@ +diff -Bbur jtorctl/net/freehaven/tor/control/Bytes.java jtorctl-briar/net/freehaven/tor/control/Bytes.java +--- jtorctl/net/freehaven/tor/control/Bytes.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/Bytes.java 2013-05-16 19:56:30.000000000 +0100 +@@ -16,46 +16,43 @@ + /** Write the two-byte value in 's' into the byte array 'ba', starting at + * the index 'pos'. */ + public static void setU16(byte[] ba, int pos, short s) { +- ba[pos] = (byte)((s >> 8) & 0xff); +- ba[pos+1] = (byte)((s ) & 0xff); ++ ba[pos] = (byte) ((s >> 8) & 0xff); ++ ba[pos + 1] = (byte) (s & 0xff); + } + + /** Write the four-byte value in 'i' into the byte array 'ba', starting at + * the index 'pos'. */ + public static void setU32(byte[] ba, int pos, int i) { +- ba[pos] = (byte)((i >> 24) & 0xff); +- ba[pos+1] = (byte)((i >> 16) & 0xff); +- ba[pos+2] = (byte)((i >> 8) & 0xff); +- ba[pos+3] = (byte)((i ) & 0xff); ++ ba[pos] = (byte) ((i >> 24) & 0xff); ++ ba[pos + 1] = (byte) ((i >> 16) & 0xff); ++ ba[pos + 2] = (byte) ((i >> 8) & 0xff); ++ ba[pos + 3] = (byte) (i & 0xff); + } + + /** Return the four-byte value starting at index 'pos' within 'ba' */ + public static int getU32(byte[] ba, int pos) { +- return +- ((ba[pos ]&0xff)<<24) | +- ((ba[pos+1]&0xff)<<16) | +- ((ba[pos+2]&0xff)<< 8) | +- ((ba[pos+3]&0xff)); ++ return ((ba[pos] & 0xff) << 24) | ++ ((ba[pos + 1] & 0xff) << 16) | ++ ((ba[pos + 2] & 0xff) << 8) | ++ ((ba[pos + 3] & 0xff)); + } + + public static String getU32S(byte[] ba, int pos) { +- return String.valueOf( (getU32(ba,pos))&0xffffffffL ); ++ return String.valueOf((getU32(ba, pos)) & 0xffffffffL); + } + + /** Return the two-byte value starting at index 'pos' within 'ba' */ + public static int getU16(byte[] ba, int pos) { +- return +- ((ba[pos ]&0xff)<<8) | +- ((ba[pos+1]&0xff)); ++ return ((ba[pos] & 0xff) << 8) | ++ ((ba[pos + 1] & 0xff)); + } + + /** Return the string starting at position 'pos' of ba and extending + * until a zero byte or the end of the string. */ + public static String getNulTerminatedStr(byte[] ba, int pos) { +- int len, maxlen = ba.length-pos; +- for (len=0; len<maxlen; ++len) { +- if (ba[pos+len] == 0) +- break; ++ int len, maxlen = ba.length - pos; ++ for(len = 0; len < maxlen; ++len) { ++ if(ba[pos + len] == 0) break; + } + return new String(ba, pos, len); + } +@@ -64,18 +61,16 @@ + * Read bytes from 'ba' starting at 'pos', dividing them into strings + * along the character in 'split' and writing them into 'lst' + */ +- public static void splitStr(List<String> lst, byte[] ba, int pos, byte split) { +- while (pos < ba.length && ba[pos] != 0) { ++ public static void splitStr(List<String> lst, byte[] ba, int pos, ++ byte split) { ++ while(pos < ba.length && ba[pos] != 0) { + int len; +- for (len=0; pos+len < ba.length; ++len) { +- if (ba[pos+len] == 0 || ba[pos+len] == split) +- break; ++ for(len = 0; pos + len < ba.length; ++len) { ++ if(ba[pos + len] == 0 || ba[pos + len] == split) break; + } +- if (len>0) +- lst.add(new String(ba, pos, len)); ++ if(len > 0) lst.add(new String(ba, pos, len)); + pos += len; +- if (ba[pos] == split) +- ++pos; ++ if(ba[pos] == split) ++pos; + } + } + +@@ -86,11 +81,8 @@ + public static List<String> splitStr(List<String> lst, String str) { + // split string on spaces, include trailing/leading + String[] tokenArray = str.split(" ", -1); +- if (lst == null) { +- lst = Arrays.asList( tokenArray ); +- } else { +- lst.addAll( Arrays.asList( tokenArray ) ); +- } ++ if(lst == null) lst = Arrays.asList(tokenArray); ++ else lst.addAll(Arrays.asList(tokenArray)); + return lst; + } + +@@ -101,10 +93,10 @@ + + public static final String hex(byte[] ba) { + StringBuffer buf = new StringBuffer(); +- for (int i = 0; i < ba.length; ++i) { +- int b = (ba[i]) & 0xff; ++ for(int i = 0; i < ba.length; ++i) { ++ int b = ba[i] & 0xff; + buf.append(NYBBLES[b >> 4]); +- buf.append(NYBBLES[b&0x0f]); ++ buf.append(NYBBLES[b & 0x0f]); + } + return buf.toString(); + } +diff -Bbur jtorctl/net/freehaven/tor/control/ConfigEntry.java jtorctl-briar/net/freehaven/tor/control/ConfigEntry.java +--- jtorctl/net/freehaven/tor/control/ConfigEntry.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/ConfigEntry.java 2013-05-16 19:56:30.000000000 +0100 +@@ -4,6 +4,11 @@ + + /** A single key-value pair from Tor's configuration. */ + public class ConfigEntry { ++ ++ public final String key; ++ public final String value; ++ public final boolean is_default; ++ + public ConfigEntry(String k, String v) { + key = k; + value = v; +@@ -14,7 +20,4 @@ + value = ""; + is_default = true; + } +- public final String key; +- public final String value; +- public final boolean is_default; + } +diff -Bbur jtorctl/net/freehaven/tor/control/EventHandler.java jtorctl-briar/net/freehaven/tor/control/EventHandler.java +--- jtorctl/net/freehaven/tor/control/EventHandler.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/EventHandler.java 2013-05-16 19:56:30.000000000 +0100 +@@ -9,9 +9,10 @@ + * @see TorControlConnection#setEvents + */ + public interface EventHandler { ++ + /** +- * Invoked when a circuit's status has changed. +- * Possible values for <b>status</b> are: ++ * Invoked when a circuit's status has changed. Possible values for ++ * <b>status</b> are: + * <ul> + * <li>"LAUNCHED" : circuit ID assigned to new circuit</li> + * <li>"BUILT" : all hops finished, can now accept streams</li> +@@ -19,7 +20,6 @@ + * <li>"FAILED" : circuit closed (was not built)</li> + * <li>"CLOSED" : circuit closed (was built)</li> + * </ul> +- * + * <b>circID</b> is the alphanumeric identifier of the affected circuit, + * and <b>path</b> is a comma-separated list of alphanumeric ServerIDs. + */ +@@ -24,9 +24,10 @@ + * and <b>path</b> is a comma-separated list of alphanumeric ServerIDs. + */ + public void circuitStatus(String status, String circID, String path); ++ + /** +- * Invoked when a stream's status has changed. +- * Possible values for <b>status</b> are: ++ * Invoked when a stream's status has changed. Possible values for ++ * <b>status</b> are: + * <ul> + * <li>"NEW" : New request to connect</li> + * <li>"NEWRESOLVE" : New request to resolve an address</li> +@@ -37,7 +38,6 @@ + * <li>"CLOSED" : Stream closed</li> + * <li>"DETACHED" : Detached from circuit; still retriable.</li> + * </ul> +- * + * <b>streamID</b> is the alphanumeric identifier of the affected stream, + * and its <b>target</b> is specified as address:port. + */ +@@ -42,18 +42,21 @@ + * and its <b>target</b> is specified as address:port. + */ + public void streamStatus(String status, String streamID, String target); ++ + /** +- * Invoked when the status of a connection to an OR has changed. +- * Possible values for <b>status</b> are ["LAUNCHED" | "CONNECTED" | "FAILED" | "CLOSED"]. +- * <b>orName</b> is the alphanumeric identifier of the OR affected. ++ * Invoked when the status of a connection to an OR has changed. Possible ++ * values for <b>status</b> are ["LAUNCHED" | "CONNECTED" | "FAILED" | ++ * "CLOSED"]. <b>orName</b> is the alphanumeric identifier of the OR ++ * affected. + */ + public void orConnStatus(String status, String orName); ++ + /** +- * Invoked once per second. <b>read</b> and <b>written</b> are +- * the number of bytes read and written, respectively, in +- * the last second. ++ * Invoked once per second. <b>read</b> and <b>written</b> are the number ++ * of bytes read and written, respectively, in the last second. + */ + public void bandwidthUsed(long read, long written); ++ + /** + * Invoked whenever Tor learns about new ORs. The <b>orList</b> object + * contains the alphanumeric ServerIDs associated with the new ORs. +@@ -59,17 +62,18 @@ + * contains the alphanumeric ServerIDs associated with the new ORs. + */ + public void newDescriptors(java.util.List<String> orList); ++ + /** +- * Invoked when Tor logs a message. +- * <b>severity</b> is one of ["DEBUG" | "INFO" | "NOTICE" | "WARN" | "ERR"], +- * and <b>msg</b> is the message string. ++ * Invoked when Tor logs a message. <b>severity</b> is one of ["DEBUG" | ++ * "INFO" | "NOTICE" | "WARN" | "ERR"], and <b>msg</b> is the message ++ * string. + */ + public void message(String severity, String msg); ++ + /** +- * Invoked when an unspecified message is received. +- * <type> is the message type, and <msg> is the message string. ++ * Invoked when an unspecified message is received. <type> is the message ++ * type, and <msg> is the message string. + */ + public void unrecognized(String type, String msg); +- + } + +diff -Bbur jtorctl/net/freehaven/tor/control/examples/DebuggingEventHandler.java jtorctl-briar/net/freehaven/tor/control/examples/DebuggingEventHandler.java +--- jtorctl/net/freehaven/tor/control/examples/DebuggingEventHandler.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/examples/DebuggingEventHandler.java 2013-05-16 19:56:30.000000000 +0100 +@@ -3,42 +3,48 @@ + package net.freehaven.tor.control.examples; + + import java.io.PrintWriter; +-import java.util.Iterator; ++import java.util.List; ++ + import net.freehaven.tor.control.EventHandler; + + public class DebuggingEventHandler implements EventHandler { + +- protected PrintWriter out; ++ private final PrintWriter out; + +- public DebuggingEventHandler(PrintWriter p) { +- out = p; ++ public DebuggingEventHandler(PrintWriter out) { ++ this.out = out; + } + + public void circuitStatus(String status, String circID, String path) { +- out.println("Circuit "+circID+" is now "+status+" (path="+path+")"); ++ out.println("Circuit " + circID + " is now " + status + ++ " (path=" + path + ")"); + } ++ + public void streamStatus(String status, String streamID, String target) { +- out.println("Stream "+streamID+" is now "+status+" (target="+target+")"); ++ out.println("Stream " + streamID + " is now " + status + ++ " (target=" + target + ")"); + } ++ + public void orConnStatus(String status, String orName) { +- out.println("OR connection to "+orName+" is now "+status); ++ out.println("OR connection to " + orName + " is now " + status); + } ++ + public void bandwidthUsed(long read, long written) { +- out.println("Bandwidth usage: "+read+" bytes read; "+ +- written+" bytes written."); ++ out.println("Bandwidth usage: " + read + " bytes read; " + ++ written +" bytes written."); + } +- public void newDescriptors(java.util.List<String> orList) { ++ ++ public void newDescriptors(List<String> orList) { + out.println("New descriptors for routers:"); +- for (Iterator<String> i = orList.iterator(); i.hasNext(); ) +- out.println(" "+i.next()); ++ for(String or : orList) out.println(" " + or); + } ++ + public void message(String type, String msg) { +- out.println("["+type+"] "+msg.trim()); ++ out.println("[" + type + "] " + msg.trim()); + } + + public void unrecognized(String type, String msg) { +- out.println("unrecognized event ["+type+"] "+msg.trim()); ++ out.println("unrecognized event [" + type + "] " + msg.trim()); + } +- + } + +diff -Bbur jtorctl/net/freehaven/tor/control/examples/Main.java jtorctl-briar/net/freehaven/tor/control/examples/Main.java +--- jtorctl/net/freehaven/tor/control/examples/Main.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/examples/Main.java 2013-05-16 19:56:30.000000000 +0100 +@@ -2,59 +2,60 @@ + // See LICENSE file for copying information + package net.freehaven.tor.control.examples; + +-import net.freehaven.tor.control.*; +-import java.io.PrintWriter; ++import java.io.EOFException; + import java.io.IOException; ++import java.io.PrintWriter; ++import java.net.Socket; + import java.util.ArrayList; +-import java.util.List; + import java.util.Arrays; ++import java.util.List; + import java.util.Map; +-import java.util.Iterator; ++ ++import net.freehaven.tor.control.ConfigEntry; ++import net.freehaven.tor.control.PasswordDigest; ++import net.freehaven.tor.control.TorControlCommands; ++import net.freehaven.tor.control.TorControlConnection; ++import net.freehaven.tor.control.TorControlError; + + public class Main implements TorControlCommands { + + public static void main(String args[]) { +- if (args.length < 1) { ++ if(args.length < 1) { + System.err.println("No command given."); + return; + } + try { +- if (args[0].equals("set-config")) { ++ if(args[0].equals("set-config")) { + setConfig(args); +- } else if (args[0].equals("get-config")) { ++ } else if(args[0].equals("get-config")) { + getConfig(args); +- } else if (args[0].equals("get-info")) { ++ } else if(args[0].equals("get-info")) { + getInfo(args); +- } else if (args[0].equals("listen")) { ++ } else if(args[0].equals("listen")) { + listenForEvents(args); +- } else if (args[0].equals("signal")) { ++ } else if(args[0].equals("signal")) { + signal(args); +- } else if (args[0].equals("auth")) { ++ } else if(args[0].equals("auth")) { + authDemo(args); + } else { + System.err.println("Unrecognized command: "+args[0]); + } +- } catch (java.io.EOFException ex) { ++ } catch(EOFException ex) { + System.out.println("Control socket closed by Tor."); +- } catch (IOException ex) { +- System.err.println("IO exception when talking to Tor process: "+ ++ } catch(TorControlError ex) { ++ System.err.println("Error from Tor process: " + ++ ex + " [" + ex.getErrorMsg() + "]"); ++ } catch(IOException ex) { ++ System.err.println("IO exception when talking to Tor process: " + + ex); + ex.printStackTrace(System.err); +- } catch (TorControlError ex) { +- System.err.println("Error from Tor process: "+ +- ex+" ["+ex.getErrorMsg()+"]"); + } + } + + private static TorControlConnection getConnection(String[] args, +- boolean daemon) +- throws IOException { +- TorControlConnection conn = TorControlConnection.getConnection( +- new java.net.Socket("127.0.0.1", 9100)); +- //if (conn instanceof TorControlConnection1) { +- // System.err.println("Debugging"); +- // ((TorControlConnection1)conn).setDebugging(System.err); +- //} ++ boolean daemon) throws IOException { ++ Socket s = new Socket("127.0.0.1", 9100); ++ TorControlConnection conn = new TorControlConnection(s); + conn.launchThread(daemon); + conn.authenticate(new byte[0]); + return conn; +@@ -71,57 +72,52 @@ + ArrayList<String> lst = new ArrayList<String>(); + int i = 1; + boolean save = false; +- if (args[i].equals("-save")) { ++ if(args[i].equals("-save")) { + save = true; + ++i; + } +- for (; i < args.length; i +=2) { +- lst.add(args[i]+" "+args[i+1]); ++ for(; i < args.length; i +=2) { ++ lst.add(args[i] + " " + args[i + 1]); + } + conn.setConf(lst); +- if (save) { +- conn.saveConf(); +- } ++ if(save) conn.saveConf(); + } + + public static void getConfig(String[] args) throws IOException { + // Usage: get-config key key key + TorControlConnection conn = getConnection(args); +- List<ConfigEntry> lst = conn.getConf(Arrays.asList(args).subList(1,args.length)); +- for (Iterator<ConfigEntry> i = lst.iterator(); i.hasNext(); ) { +- ConfigEntry e = i.next(); +- System.out.println("KEY: "+e.key); +- System.out.println("VAL: "+e.value); ++ List<String> keys = Arrays.asList(args).subList(1, args.length); ++ List<ConfigEntry> lst = conn.getConf(keys); ++ for(ConfigEntry e : lst) { ++ System.out.println("KEY: " + e.key); ++ System.out.println("VAL: " + e.value); + } + } + + public static void getInfo(String[] args) throws IOException { + TorControlConnection conn = getConnection(args); +- Map<String,String> m = conn.getInfo(Arrays.asList(args).subList(1,args.length)); +- for (Iterator<Map.Entry<String, String>> i = m.entrySet().iterator(); i.hasNext(); ) { +- Map.Entry<String,String> e = i.next(); +- System.out.println("KEY: "+e.getKey()); +- System.out.println("VAL: "+e.getValue()); ++ List<String> keys = Arrays.asList(args).subList(1, args.length); ++ Map<String, String> m = conn.getInfo(keys); ++ for(Map.Entry<String, String> e : m.entrySet()) { ++ System.out.println("KEY: " + e.getKey()); ++ System.out.println("VAL: " + e.getValue()); + } + } + + public static void listenForEvents(String[] args) throws IOException { + // Usage: listen [circ|stream|orconn|bw|newdesc|info|notice|warn|error]* + TorControlConnection conn = getConnection(args, false); +- ArrayList<String> lst = new ArrayList<String>(); +- for (int i = 1; i < args.length; ++i) { +- lst.add(args[i]); +- } ++ List<String> events = Arrays.asList(args).subList(1, args.length); + conn.setEventHandler( + new DebuggingEventHandler(new PrintWriter(System.out, true))); +- conn.setEvents(lst); ++ conn.setEvents(events); + } + + public static void signal(String[] args) throws IOException { + // Usage signal [reload|shutdown|dump|debug|halt] + TorControlConnection conn = getConnection(args, false); + // distinguish shutdown signal from other signals +- if ("SHUTDOWN".equalsIgnoreCase(args[1]) ++ if("SHUTDOWN".equalsIgnoreCase(args[1]) + || "HALT".equalsIgnoreCase(args[1])) { + conn.shutdownTor(args[1].toUpperCase()); + } else { +@@ -130,17 +126,13 @@ + } + + public static void authDemo(String[] args) throws IOException { +- + PasswordDigest pwd = PasswordDigest.generateDigest(); +- java.net.Socket s = new java.net.Socket("127.0.0.1", 9100); +- TorControlConnection conn = TorControlConnection.getConnection(s); ++ Socket s = new Socket("127.0.0.1", 9100); ++ TorControlConnection conn = new TorControlConnection(s); + conn.launchThread(true); + conn.authenticate(new byte[0]); +- + conn.setConf("HashedControlPassword", pwd.getHashedPassword()); +- +- conn = TorControlConnection.getConnection( +- new java.net.Socket("127.0.0.1", 9100)); ++ conn = new TorControlConnection(new Socket("127.0.0.1", 9100)); + conn.launchThread(true); + conn.authenticate(pwd.getSecret()); + } +diff -Bbur jtorctl/net/freehaven/tor/control/PasswordDigest.java jtorctl-briar/net/freehaven/tor/control/PasswordDigest.java +--- jtorctl/net/freehaven/tor/control/PasswordDigest.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/PasswordDigest.java 2013-05-16 19:56:30.000000000 +0100 +@@ -2,19 +2,20 @@ + // See LICENSE file for copying information + package net.freehaven.tor.control; + ++import java.security.NoSuchAlgorithmException; + import java.security.SecureRandom; + import java.security.MessageDigest; + + /** +- * A hashed digest of a secret password (used to set control connection ++ * A hashed digest of a secret password(used to set control connection + * security.) + * + * For the actual hashing algorithm, see RFC2440's secret-to-key conversion. + */ + public class PasswordDigest { + +- byte[] secret; +- String hashedKey; ++ private final byte[] secret; ++ private final String hashedKey; + + /** Return a new password digest with a random secret and salt. */ + public static PasswordDigest generateDigest() { +@@ -35,17 +36,16 @@ + */ + public PasswordDigest(byte[] secret, byte[] specifier) { + this.secret = secret.clone(); +- if (specifier == null) { ++ if(specifier == null) { + specifier = new byte[9]; + SecureRandom rng = new SecureRandom(); + rng.nextBytes(specifier); + specifier[8] = 96; + } +- hashedKey = "16:"+encodeBytes(secretToKey(secret, specifier)); ++ hashedKey = "16:" + Bytes.hex(secretToKey(secret, specifier)); + } + +- /** Return the secret used to generate this password hash. +- */ ++ /** Return the secret used to generate this password hash. */ + public byte[] getSecret() { + return secret.clone(); + } +@@ -63,17 +63,17 @@ + MessageDigest d; + try { + d = MessageDigest.getInstance("SHA-1"); +- } catch (java.security.NoSuchAlgorithmException ex) { ++ } catch(NoSuchAlgorithmException ex) { + throw new RuntimeException("Can't run without sha-1."); + } +- int c = (specifier[8])&0xff; +- int count = (16 + (c&15)) << ((c>>4) + EXPBIAS); ++ int c = specifier[8] & 0xff; ++ int count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS); + +- byte[] tmp = new byte[8+secret.length]; ++ byte[] tmp = new byte[8 + secret.length]; + System.arraycopy(specifier, 0, tmp, 0, 8); + System.arraycopy(secret, 0, tmp, 8, secret.length); +- while (count > 0) { +- if (count >= tmp.length) { ++ while(count > 0) { ++ if(count >= tmp.length) { + d.update(tmp); + count -= tmp.length; + } else { +@@ -81,17 +81,9 @@ + count = 0; + } + } +- byte[] key = new byte[20+9]; ++ byte[] key = new byte[20 + 9]; + System.arraycopy(d.digest(), 0, key, 9, 20); + System.arraycopy(specifier, 0, key, 0, 9); + return key; + } +- +- /** Return a hexadecimal encoding of a byte array. */ +- // XXX There must be a better way to do this in Java. +- private static final String encodeBytes(byte[] ba) { +- return Bytes.hex(ba); +- } +- + } +- +diff -Bbur jtorctl/net/freehaven/tor/control/TorControlCommands.java jtorctl-briar/net/freehaven/tor/control/TorControlCommands.java +--- jtorctl/net/freehaven/tor/control/TorControlCommands.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/TorControlCommands.java 2013-05-16 19:56:30.000000000 +0100 +@@ -119,7 +119,10 @@ + public static final byte OR_CONN_STATUS_CLOSED = 0x03; + + public static final String[] OR_CONN_STATUS_NAMES = { +- "LAUNCHED","CONNECTED","FAILED","CLOSED" ++ "LAUNCHED", ++ "CONNECTED", ++ "FAILED", ++ "CLOSED" + }; + + public static final byte SIGNAL_HUP = 0x01; +diff -Bbur jtorctl/net/freehaven/tor/control/TorControlConnection.java jtorctl-briar/net/freehaven/tor/control/TorControlConnection.java +--- jtorctl/net/freehaven/tor/control/TorControlConnection.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/TorControlConnection.java 2013-05-16 19:56:30.000000000 +0100 +@@ -2,120 +2,107 @@ + // See LICENSE file for copying information + package net.freehaven.tor.control; + ++import java.io.BufferedReader; + import java.io.IOException; +-import java.net.SocketException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.io.OutputStream; ++import java.io.OutputStreamWriter; ++import java.io.PrintStream; ++import java.io.PrintWriter; ++import java.io.Reader; ++import java.io.Writer; ++import java.net.Socket; + import java.util.ArrayList; ++import java.util.Arrays; + import java.util.Collection; + import java.util.HashMap; +-import java.util.Iterator; + import java.util.LinkedList; + import java.util.List; + import java.util.Map; + import java.util.StringTokenizer; +-import java.util.concurrent.CancellationException; + + /** A connection to a running Tor process as specified in control-spec.txt. */ +-public class TorControlConnection implements TorControlCommands +-{ ++public class TorControlConnection implements TorControlCommands { + +- protected EventHandler handler; ++ private final LinkedList<Waiter> waiters; ++ private final BufferedReader input; ++ private final Writer output; + +- protected LinkedList<Waiter> waiters; ++ private ControlParseThread thread; // Locking: this + +- protected ControlParseThread thread; ++ private volatile EventHandler handler; ++ private volatile PrintWriter debugOutput; ++ private volatile IOException parseThreadException; + +- protected java.io.BufferedReader input; ++ private static class Waiter { + +- protected java.io.Writer output; +- +- protected java.io.PrintWriter debugOutput; +- +- static class Waiter { + List<ReplyLine> response; +- public synchronized List<ReplyLine> getResponse() { +- try { +- while (response == null) { +- wait(); +- } +- } catch (InterruptedException ex) { +- throw new CancellationException( +- "Please don't interrupt library calls."); +- } ++ ++ synchronized List<ReplyLine> getResponse() throws InterruptedException { ++ while(response == null) wait(); + return response; + } +- public synchronized void setResponse(List<ReplyLine> response) { ++ ++ synchronized void setResponse(List<ReplyLine> response) { + this.response = response; + notifyAll(); + } + } + +- static class ReplyLine { +- public String status; +- public String msg; +- public String rest; ++ private static class ReplyLine { ++ ++ final String status; ++ final String msg; ++ final String rest; + + ReplyLine(String status, String msg, String rest) { +- this.status = status; this.msg = msg; this.rest = rest; +- } ++ this.status = status; ++ this.msg = msg; ++ this.rest = rest; + } +- +- public static TorControlConnection getConnection(java.net.Socket sock) +- throws IOException +- { +- return new TorControlConnection(sock); + } + + /** Create a new TorControlConnection to communicate with Tor over + * a given socket. After calling this constructor, it is typical to + * call launchThread and authenticate. */ +- public TorControlConnection(java.net.Socket connection) +- throws IOException { +- this(connection.getInputStream(), connection.getOutputStream()); ++ public TorControlConnection(Socket s) throws IOException { ++ this(s.getInputStream(), s.getOutputStream()); + } + + /** Create a new TorControlConnection to communicate with Tor over + * an arbitrary pair of data streams. + */ +- public TorControlConnection(java.io.InputStream i, java.io.OutputStream o) { +- this(new java.io.InputStreamReader(i), +- new java.io.OutputStreamWriter(o)); ++ public TorControlConnection(InputStream i, OutputStream o) { ++ this(new InputStreamReader(i), new OutputStreamWriter(o)); + } + +- public TorControlConnection(java.io.Reader i, java.io.Writer o) { +- this.output = o; +- if (i instanceof java.io.BufferedReader) +- this.input = (java.io.BufferedReader) i; +- else +- this.input = new java.io.BufferedReader(i); +- +- this.waiters = new LinkedList<Waiter>(); ++ public TorControlConnection(Reader i, Writer o) { ++ if(i instanceof BufferedReader) input = (BufferedReader) i; ++ else input = new BufferedReader(i); ++ output = o; ++ waiters = new LinkedList<Waiter>(); + } + +- protected final void writeEscaped(String s) throws IOException { ++ private void writeEscaped(String s) throws IOException { + StringTokenizer st = new StringTokenizer(s, "\n"); +- while (st.hasMoreTokens()) { ++ while(st.hasMoreTokens()) { + String line = st.nextToken(); +- if (line.startsWith(".")) +- line = "."+line; +- if (line.endsWith("\r")) +- line += "\n"; +- else +- line += "\r\n"; +- if (debugOutput != null) +- debugOutput.print(">> "+line); ++ if(line.startsWith(".")) line = "." + line; ++ if(line.endsWith("\r")) line += "\n"; ++ else line += "\r\n"; ++ if(debugOutput != null) debugOutput.print(">> " + line); + output.write(line); + } + output.write(".\r\n"); +- if (debugOutput != null) +- debugOutput.print(">> .\n"); ++ if(debugOutput != null) debugOutput.print(">> .\n"); + } + +- protected static final String quote(String s) { ++ private static final String quote(String s) { + StringBuffer sb = new StringBuffer("\""); +- for (int i = 0; i < s.length(); ++i) { ++ for(int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); +- switch (c) +- { ++ switch (c) { + case '\r': + case '\n': + case '\\': +@@ -128,15 +115,15 @@ + return sb.toString(); + } + +- protected final ArrayList<ReplyLine> readReply() throws IOException { ++ private ArrayList<ReplyLine> readReply() throws IOException { + ArrayList<ReplyLine> reply = new ArrayList<ReplyLine>(); + char c; + do { + String line = input.readLine(); +- if (line == null) { ++ if(line == null) { + // if line is null, the end of the stream has been reached, i.e. + // the connection to Tor has been closed! +- if (reply.isEmpty()) { ++ if(reply.isEmpty()) { + // nothing received so far, can exit cleanly + return reply; + } +@@ -144,91 +131,86 @@ + throw new TorControlSyntaxError("Connection to Tor " + + " broke down while receiving reply!"); + } +- if (debugOutput != null) +- debugOutput.println("<< "+line); +- if (line.length() < 4) +- throw new TorControlSyntaxError("Line (\""+line+"\") too short"); +- String status = line.substring(0,3); ++ if(debugOutput != null) debugOutput.println("<< " + line); ++ if(line.length() < 4) { ++ throw new TorControlSyntaxError("Line (\"" + line + ++ "\") too short"); ++ } ++ String status = line.substring(0, 3); + c = line.charAt(3); + String msg = line.substring(4); + String rest = null; +- if (c == '+') { ++ if(c == '+') { + StringBuffer data = new StringBuffer(); +- while (true) { ++ while(true) { + line = input.readLine(); +- if (debugOutput != null) +- debugOutput.print("<< "+line); +- if (line.equals(".")) +- break; +- else if (line.startsWith(".")) +- line = line.substring(1); ++ if(debugOutput != null) debugOutput.print("<< " + line); ++ if(line.equals(".")) break; ++ if(line.startsWith(".")) line = line.substring(1); + data.append(line).append('\n'); + } + rest = data.toString(); + } + reply.add(new ReplyLine(status, msg, rest)); +- } while (c != ' '); +- ++ } while(c != ' '); + return reply; + } + +- protected synchronized List<ReplyLine> sendAndWaitForResponse(String s,String rest) +- throws IOException { ++ private synchronized List<ReplyLine> sendAndWaitForResponse(String s, ++ String rest) throws IOException { ++ if(parseThreadException != null) throw parseThreadException; + checkThread(); + Waiter w = new Waiter(); +- if (debugOutput != null) +- debugOutput.print(">> "+s); +- synchronized (waiters) { ++ if(debugOutput != null) debugOutput.print(">> " + s); ++ synchronized(waiters) { + output.write(s); +- if (rest != null) +- writeEscaped(rest); ++ if(rest != null) writeEscaped(rest); + output.flush(); + waiters.addLast(w); + } +- List<ReplyLine> lst = w.getResponse(); +- for (Iterator<ReplyLine> i = lst.iterator(); i.hasNext(); ) { +- ReplyLine c = i.next(); +- if (! c.status.startsWith("2")) +- throw new TorControlError("Error reply: "+c.msg); ++ List<ReplyLine> lst; ++ try { ++ lst = w.getResponse(); ++ } catch(InterruptedException ex) { ++ throw new IOException(ex); ++ } ++ for(ReplyLine line : lst) { ++ if(!line.status.startsWith("2")) ++ throw new TorControlError("Error reply: " + line.msg); + } + return lst; + } + + /** Helper: decode a CMD_EVENT command and dispatch it to our + * EventHandler (if any). */ +- protected void handleEvent(ArrayList<ReplyLine> events) { +- if (handler == null) +- return; +- +- for (Iterator<ReplyLine> i = events.iterator(); i.hasNext(); ) { +- ReplyLine line = i.next(); ++ private void handleEvent(ArrayList<ReplyLine> events) { ++ if(handler == null) return; ++ for(ReplyLine line : events) { + int idx = line.msg.indexOf(' '); + String tp = line.msg.substring(0, idx).toUpperCase(); + String rest = line.msg.substring(idx+1); +- if (tp.equals("CIRC")) { ++ if(tp.equals("CIRC")) { + List<String> lst = Bytes.splitStr(null, rest); +- handler.circuitStatus(lst.get(1), +- lst.get(0), +- lst.get(1).equals("LAUNCHED") +- || lst.size() < 2 ? "" +- : lst.get(2)); +- } else if (tp.equals("STREAM")) { ++ String path; ++ if(lst.get(1).equals("LAUNCHED") || lst.size() < 2) path = ""; ++ else path = lst.get(2); ++ handler.circuitStatus(lst.get(1), lst.get(0), path); ++ } else if(tp.equals("STREAM")) { + List<String> lst = Bytes.splitStr(null, rest); +- handler.streamStatus(lst.get(1), +- lst.get(0), +- lst.get(3)); ++ handler.streamStatus(lst.get(1), lst.get(0), lst.get(3)); + // XXXX circID. +- } else if (tp.equals("ORCONN")) { ++ } else if(tp.equals("ORCONN")) { + List<String> lst = Bytes.splitStr(null, rest); + handler.orConnStatus(lst.get(1), lst.get(0)); +- } else if (tp.equals("BW")) { ++ } else if(tp.equals("BW")) { + List<String> lst = Bytes.splitStr(null, rest); +- handler.bandwidthUsed(Integer.parseInt(lst.get(0)), +- Integer.parseInt(lst.get(1))); +- } else if (tp.equals("NEWDESC")) { ++ int read = Integer.parseInt(lst.get(0)); ++ int written = Integer.parseInt(lst.get(1)); ++ handler.bandwidthUsed(read, written); ++ } else if(tp.equals("NEWDESC")) { + List<String> lst = Bytes.splitStr(null, rest); + handler.newDescriptors(lst); +- } else if (tp.equals("DEBUG") || ++ } else if(tp.equals("DEBUG") || + tp.equals("INFO") || + tp.equals("NOTICE") || + tp.equals("WARN") || +@@ -240,23 +222,22 @@ + } + } + +- + /** Sets <b>w</b> as the PrintWriter for debugging output, + * which writes out all messages passed between Tor and the controller. +- * Outgoing messages are preceded by "\>\>" and incoming messages are preceded +- * by "\<\<" ++ * Outgoing messages are preceded by "\>\>" and incoming messages are ++ * preceded by "\<\<" + */ +- public void setDebugging(java.io.PrintWriter w) { ++ public void setDebugging(PrintWriter w) { + debugOutput = w; + } + + /** Sets <b>s</b> as the PrintStream for debugging output, + * which writes out all messages passed between Tor and the controller. +- * Outgoing messages are preceded by "\>\>" and incoming messages are preceded +- * by "\<\<" ++ * Outgoing messages are preceded by "\>\>" and incoming messages are ++ * preceded by "\<\<" + */ +- public void setDebugging(java.io.PrintStream s) { +- debugOutput = new java.io.PrintWriter(s, true); ++ public void setDebugging(PrintStream s) { ++ debugOutput = new PrintWriter(s, true); + } + + /** Set the EventHandler object that will be notified of any +@@ -271,52 +252,43 @@ + * This is necessary to handle asynchronous events and synchronous + * responses that arrive independantly over the same socket. + */ +- public Thread launchThread(boolean daemon) { ++ public synchronized Thread launchThread(boolean daemon) { + ControlParseThread th = new ControlParseThread(); +- if (daemon) +- th.setDaemon(true); ++ if(daemon) th.setDaemon(true); + th.start(); +- this.thread = th; ++ thread = th; + return th; + } + +- protected class ControlParseThread extends Thread { +- boolean stopped = false; ++ private class ControlParseThread extends Thread { ++ + @Override + public void run() { + try { + react(); +- } catch (SocketException ex) { +- if (stopped) // we expected this exception +- return; +- throw new RuntimeException(ex); +- } catch (IOException ex) { +- throw new RuntimeException(ex); +- } ++ } catch(IOException ex) { ++ parseThreadException = ex; + } +- public void stopListening() { +- this.stopped = true; + } + } + +- protected final void checkThread() { +- if (thread == null) +- launchThread(true); ++ private synchronized void checkThread() { ++ if(thread == null) launchThread(true); + } + + /** helper: implement the main background loop. */ +- protected void react() throws IOException { +- while (true) { ++ private void react() throws IOException { ++ while(true) { + ArrayList<ReplyLine> lst = readReply(); +- if (lst.isEmpty()) { ++ if(lst.isEmpty()) { + // connection has been closed remotely! end the loop! + return; + } +- if ((lst.get(0)).status.startsWith("6")) ++ if((lst.get(0)).status.startsWith("6")) { + handleEvent(lst); +- else { ++ } else { + Waiter w; +- synchronized (waiters) { ++ synchronized(waiters) { + w = waiters.removeFirst(); + } + w.setResponse(lst); +@@ -327,17 +299,14 @@ + /** Change the value of the configuration option 'key' to 'val'. + */ + public void setConf(String key, String value) throws IOException { +- List<String> lst = new ArrayList<String>(); +- lst.add(key+" "+value); +- setConf(lst); ++ setConf(Arrays.asList(key + " " + value)); + } + + /** Change the values of the configuration options stored in kvMap. */ + public void setConf(Map<String, String> kvMap) throws IOException { + List<String> lst = new ArrayList<String>(); +- for (Iterator<Map.Entry<String,String>> it = kvMap.entrySet().iterator(); it.hasNext(); ) { +- Map.Entry<String,String> ent = it.next(); +- lst.add(ent.getKey()+" "+ent.getValue()+"\n"); ++ for(Map.Entry<String, String> e : kvMap.entrySet()) { ++ lst.add(e.getKey() + " " + e.getValue() + "\n"); + } + setConf(lst); + } +@@ -345,34 +314,35 @@ + /** Changes the values of the configuration options stored in + * <b>kvList</b>. Each list element in <b>kvList</b> is expected to be + * String of the format "key value". +- * ++ * <p> + * Tor behaves as though it had just read each of the key-value pairs + * from its configuration file. Keywords with no corresponding values have + * their configuration values reset to their defaults. setConf is +- * all-or-nothing: if there is an error in any of the configuration settings, +- * Tor sets none of them. +- * ++ * all-or-nothing: if there is an error in any of the configuration ++ * settings, Tor sets none of them. ++ * <p> + * When a configuration option takes multiple values, or when multiple +- * configuration keys form a context-sensitive group (see getConf below), then +- * setting any of the options in a setConf command is taken to reset all of +- * the others. For example, if two ORBindAddress values are configured, and a +- * command arrives containing a single ORBindAddress value, the new +- * command's value replaces the two old values. +- * ++ * configuration keys form a context-sensitive group (see getConf below), ++ * then setting any of the options in a setConf command is taken to reset ++ * all of the others. For example, if two ORBindAddress values are ++ * configured, and a command arrives containing a single ORBindAddress ++ * value, the new command's value replaces the two old values. ++ * <p> + * To remove all settings for a given option entirely (and go back to its +- * default value), include a String in <b>kvList</b> containing the key and no value. ++ * default value), include a String in <b>kvList</b> containing the key and ++ * no value. + */ + public void setConf(Collection<String> kvList) throws IOException { +- if (kvList.size() == 0) +- return; ++ if(kvList.size() == 0) return; + StringBuffer b = new StringBuffer("SETCONF"); +- for (Iterator<String> it = kvList.iterator(); it.hasNext(); ) { +- String kv = it.next(); +- int i = kv.indexOf(' '); +- if (i == -1) ++ for(String kv : kvList) { ++ int idx = kv.indexOf(' '); ++ if(idx == -1) { + b.append(" ").append(kv); +- b.append(" ").append(kv.substring(0,i)).append("=") +- .append(quote(kv.substring(i+1))); ++ } else { ++ b.append(" ").append(kv.substring(0, idx)); ++ b.append("=").append(quote(kv.substring(idx + 1))); ++ } + } + b.append("\r\n"); + sendAndWaitForResponse(b.toString(), null); +@@ -382,11 +352,9 @@ + * default values. + **/ + public void resetConf(Collection<String> keys) throws IOException { +- if (keys.size() == 0) +- return; ++ if(keys.size() == 0) return; + StringBuffer b = new StringBuffer("RESETCONF"); +- for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { +- String key = it.next(); ++ for(String key : keys) { + b.append(" ").append(key); + } + b.append("\r\n"); +@@ -400,36 +368,38 @@ + return getConf(lst); + } + +- /** Requests the values of the configuration variables listed in <b>keys</b>. +- * Results are returned as a list of ConfigEntry objects. +- * ++ /** Requests the values of the configuration variables listed in ++ * <b>keys</b>. Results are returned as a list of ConfigEntry objects. ++ * <p> + * If an option appears multiple times in the configuration, all of its + * key-value pairs are returned in order. +- * ++ * <p> + * Some options are context-sensitive, and depend on other options with + * different keywords. These cannot be fetched directly. Currently there + * is only one such option: clients should use the "HiddenServiceOptions" + * virtual keyword to get all HiddenServiceDir, HiddenServicePort, + * HiddenServiceNodes, and HiddenServiceExcludeNodes option settings. + */ +- public List<ConfigEntry> getConf(Collection<String> keys) throws IOException { ++ public List<ConfigEntry> getConf(Collection<String> keys) ++ throws IOException { + StringBuffer sb = new StringBuffer("GETCONF"); +- for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { +- String key = it.next(); ++ for(String key : keys) { + sb.append(" ").append(key); + } + sb.append("\r\n"); + List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null); + List<ConfigEntry> result = new ArrayList<ConfigEntry>(); +- for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) { +- String kv = (it.next()).msg; ++ for(ReplyLine line : lst) { ++ String kv = line.msg; + int idx = kv.indexOf('='); +- if (idx >= 0) +- result.add(new ConfigEntry(kv.substring(0, idx), +- kv.substring(idx+1))); +- else ++ if(idx >= 0) { ++ String key = kv.substring(0, idx); ++ String value = kv.substring(idx + 1); ++ result.add(new ConfigEntry(key, value)); ++ } else { + result.add(new ConfigEntry(kv)); + } ++ } + return result; + } + +@@ -437,37 +407,41 @@ + * Each element of <b>events</b> is one of the following Strings: + * ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" | + * "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] . +- * ++ * <p> + * Any events not listed in the <b>events</b> are turned off; thus, calling +- * setEvents with an empty <b>events</b> argument turns off all event reporting. ++ * setEvents with an empty <b>events</b> argument turns off all event ++ * reporting. + */ + public void setEvents(List<String> events) throws IOException { + StringBuffer sb = new StringBuffer("SETEVENTS"); +- for (Iterator<String> it = events.iterator(); it.hasNext(); ) { +- sb.append(" ").append(it.next()); ++ for(String event : events) { ++ sb.append(" ").append(event); + } + sb.append("\r\n"); + sendAndWaitForResponse(sb.toString(), null); + } + + /** Authenticates the controller to the Tor server. +- * ++ * <p> + * By default, the current Tor implementation trusts all local users, and +- * the controller can authenticate itself by calling authenticate(new byte[0]). +- * +- * If the 'CookieAuthentication' option is true, Tor writes a "magic cookie" +- * file named "control_auth_cookie" into its data directory. To authenticate, +- * the controller must send the contents of this file in <b>auth</b>. +- * +- * If the 'HashedControlPassword' option is set, <b>auth</b> must contain the salted +- * hash of a secret password. The salted hash is computed according to the +- * S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier. +- * This is then encoded in hexadecimal, prefixed by the indicator sequence +- * "16:". +- * ++ * the controller can authenticate itself by calling ++ * authenticate(new byte[0]). ++ * <p> ++ * If the 'CookieAuthentication' option is true, Tor writes a "magic ++ * cookie" file named "control_auth_cookie" into its data directory. To ++ * authenticate, the controller must send the contents of this file in ++ * <b>auth</b>. ++ * <p> ++ * If the 'HashedControlPassword' option is set, <b>auth</b> must contain ++ * the salted hash of a secret password. The salted hash is computed ++ * according to the S2K algorithm in RFC 2440 (OpenPGP), and prefixed with ++ * the s2k specifier. This is then encoded in hexadecimal, prefixed by the ++ * indicator sequence "16:". ++ * <p> + * You can generate the salt of a password by calling +- * 'tor --hash-password <password>' ++ * <tt>'tor --hash-password <password>'</tt> + * or by using the provided PasswordDigest class. ++ * <p> + * To authenticate under this scheme, the controller sends Tor the original + * secret that was used to generate the password. + */ +@@ -476,7 +450,8 @@ + sendAndWaitForResponse(cmd, null); + } + +- /** Instructs the server to write out its configuration options into its torrc. ++ /** Instructs the server to write out its configuration options into its ++ * torrc. + */ + public void saveConf() throws IOException { + sendAndWaitForResponse("SAVECONF\r\n", null); +@@ -503,234 +478,239 @@ + public void shutdownTor(String signal) throws IOException { + String s = "SIGNAL " + signal + "\r\n"; + Waiter w = new Waiter(); +- if (debugOutput != null) +- debugOutput.print(">> "+s); +- if (this.thread != null) { +- this.thread.stopListening(); +- } +- synchronized (waiters) { ++ if(debugOutput != null) debugOutput.print(">> " + s); ++ synchronized(waiters) { + output.write(s); + output.flush(); + waiters.addLast(w); // Prevent react() from finding the list empty + } + } + +- /** Tells the Tor server that future SOCKS requests for connections to a set of original +- * addresses should be replaced with connections to the specified replacement +- * addresses. Each element of <b>kvLines</b> is a String of the form +- * "old-address new-address". This function returns the new address mapping. +- * ++ /** Tells the Tor server that future SOCKS requests for connections to a ++ * set of original addresses should be replaced with connections to the ++ * specified replacement addresses. Each element of <b>kvLines</b> is a ++ * String of the form "old-address new-address". This function returns the ++ * new address mapping. ++ * <p> + * The client may decline to provide a body for the original address, and +- * instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, or +- * "." for hostname), signifying that the server should choose the original +- * address itself, and return that address in the reply. The server +- * should ensure that it returns an element of address space that is unlikely +- * to be in actual use. If there is already an address mapped to the +- * destination address, the server may reuse that mapping. +- * +- * If the original address is already mapped to a different address, the old +- * mapping is removed. If the original address and the destination address +- * are the same, the server removes any mapping in place for the original +- * address. +- * +- * Mappings set by the controller last until the Tor process exits: +- * they never expire. If the controller wants the mapping to last only +- * a certain time, then it must explicitly un-map the address when that +- * time has elapsed. ++ * instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, ++ * or "." for hostname), signifying that the server should choose the ++ * original address itself, and return that address in the reply. The ++ * server should ensure that it returns an element of address space that is ++ * unlikely to be in actual use. If there is already an address mapped to ++ * the destination address, the server may reuse that mapping. ++ * <p> ++ * If the original address is already mapped to a different address, the ++ * old mapping is removed. If the original address and the destination ++ * address are the same, the server removes any mapping in place for the ++ * original address. ++ * <p> ++ * Mappings set by the controller last until the Tor process exits: they ++ * never expire. If the controller wants the mapping to last only a certain ++ * time, then it must explicitly un-map the address when that time has ++ * elapsed. + */ +- public Map<String,String> mapAddresses(Collection<String> kvLines) throws IOException { ++ public Map<String, String> mapAddresses(Collection<String> kvLines) ++ throws IOException { + StringBuffer sb = new StringBuffer("MAPADDRESS"); +- for (Iterator<String> it = kvLines.iterator(); it.hasNext(); ) { +- String kv = it.next(); +- int i = kv.indexOf(' '); +- sb.append(" ").append(kv.substring(0,i)).append("=") +- .append(quote(kv.substring(i+1))); ++ for(String kv : kvLines) { ++ int idx = kv.indexOf(' '); ++ sb.append(" ").append(kv.substring(0, idx)); ++ sb.append("=").append(quote(kv.substring(idx + 1))); + } + sb.append("\r\n"); + List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null); +- Map<String,String> result = new HashMap<String,String>(); +- for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) { +- String kv = (it.next()).msg; ++ Map<String, String> result = new HashMap<String, String>(); ++ for(ReplyLine line : lst) { ++ String kv = line.msg; + int idx = kv.indexOf('='); +- result.put(kv.substring(0, idx), +- kv.substring(idx+1)); ++ result.put(kv.substring(0, idx), kv.substring(idx + 1)); + } + return result; + } + +- public Map<String,String> mapAddresses(Map<String,String> addresses) throws IOException { ++ public Map<String, String> mapAddresses(Map<String, String> addresses) ++ throws IOException { + List<String> kvList = new ArrayList<String>(); +- for (Iterator<Map.Entry<String, String>> it = addresses.entrySet().iterator(); it.hasNext(); ) { +- Map.Entry<String,String> e = it.next(); +- kvList.add(e.getKey()+" "+e.getValue()); ++ for(Map.Entry<String, String> e : addresses.entrySet()) { ++ kvList.add(e.getKey() + " " + e.getValue()); + } + return mapAddresses(kvList); + } + +- public String mapAddress(String fromAddr, String toAddr) throws IOException { ++ public String mapAddress(String fromAddr, String toAddr) ++ throws IOException { + List<String> lst = new ArrayList<String>(); +- lst.add(fromAddr+" "+toAddr+"\n"); +- Map<String,String> m = mapAddresses(lst); ++ lst.add(fromAddr + " " + toAddr + "\n"); ++ Map<String, String> m = mapAddresses(lst); + return m.get(fromAddr); + } + +- /** Queries the Tor server for keyed values that are not stored in the torrc +- * configuration file. Returns a map of keys to values. +- * ++ /** Queries the Tor server for keyed values that are not stored in the ++ * torrc configuration file. Returns a map of keys to values. ++ * <p> + * Recognized keys include: + * <ul> + * <li>"version" : The version of the server's software, including the name + * of the software. (example: "Tor 0.0.9.4")</li> +- * <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server +- * descriptor for a given OR, NUL-terminated. If no such OR is known, the +- * corresponding value is an empty string.</li> +- * <li>"network-status" : a space-separated list of all known OR identities. +- * This is in the same format as the router-status line in directories; +- * see tor-spec.txt for details.</li> ++ * <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest ++ * server descriptor for a given OR, NUL-terminated. If no such OR is ++ * known, the corresponding value is an empty string.</li> ++ * <li>"network-status" : a space-separated list of all known OR ++ * identities. This is in the same format as the router-status line in ++ * directories; see tor-spec.txt for details.</li> + * <li>"addr-mappings/all"</li> + * <li>"addr-mappings/config"</li> + * <li>"addr-mappings/cache"</li> +- * <li>"addr-mappings/control" : a space-separated list of address mappings, each +- * in the form of "from-address=to-address". The 'config' key +- * returns those address mappings set in the configuration; the 'cache' ++ * <li>"addr-mappings/control" : a space-separated list of address ++ * mappings, each in the form of "from-address=to-address". The 'config' ++ * key returns those address mappings set in the configuration; the 'cache' + * key returns the mappings in the client-side DNS cache; the 'control' + * key returns the mappings set via the control interface; the 'all' + * target returns the mappings set through any mechanism.</li> +- * <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form: +- * "CircuitID CircStatus Path"</li> +- * <li>"stream-status" : A series of lines as for a stream status event. Each is of the form: +- * "StreamID StreamStatus CircID Target"</li> +- * <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the +- * form: "ServerID ORStatus"</li> ++ * <li>"circuit-status" : A series of lines as for a circuit status event. ++ * Each line is of the form: "CircuitID CircStatus Path"</li> ++ * <li>"stream-status" : A series of lines as for a stream status event. ++ * Each is of the form: "StreamID StreamStatus CircID Target"</li> ++ * <li>"orconn-status" : A series of lines as for an OR connection status ++ * event. Each is of the form: "ServerID ORStatus"</li> + * </ul> + */ +- public Map<String,String> getInfo(Collection<String> keys) throws IOException { ++ public Map<String, String> getInfo(Collection<String> keys) ++ throws IOException { + StringBuffer sb = new StringBuffer("GETINFO"); +- for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { +- sb.append(" ").append(it.next()); ++ for(String key : keys) { ++ sb.append(" ").append(key); + } + sb.append("\r\n"); + List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null); +- Map<String,String> m = new HashMap<String,String>(); +- for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) { +- ReplyLine line = it.next(); ++ Map<String, String> m = new HashMap<String, String>(); ++ for(ReplyLine line : lst) { + int idx = line.msg.indexOf('='); +- if (idx<0) +- break; +- String k = line.msg.substring(0,idx); ++ if(idx < 0) break; ++ String k = line.msg.substring(0, idx); + String v; +- if (line.rest != null) { +- v = line.rest; +- } else { +- v = line.msg.substring(idx+1); +- } ++ if(line.rest != null) v = line.rest; ++ else v = line.msg.substring(idx + 1); + m.put(k, v); + } + return m; + } + +- +- +- /** Return the value of the information field 'key' */ ++ /** Returns the value of the information field 'key' */ + public String getInfo(String key) throws IOException { +- List<String> lst = new ArrayList<String>(); +- lst.add(key); +- Map<String,String> m = getInfo(lst); ++ Map<String, String> m = getInfo(Arrays.asList(key)); + return m.get(key); + } + +- /** An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in +- * which case it is a request for the server to build a new circuit according +- * to the specified path, or the <b>circID</b> is nonzero, in which case it is a +- * request for the server to extend an existing circuit with that ID according +- * to the specified <b>path</b>. +- * +- * If successful, returns the Circuit ID of the (maybe newly created) circuit. ++ /** An extendCircuit request takes one of two forms: either the ++ * <b>circID</b> is zero, in which case it is a request for the server to ++ * build a new circuit according to the specified path, or the ++ * <b>circID</b> is nonzero, in which case it is a request for the server ++ * to extend an existing circuit with that ID according to the specified ++ * <b>path</b>. ++ * <p> ++ * If successful, returns the Circuit ID of the (maybe newly created) ++ * circuit. + */ + public String extendCircuit(String circID, String path) throws IOException { +- List<ReplyLine> lst = sendAndWaitForResponse( +- "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null); +- return (lst.get(0)).msg; ++ String cmd = "EXTENDCIRCUIT " + circID + " " + path + "\r\n"; ++ List<ReplyLine> lst = sendAndWaitForResponse(cmd, null); ++ return lst.get(0).msg; + } + +- /** Informs the Tor server that the stream specified by <b>streamID</b> should be +- * associated with the circuit specified by <b>circID</b>. +- * +- * Each stream may be associated with +- * at most one circuit, and multiple streams may share the same circuit. +- * Streams can only be attached to completed circuits (that is, circuits that +- * have sent a circuit status "BUILT" event or are listed as built in a +- * getInfo circuit-status request). +- * ++ /** Informs the Tor server that the stream specified by <b>streamID</b> ++ * should be associated with the circuit specified by <b>circID</b>. ++ * <p> ++ * Each stream may be associated with at most one circuit, and multiple ++ * streams may share the same circuit. Streams can only be attached to ++ * completed circuits (that is, circuits that have sent a circuit status ++ * "BUILT" event or are listed as built in a getInfo circuit-status ++ * request). ++ * <p> + * If <b>circID</b> is 0, responsibility for attaching the given stream is + * returned to Tor. +- * +- * By default, Tor automatically attaches streams to +- * circuits itself, unless the configuration variable +- * "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams +- * via TC when "__LeaveStreamsUnattached" is false may cause a race between +- * Tor and the controller, as both attempt to attach streams to circuits. ++ * <p> ++ * By default, Tor automatically attaches streams to circuits itself, ++ * unless the configuration variable "__LeaveStreamsUnattached" is set to ++ * "1". Attempting to attach streams via TC when ++ * "__LeaveStreamsUnattached" is false may cause a race between Tor and the ++ * controller, as both attempt to attach streams to circuits. + */ + public void attachStream(String streamID, String circID) + throws IOException { +- sendAndWaitForResponse("ATTACHSTREAM "+streamID+" "+circID+"\r\n", null); ++ String cmd = "ATTACHSTREAM " + streamID + " " + circID + "\r\n"; ++ sendAndWaitForResponse(cmd, null); + } + + /** Tells Tor about the server descriptor in <b>desc</b>. +- * ++ * <p> + * The descriptor, when parsed, must contain a number of well-specified + * fields, including fields for its nickname and identity. + */ + // More documentation here on format of desc? + // No need for return value? control-spec.txt says reply is merely "250 OK" on success... + public String postDescriptor(String desc) throws IOException { +- List<ReplyLine> lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc); +- return (lst.get(0)).msg; ++ String cmd = "+POSTDESCRIPTOR\r\n"; ++ List<ReplyLine> lst = sendAndWaitForResponse(cmd, desc); ++ return lst.get(0).msg; + } + +- /** Tells Tor to change the exit address of the stream identified by <b>streamID</b> +- * to <b>address</b>. No remapping is performed on the new provided address. +- * +- * To be sure that the modified address will be used, this event must be sent +- * after a new stream event is received, and before attaching this stream to +- * a circuit. +- */ +- public void redirectStream(String streamID, String address) throws IOException { +- sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n", +- null); ++ /** Tells Tor to change the exit address of the stream identified by ++ * <b>streamID</b> to <b>address</b>. No remapping is performed on the new ++ * provided address. ++ * <p> ++ * To be sure that the modified address will be used, this event must be ++ * sent after a new stream event is received, and before attaching this ++ * stream to a circuit. ++ */ ++ public void redirectStream(String streamID, String address) ++ throws IOException { ++ String cmd = "REDIRECTSTREAM " + streamID + " " + address + "\r\n"; ++ sendAndWaitForResponse(cmd, null); + } + + /** Tells Tor to close the stream identified by <b>streamID</b>. +- * <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal: ++ * <b>reason</b> should be one of the Tor RELAY_END reasons given in ++ * tor-spec.txt, as a decimal: + * <ul> + * <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li> + * <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li> + * <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li> +- * <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li> ++ * <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or ++ * port)</li> + * <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li> +- * <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li> +- * <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li> ++ * <li>6 -- REASON_DONE (Anonymized TCP connection was ++ * closed)</li> ++ * <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out ++ * while connecting)</li> + * <li>8 -- (unallocated)</li> + * <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li> + * <li>10 -- REASON_INTERNAL (Internal error at the OR)</li> +- * <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li> ++ * <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill ++ * request)</li> + * <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li> +- * <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li> ++ * <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of ++ * Tor protocol violations)</li> + * </ul> +- * +- * Tor may hold the stream open for a while to flush any data that is pending. ++ * Tor may hold the stream open for a while to flush any data that is ++ * pending. + */ +- public void closeStream(String streamID, byte reason) +- throws IOException { +- sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null); ++ public void closeStream(String streamID, byte reason) throws IOException { ++ String cmd = "CLOSESTREAM " + streamID + " " + reason + "\r\n"; ++ sendAndWaitForResponse(cmd, null); + } + + /** Tells Tor to close the circuit identified by <b>circID</b>. +- * If <b>ifUnused</b> is true, do not close the circuit unless it is unused. ++ * If <b>ifUnused</b> is true, do not close the circuit unless it is ++ * unused. + */ +- public void closeCircuit(String circID, boolean ifUnused) throws IOException { +- sendAndWaitForResponse("CLOSECIRCUIT "+circID+ +- (ifUnused?" IFUNUSED":"")+"\r\n", null); ++ public void closeCircuit(String circID, boolean ifUnused) ++ throws IOException { ++ String cmd; ++ if(ifUnused) cmd = "CLOSECIRCUIT " + circID + " IFUNUSED\r\n"; ++ else cmd = "CLOSECIRCUIT " + circID + "\r\n"; ++ sendAndWaitForResponse(cmd, null); + } + } + +diff -Bbur jtorctl/net/freehaven/tor/control/TorControlError.java jtorctl-briar/net/freehaven/tor/control/TorControlError.java +--- jtorctl/net/freehaven/tor/control/TorControlError.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/TorControlError.java 2013-05-16 19:56:30.000000000 +0100 +@@ -2,13 +2,15 @@ + // See LICENSE file for copying information + package net.freehaven.tor.control; + +-/** +- * An exception raised when Tor tells us about an error. +- */ +-public class TorControlError extends RuntimeException { +- static final long serialVersionUID = 2; ++import java.io.IOException; ++ ++/** An exception raised when Tor tells us about an error. */ ++public class TorControlError extends IOException { ++ ++ private static final long serialVersionUID = 2; ++ ++ private final int errorType; + +- int errorType; + public TorControlError(int type, String s) { + super(s); + errorType = type; +@@ -19,13 +23,13 @@ + public int getErrorType() { + return errorType; + } ++ + public String getErrorMsg() { + try { +- if (errorType == -1) +- return null; ++ if(errorType == -1) return null; + return TorControlCommands.ERROR_MSGS[errorType]; +- } catch (ArrayIndexOutOfBoundsException ex) { +- return "Unrecongized error #"+errorType; ++ } catch(ArrayIndexOutOfBoundsException ex) { ++ return "Unrecongized error #" + errorType; + } + } + } +diff -Bbur jtorctl/net/freehaven/tor/control/TorControlSyntaxError.java jtorctl-briar/net/freehaven/tor/control/TorControlSyntaxError.java +--- jtorctl/net/freehaven/tor/control/TorControlSyntaxError.java 2013-04-24 16:46:08.000000000 +0100 ++++ jtorctl-briar/net/freehaven/tor/control/TorControlSyntaxError.java 2013-05-16 19:56:30.000000000 +0100 +@@ -2,12 +2,15 @@ + // See LICENSE file for copying information + package net.freehaven.tor.control; + +-/** +- * An exception raised when Tor behaves in an unexpected way. +- */ +-public class TorControlSyntaxError extends RuntimeException { +- static final long serialVersionUID = 2; ++import java.io.IOException; + +- public TorControlSyntaxError(String s) { super(s); } ++/** An exception raised when Tor behaves in an unexpected way. */ ++public class TorControlSyntaxError extends IOException { ++ ++ private static final long serialVersionUID = 2; ++ ++ public TorControlSyntaxError(String s) { ++ super(s); ++ } + } +