[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] Refactor java tor control library to split out v0 protocol



Update of /home/or/cvsroot/control/java/net/freehaven/tor/control
In directory moria:/tmp/cvs-serv17111/net/freehaven/tor/control

Modified Files:
	Bytes.java EventHandler.java TorControlConnection.java 
Added Files:
	TorControlConnection0.java 
Log Message:
Refactor java tor control library to split out v0 protocol

Index: Bytes.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/Bytes.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- Bytes.java	4 Jun 2005 02:42:55 -0000	1.1
+++ Bytes.java	21 Jun 2005 21:49:30 -0000	1.2
@@ -38,6 +38,10 @@
             ((ba[pos+3]&0xff));
     }
 
+    public static String getU32S(byte[] ba, int pos) {
+        return String.valueOf( ((long)getU32(ba,pos))&0xffffffffL );
+    }
+
     /** Return the two-byte value starting at index 'pos' within 'ba' */
     public static int getU16(byte[] ba, int pos) {
         return

Index: EventHandler.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/EventHandler.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- EventHandler.java	4 Jun 2005 02:42:55 -0000	1.1
+++ EventHandler.java	21 Jun 2005 21:49:30 -0000	1.2
@@ -10,22 +10,21 @@
  * @see TorControlConnection#listenForEvents
  */
 public interface EventHandler {
-
     /**
      * Invoked when a circuit's status has changed.
      * See TorControlCommands.CIRC_STATUS_* for possible status codes.
      */
-    public void circuitStatus(int status, int circID, String path);
+    public void circuitStatus(String status, String circID, String path);
     /**
      * Invoked when a stream's status has changed.
      * See TorControlCommands.STREAM_STATUS_* for possible status codes.
      */
-    public void streamStatus(int status, int streamID, String target);
+    public void streamStatus(String status, String streamID, String target);
     /**
      * Invoked when the status of a connection to an OR has changed.
      * See TorControlCommands.OR_CONN_STATUS_* for possible status codes.
      */
-    public void orConnStatus(int status, String orName);
+    public void orConnStatus(String status, String orName);
     /**
      * Invoked once per second with the number of bytes read an written in
      * the last secone.
@@ -39,4 +38,4 @@
      * Invoked when Tor logs a message.
      */
     public void message(int type, String msg);
-}
\ No newline at end of file
+}

Index: TorControlConnection.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/TorControlConnection.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- TorControlConnection.java	4 Jun 2005 02:42:55 -0000	1.1
+++ TorControlConnection.java	21 Jun 2005 21:49:30 -0000	1.2
@@ -13,16 +13,15 @@
 import java.util.Map;
 
 /** A connection to a running Tor process. */
-public class TorControlConnection implements TorControlCommands {
-    protected java.io.DataOutputStream outStream;
-    protected java.io.DataInputStream inStream;
+public abstract class TorControlConnection// implements TorControlCommands {
+{
     protected EventHandler handler;
 
     protected LinkedList waiters;
 
     static class Waiter {
-        Cmd response;
-        public synchronized Cmd getResponse() {
+        Object response;
+        public synchronized Object getResponse() {
             try {
                 while (response == null) {
                     wait();
@@ -32,104 +31,16 @@
             }
             return response;
         }
-        public synchronized void setResponse(Cmd response) {
+        public synchronized void setResponse(Object response) {
             this.response = response;
             notifyAll();
         }
     }
 
-    static class Cmd {
-        public int type;
-        public byte[] body;
-
-        Cmd(int t, byte[] b) { type = t; body = b; }
-        Cmd(int t, int l) { type = t; body = new byte[l]; };
-    }
-
-    /** 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());
-    }
-
-    /** 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)
-        throws IOException {
-        this.outStream = new java.io.DataOutputStream(o);
-        this.inStream = new java.io.DataInputStream(i);
+    protected TorControlConnection() {
         this.waiters = new LinkedList();
     }
 
-    /** helper: sends a single (unfragmentable) command to Tor */
-    protected final void sendCommand0(int type, byte[] cmd)
-        throws IOException {
-        int length = cmd == null ? 0 : cmd.length;
-        outStream.writeShort((short)length);
-        outStream.writeShort(type);
-        if (cmd != null)
-            outStream.write(cmd);
-    }
-
-    /** helper: sends a single (possibly fragmented) command to Tor */
-    protected void sendCommand(short type, byte[] cmd) throws IOException {
-        synchronized(this.outStream) {
-            if (cmd == null || cmd.length <= 65535) {
-                sendCommand0(type, cmd);
-                return;
-            }
-            int length = cmd.length;
-            outStream.writeShort(65535);
-            outStream.writeShort(CMD_FRAGMENTHEADER);
-            outStream.writeShort(type);
-            outStream.writeInt(length);
-            outStream.write(cmd, 0, 65535);
-            for (int pos=65535; pos < length; pos += 65535) {
-                int flen = length-pos < 65535 ? length-pos : 65535;
-                outStream.writeShort(flen);
-                outStream.writeShort(CMD_FRAGMENT);
-                this.outStream.write(cmd, pos, flen);
-            }
-        }
-    }
-
-    /** helper: read a possibly fragmented command from Tor */
-    protected final Cmd readCommand0() throws IOException {
-        int len = this.inStream.readUnsignedShort();
-        int cmd = this.inStream.readUnsignedShort();
-        byte[] result = new byte[len];
-        this.inStream.readFully(result);
-        return new Cmd(cmd, result);
-    }
-
-    /** Read a command from Tor, defragmenting as necessary */
-    protected Cmd readCommand() throws IOException {
-        synchronized (inStream) {
-            Cmd c = readCommand0();
-            if (c.type != CMD_FRAGMENT && c.type != CMD_FRAGMENTHEADER)
-                return c;
-
-            if (c.type == CMD_FRAGMENT)
-                throw new TorControlSyntaxError("Fragment without header");
-
-            int realType = Bytes.getU16(c.body, 0);
-            int realLen = Bytes.getU32(c.body, 2);
-
-            Cmd out = new Cmd(realType, realLen);
-            System.arraycopy(c.body, 6, out.body, 0, c.body.length-6);
-            int pos = c.body.length-6;
-            while (pos < realLen) {
-                c = readCommand0();
-                if (c.type != CMD_FRAGMENT)
-                    throw new TorControlSyntaxError("Incomplete fragmented message");
-                System.arraycopy(c.body, 0, out.body, pos, c.body.length);
-                pos += c.body.length;
-            }
-            return out;
-        }
-    }
 
     /** Set the EventHandler object that will be notified of any
      * events Tor delivers to this connection.  To make Tor send us
@@ -159,123 +70,8 @@
         return th;
     }
 
-    /** helper: implement the main background loop. */
-    protected void react() throws IOException {
-        while (true) {
-            Cmd c = readCommand();
-            if (c.type == CMD_EVENT)
-                handleEvent(c);
-            else {
-                Waiter w;
-                synchronized (waiters) {
-                    w = (Waiter) waiters.removeFirst();
-                }
-                w.setResponse(c);
-            }
-        }
-    }
-
-    /** helper: Send a command and wait for the next reponse type command
-     * to be received (in order) */
-    protected synchronized Cmd _sendAndWaitForResponse(short type, byte[] cmd)
-        throws IOException {
-        Waiter w = new Waiter();
-        synchronized (waiters) {
-            sendCommand(type, cmd);
-            waiters.addLast(w);
-        }
-        return w.getResponse();
-    }
-
-    /** Send a message to Tor, and wait for a respose.
-     *
-     * @throw TorControlError if Tor tells us about an error
-     * @throw TorControlSyntaxError if the response type wasn't exType1...4
-     **/
-    protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
-                   short exType1, short exType2, short exType3, short exType4)
-        throws IOException {
-
-        Cmd c = _sendAndWaitForResponse(type, cmd);
-        if (c.type == CMD_ERROR)
-            throw new TorControlError(Bytes.getU16(c.body, 0),
-                                      Bytes.getNulTerminatedStr(c.body, 2));
-        if (c.type == exType1 || c.type == exType2 || c.type == exType3 ||
-            c.type == exType4)
-            return c;
-
-        throw new TorControlSyntaxError("Unexpected reply type: "+c.type);
-    }
-
-    protected Cmd sendAndWaitForResponse(short type, byte[] cmd)
-        throws IOException {
-        return sendAndWaitForResponse(type, cmd, CMD_DONE, CMD_DONE, CMD_DONE, CMD_DONE);
-    }
-
-
-    protected Cmd sendAndWaitForResponse(short type, byte[] cmd, short exType1)
-        throws IOException {
-        return sendAndWaitForResponse(type, cmd, exType1, exType1, exType1,
-                                      exType1);
-    }
-
-    protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
-                                    short exType1, short exType2)
-        throws IOException {
-        return sendAndWaitForResponse(type, cmd, exType1, exType2, exType2,
-                                      exType2);
-    }
-
-
-    protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
-                                   short exType1, short exType2, short exType3)
-        throws IOException {
-        return sendAndWaitForResponse(type, cmd, exType1, exType2, exType3,
-                                      exType3);
-    }
-
-    /** Helper: decode a CMD_EVENT command and dispatch it to our
-     * EventHandler (if any). */
-    protected void handleEvent(Cmd c) {
-        if (handler == null)
-            return;
-        int type = Bytes.getU16(c.body, 0);
+    protected abstract void react() throws IOException;
 
-        switch (type) {
-          case EVENT_CIRCSTATUS:
-              handler.circuitStatus(c.body[2],
-                                    (int)Bytes.getU32(c.body, 3),
-                                    Bytes.getNulTerminatedStr(c.body, 7));
-              break;
-          case EVENT_STREAMSTATUS:
-              handler.streamStatus(c.body[2],
-                                   (int)Bytes.getU32(c.body, 3),
-                                   Bytes.getNulTerminatedStr(c.body, 7));
-              break;
-          case EVENT_ORCONNSTATUS:
-              handler.orConnStatus(c.body[2],
-                                   Bytes.getNulTerminatedStr(c.body, 3));
-              break;
-          case EVENT_BANDWIDTH:
-              handler.bandwidthUsed(Bytes.getU32(c.body, 2),
-                                    Bytes.getU32(c.body, 6));
-              break;
-          case EVENT_NEWDESCRIPTOR:
-              List lst = new ArrayList();
-              Bytes.splitStr(lst, c.body, 2, (byte)',');
-              handler.newDescriptors(lst);
-              break;
-          case EVENT_MSG_DEBUG:
-          case EVENT_MSG_INFO:
-          case EVENT_MSG_NOTICE:
-          case EVENT_MSG_WARN:
-          case EVENT_MSG_ERROR:
-              handler.message(type, Bytes.getNulTerminatedStr(c.body, 2));
-              break;
-          default:
-              throw new TorControlSyntaxError("Unrecognized event type.");
-        }
-    }
 
     /** Change the value of the configuration option 'key' to 'val'.
      */
@@ -285,17 +81,6 @@
         setConf(lst);
     }
 
-    /** Change the values of the configuration options stored in
-     * 'kvList'.  (The format is "key value"). */
-    public void setConf(Collection kvList) throws IOException {
-        StringBuffer b = new StringBuffer();
-        for (Iterator it = kvList.iterator(); it.hasNext(); ) {
-            String kv = (String) it.next();
-            b.append(kv).append("\n");
-        }
-        sendAndWaitForResponse(CMD_SETCONF, b.toString().getBytes());
-    }
-
     /** Change the values of the configuration options stored in kvMap. */
     public void setConf(Map kvMap) throws IOException {
         List lst = new ArrayList();
@@ -306,6 +91,10 @@
         setConf(lst);
     }
 
+    /** Change the values of the configuration options stored in
+     * 'kvList'.  (The format is "key value"). */
+    public abstract void setConf(Collection kvList) throws IOException;
+
     /** Return the value of the configuration option 'key' */
     public String getConf(String key) throws IOException {
         List lst = new ArrayList();
@@ -315,82 +104,26 @@
     }
 
     /** Return a key-value map for the configuration options in 'keys' */
-    public Map getConf(Collection keys) throws IOException {
-        StringBuffer s = new StringBuffer();
-        for (Iterator it = keys.iterator(); it.hasNext(); ) {
-            String key = (String) it.next();
-            s.append(key).append("\n");
-        }
-        Cmd c = sendAndWaitForResponse(CMD_GETCONF, s.toString().getBytes(),
-                                       CMD_CONFVALUE);
-        List lines = new ArrayList();
-        Bytes.splitStr(lines, c.body, 0, (byte)'\n');
-        Map result = new HashMap();
-        for (Iterator it = lines.iterator(); it.hasNext(); ) {
-            String kv = (String) it.next();
-            int idx = kv.indexOf(' ');
-            result.put(kv.substring(0, idx),
-                       kv.substring(idx+1));
-        }
-        return result;
-    }
+    public abstract Map getConf(Collection keys) throws IOException;
 
     /** Tell Tor to begin sending us events of the types listed in 'events'.
      * Elements must be one of the EVENT_* values from TorControlCommands */
-    public void setEvents(List events) throws IOException {
-        byte[] ba = new byte[events.size() * 2];
-        int i;
-        Iterator it;
-        for(i=0, it = events.iterator(); it.hasNext(); i += 2) {
-            short event = ((Number)it.next()).shortValue();
-            Bytes.setU16(ba, i, event);
-        }
-        sendAndWaitForResponse(CMD_SETEVENTS, ba);
-        System.out.println("OK");
-    }
+    public abstract void setEvents(List events) throws IOException;
 
     /** Send Tor an authentication sequence 'auth' */
     // XXXX more info about how to set this up securely.
-    public void authenticate(byte[] auth) throws IOException {
-        if (auth == null)
-            auth = new byte[0];
-        sendAndWaitForResponse(CMD_AUTH, auth);
-    }
+    public abstract void authenticate(byte[] auth) throws IOException;
 
     /** Tell Tor to save the value of its configuration to disk. */
-    public void saveConf() throws IOException {
-        sendAndWaitForResponse(CMD_SAVECONF, new byte[0]);
-    }
+    public abstract void saveConf() throws IOException;
 
     /** Send a signal to the Tor process. */
-    public void signal(int signal) throws IOException {
-        if (signal != SIGNAL_HUP && signal != SIGNAL_INT &&
-            signal != SIGNAL_USR1 && signal != SIGNAL_USR2 &&
-            signal != SIGNAL_TERM)
-            throw new Error("Unrecognized value for signal()");
-        byte[] ba = { (byte)signal };
-        sendAndWaitForResponse(CMD_SIGNAL, ba);
-    }
+    public abstract void signal(String signal) throws IOException;
 
     /** Tell Tor to replace incoming addresses with those as listed in 'kvLines'.
      */
-    public Map mapAddresses(Collection kvLines) throws IOException {
-        StringBuffer sb = new StringBuffer();
-        for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
-            sb.append((String)it.next()).append("\n");
-        }
-        Cmd c = sendAndWaitForResponse(CMD_MAPADDRESS, sb.toString().getBytes());
-        Map result = new HashMap();
-        List lst = new ArrayList();
-        Bytes.splitStr(lst, c.body, 0, (byte)'\n');
-        for (Iterator it = lst.iterator(); it.hasNext(); ) {
-            String kv = (String) it.next();
-            int idx = kv.indexOf(' ');
-            result.put(kv.substring(0, idx),
-                       kv.substring(idx+1));
-        }
-        return result;
-    }
+    public abstract Map mapAddresses(Collection kvLines) throws IOException;
+
     public Map mapAddresses(Map addresses) throws IOException {
         List kvList = new ArrayList();
         for (Iterator it = addresses.entrySet().iterator(); it.hasNext(); ) {
@@ -399,6 +132,7 @@
         }
         return mapAddresses(kvList);
     }
+
     public String mapAddress(String fromAddr, String toAddr) throws IOException {
         List lst = new ArrayList();
         lst.add(fromAddr+" "+toAddr+"\n");
@@ -407,26 +141,7 @@
     }
 
     /** Look up the information values listed in keys. */
-    public Map getInfo(Collection keys) throws IOException {
-        StringBuffer sb = new StringBuffer();
-        for (Iterator it = keys.iterator(); it.hasNext(); ) {
-            sb.append(((String)it.next())+"\n");
-        }
-        Cmd c = sendAndWaitForResponse(CMD_GETINFO, sb.toString().getBytes(),
-                                       CMD_INFOVALUE);
-        Map m = new HashMap();
-        List lst = new ArrayList();
-        Bytes.splitStr(lst, c.body, 0, (byte)0);
-        if ((lst.size() % 2) != 0)
-            throw new TorControlSyntaxError(
-                                     "Odd number of substrings from GETINFO");
-        for (Iterator it = lst.iterator(); it.hasNext(); ) {
-            Object k = it.next();
-            Object v = it.next();
-            m.put(k, v);
-        }
-        return m;
-    }
+    public abstract Map getInfo(Collection keys) throws IOException;
 
     /** Return the value of the information field 'key' */
     public String getInfo(String key) throws IOException {
@@ -440,62 +155,29 @@
      * Tell Tor to extend the circuit identified by 'circID' through the
      * servers named in the list 'path'.
      */
-    public int extendCircuit(int circID, String path) throws IOException {
-        byte[] p = path.getBytes();
-        byte[] ba = new byte[p.length+4];
-        Bytes.setU32(ba, 0, circID);
-        System.arraycopy(p, 0, ba, 4, p.length);
-        Cmd c = sendAndWaitForResponse(CMD_EXTENDCIRCUIT, ba);
-        return Bytes.getU32(c.body, 0);
-    }
+    public abstract int extendCircuit(String circID, String path) throws IOException;
 
     /**
      * Tell Tor to attach the stream identified by 'streamID' to the circuit
      * identified by 'circID'.
      */
-    public void attachStream(int streamID, int circID) throws IOException {
-        byte[] ba = new byte[8];
-        Bytes.setU32(ba, 0, streamID);
-        Bytes.setU32(ba, 4, circID);
-        sendAndWaitForResponse(CMD_ATTACHSTREAM, ba);
-    }
+    public abstract void attachStream(String streamID, String circID) throws IOException;
 
     /** Tell Tor about the server descriptor in 'desc' */
-    public String postDescriptor(byte[] desc) throws IOException {
-        return new String(
-                 sendAndWaitForResponse(CMD_POSTDESCRIPTOR, desc).body);
-    }
+    public abstract String postDescriptor(String desc) throws IOException;
 
     /** Tell Tor to change the target of the stream identified by 'streamID'
      * to 'address'.
      */
-    public void redirectStream(int streamID, String address) throws IOException {
-        byte[] addr = address.getBytes();
-        byte[] ba = new byte[addr.length+4];
-        Bytes.setU32(ba, 0, streamID);
-        System.arraycopy(addr, 0, ba, 4, addr.length);
-        sendAndWaitForResponse(CMD_REDIRECTSTREAM, ba);
-    }
+    public abstract void redirectStream(String streamID, String address) throws IOException;
 
     /** Tell Tor to close the stream identified by 'streamID'.
      */
-    public void closeStream(int streamID, byte reason, byte flags)
-        throws IOException {
-        byte[] ba = new byte[6];
-        Bytes.setU32(ba, 0, streamID);
-        ba[4] = reason;
-        ba[5] = flags;
-        sendAndWaitForResponse(CMD_CLOSESTREAM, ba);
-    }
+    public abstract void closeStream(String streamID, byte reason, byte flags)
+        throws IOException;
 
     /** Tell Tor to close the circuit identified by 'streamID'.
      */
-    public void closeCircuit(int circID, byte flags) throws IOException {
-        byte[] ba = new byte[5];
-        Bytes.setU32(ba, 0, circID);
-        ba[4] = flags;
-        sendAndWaitForResponse(CMD_CLOSECIRCUIT, ba);
-    }
-
+    public abstract void closeCircuit(String circID, byte flags) throws IOException;
 
-}
\ No newline at end of file
+}

--- NEW FILE: TorControlConnection0.java ---
// $Id: TorControlConnection0.java,v 1.1 2005/06/21 21:49:30 nickm Exp $
// Copyright 2005 Nick Mathewson, Roger Dingledine
// See LICENSE file for copying information
package net.freehaven.tor.control;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/** DOCDOC */
public class TorControlConnection0 extends TorControlConnection
    implements TorControlCommands
{
    protected java.io.DataOutputStream outStream;
    protected java.io.DataInputStream inStream;

    static class Cmd {
        public int type;
        public byte[] body;

        Cmd(int t, byte[] b) { type = t; body = b; }
        Cmd(int t, int l) { type = t; body = new byte[l]; };
    }



    /** 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 TorControlConnection0(java.net.Socket connection)
        throws IOException {
        this(connection.getInputStream(), connection.getOutputStream());
    }

    /** Create a new TorControlConnection to communicate with Tor over
     * an arbitrary pair of data streams.
     */
    public TorControlConnection0(java.io.InputStream i, java.io.OutputStream o)
        throws IOException {
        this.outStream = new java.io.DataOutputStream(o);
        this.inStream = new java.io.DataInputStream(i);
        this.waiters = new LinkedList();
    }

    /** helper: sends a single (unfragmentable) command to Tor */
    protected final void sendCommand0(int type, byte[] cmd)
        throws IOException {
        int length = cmd == null ? 0 : cmd.length;
        outStream.writeShort((short)length);
        outStream.writeShort(type);
        if (cmd != null)
            outStream.write(cmd);
    }

    /** helper: sends a single (possibly fragmented) command to Tor */
    protected void sendCommand(short type, byte[] cmd) throws IOException {
        synchronized(this.outStream) {
            if (cmd == null || cmd.length <= 65535) {
                sendCommand0(type, cmd);
                return;
            }
            int length = cmd.length;
            outStream.writeShort(65535);
            outStream.writeShort(CMD_FRAGMENTHEADER);
            outStream.writeShort(type);
            outStream.writeInt(length);
            outStream.write(cmd, 0, 65535);
            for (int pos=65535; pos < length; pos += 65535) {
                int flen = length-pos < 65535 ? length-pos : 65535;
                outStream.writeShort(flen);
                outStream.writeShort(CMD_FRAGMENT);
                this.outStream.write(cmd, pos, flen);
            }
        }
    }

    /** helper: read a possibly fragmented command from Tor */
    protected final Cmd readCommand0() throws IOException {
        int len = this.inStream.readUnsignedShort();
        int cmd = this.inStream.readUnsignedShort();
        byte[] result = new byte[len];
        this.inStream.readFully(result);
        return new Cmd(cmd, result);
    }

    /** Read a command from Tor, defragmenting as necessary */
    protected Cmd readCommand() throws IOException {
        synchronized (inStream) {
            Cmd c = readCommand0();
            if (c.type != CMD_FRAGMENT && c.type != CMD_FRAGMENTHEADER)
                return c;

            if (c.type == CMD_FRAGMENT)
                throw new TorControlSyntaxError("Fragment without header");

            int realType = Bytes.getU16(c.body, 0);
            int realLen = Bytes.getU32(c.body, 2);

            Cmd out = new Cmd(realType, realLen);
            System.arraycopy(c.body, 6, out.body, 0, c.body.length-6);
            int pos = c.body.length-6;
            while (pos < realLen) {
                c = readCommand0();
                if (c.type != CMD_FRAGMENT)
                    throw new TorControlSyntaxError("Incomplete fragmented message");
                System.arraycopy(c.body, 0, out.body, pos, c.body.length);
                pos += c.body.length;
            }
            return out;
        }
    }

    /** helper: implement the main background loop. */
    protected void react() throws IOException {
        while (true) {
            Cmd c = readCommand();
            if (c.type == CMD_EVENT)
                handleEvent(c);
            else {
                Waiter w;
                synchronized (waiters) {
                    w = (Waiter) waiters.removeFirst();
                }
                w.setResponse(c);
            }
        }
    }

    /** helper: Send a command and wait for the next reponse type command
     * to be received (in order) */
    protected synchronized Cmd _sendAndWaitForResponse(short type, byte[] cmd)
        throws IOException {
        Waiter w = new Waiter();
        synchronized (waiters) {
            sendCommand(type, cmd);
            waiters.addLast(w);
        }
        return (Cmd) w.getResponse();
    }

    /** Send a message to Tor, and wait for a respose.
     *
     * @throw TorControlError if Tor tells us about an error
     * @throw TorControlSyntaxError if the response type wasn't exType1...4
     **/
    protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
                   short exType1, short exType2, short exType3, short exType4)
        throws IOException {

        Cmd c = _sendAndWaitForResponse(type, cmd);
        if (c.type == CMD_ERROR)
            throw new TorControlError(Bytes.getU16(c.body, 0),
                                      Bytes.getNulTerminatedStr(c.body, 2));
        if (c.type == exType1 || c.type == exType2 || c.type == exType3 ||
            c.type == exType4)
            return c;

        throw new TorControlSyntaxError("Unexpected reply type: "+c.type);
    }

    protected Cmd sendAndWaitForResponse(short type, byte[] cmd)
        throws IOException {
        return sendAndWaitForResponse(type, cmd, CMD_DONE, CMD_DONE, CMD_DONE, CMD_DONE);
    }


    protected Cmd sendAndWaitForResponse(short type, byte[] cmd, short exType1)
        throws IOException {
        return sendAndWaitForResponse(type, cmd, exType1, exType1, exType1,
                                      exType1);
    }

    protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
                                    short exType1, short exType2)
        throws IOException {
        return sendAndWaitForResponse(type, cmd, exType1, exType2, exType2,
                                      exType2);
    }


    protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
                                   short exType1, short exType2, short exType3)
        throws IOException {
        return sendAndWaitForResponse(type, cmd, exType1, exType2, exType3,
                                      exType3);
    }

    /** Helper: decode a CMD_EVENT command and dispatch it to our
     * EventHandler (if any). */
    protected void handleEvent(Cmd c) {
        if (handler == null)
            return;
        int type = Bytes.getU16(c.body, 0);

        switch (type) {
          case EVENT_CIRCSTATUS:
              handler.circuitStatus(CIRC_STATUS_NAMES[c.body[2]],
                                    Bytes.getU32S(c.body, 3),
                                    Bytes.getNulTerminatedStr(c.body, 7));
              break;
          case EVENT_STREAMSTATUS:
              handler.streamStatus(STREAM_STATUS_NAMES[c.body[2]],
                                   Bytes.getU32S(c.body, 3),
                                   Bytes.getNulTerminatedStr(c.body, 7));
              break;
          case EVENT_ORCONNSTATUS:
              handler.orConnStatus(OR_CONN_STATUS_NAMES[c.body[2]],
                                   Bytes.getNulTerminatedStr(c.body, 3));
              break;
          case EVENT_BANDWIDTH:
              handler.bandwidthUsed(Bytes.getU32(c.body, 2),
                                    Bytes.getU32(c.body, 6));
              break;
          case EVENT_NEWDESCRIPTOR:
              List lst = new ArrayList();
              Bytes.splitStr(lst, c.body, 2, (byte)',');
              handler.newDescriptors(lst);
              break;
          case EVENT_MSG_DEBUG:
          case EVENT_MSG_INFO:
          case EVENT_MSG_NOTICE:
          case EVENT_MSG_WARN:
          case EVENT_MSG_ERROR:
              handler.message(type, Bytes.getNulTerminatedStr(c.body, 2));
              break;
          default:
              throw new TorControlSyntaxError("Unrecognized event type.");
        }
    }

    /** Change the values of the configuration options stored in
     * 'kvList'.  (The format is "key value"). */
    public void setConf(Collection kvList) throws IOException {
        StringBuffer b = new StringBuffer();
        for (Iterator it = kvList.iterator(); it.hasNext(); ) {
            String kv = (String) it.next();
            b.append(kv).append("\n");
        }
        sendAndWaitForResponse(CMD_SETCONF, b.toString().getBytes());
    }


    public Map getConf(Collection keys) throws IOException {
        StringBuffer s = new StringBuffer();
        for (Iterator it = keys.iterator(); it.hasNext(); ) {
            String key = (String) it.next();
            s.append(key).append("\n");
        }
        Cmd c = sendAndWaitForResponse(CMD_GETCONF, s.toString().getBytes(),
                                       CMD_CONFVALUE);
        List lines = new ArrayList();
        Bytes.splitStr(lines, c.body, 0, (byte)'\n');
        Map result = new HashMap();
        for (Iterator it = lines.iterator(); it.hasNext(); ) {
            String kv = (String) it.next();
            int idx = kv.indexOf(' ');
            result.put(kv.substring(0, idx),
                       kv.substring(idx+1));
        }
        return result;
    }

    public void setEvents(List events) throws IOException {
        byte[] ba = new byte[events.size() * 2];
        int i;
        Iterator it;
        for(i=0, it = events.iterator(); it.hasNext(); i += 2) {
            short event = ((Number)it.next()).shortValue();
            Bytes.setU16(ba, i, event);
        }
        sendAndWaitForResponse(CMD_SETEVENTS, ba);
        System.out.println("OK");
    }

    public void authenticate(byte[] auth) throws IOException {
        if (auth == null)
            auth = new byte[0];
        sendAndWaitForResponse(CMD_AUTH, auth);
    }

    public void saveConf() throws IOException {
        sendAndWaitForResponse(CMD_SAVECONF, new byte[0]);
    }

    public void signal(String signal) throws IOException {
        int sig;
        signal = signal.toUpperCase();
        if (signal.equals("HUP") || signal.equals("RELOAD"))
            sig = SIGNAL_HUP;
        else if (signal.equals("INT") || signal.equals("SHUTDOWN"))
            sig = SIGNAL_HUP;
        else if (signal.equals("USR1") || signal.equals("DUMP"))
            sig = SIGNAL_HUP;
        else if (signal.equals("USR2") || signal.equals("DEBUG"))
            sig = SIGNAL_HUP;
        else if (signal.equals("TERM") || signal.equals("HALT"))
            sig = SIGNAL_HUP;
        else
            throw new Error("Unrecognized value for signal()");
        byte[] ba = { (byte)sig };
        sendAndWaitForResponse(CMD_SIGNAL, ba);
    }

    public Map mapAddresses(Collection kvLines) throws IOException {
        StringBuffer sb = new StringBuffer();
        for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
            sb.append((String)it.next()).append("\n");
        }
        Cmd c = sendAndWaitForResponse(CMD_MAPADDRESS, sb.toString().getBytes());
        Map result = new HashMap();
        List lst = new ArrayList();
        Bytes.splitStr(lst, c.body, 0, (byte)'\n');
        for (Iterator it = lst.iterator(); it.hasNext(); ) {
            String kv = (String) it.next();
            int idx = kv.indexOf(' ');
            result.put(kv.substring(0, idx),
                       kv.substring(idx+1));
        }
        return result;
    }

    public Map getInfo(Collection keys) throws IOException {
        StringBuffer sb = new StringBuffer();
        for (Iterator it = keys.iterator(); it.hasNext(); ) {
            sb.append(((String)it.next())+"\n");
        }
        Cmd c = sendAndWaitForResponse(CMD_GETINFO, sb.toString().getBytes(),
                                       CMD_INFOVALUE);
        Map m = new HashMap();
        List lst = new ArrayList();
        Bytes.splitStr(lst, c.body, 0, (byte)0);
        if ((lst.size() % 2) != 0)
            throw new TorControlSyntaxError(
                                     "Odd number of substrings from GETINFO");
        for (Iterator it = lst.iterator(); it.hasNext(); ) {
            Object k = it.next();
            Object v = it.next();
            m.put(k, v);
        }
        return m;
    }

    public int extendCircuit(String circID, String path) throws IOException {
        byte[] p = path.getBytes();
        byte[] ba = new byte[p.length+4];
        Bytes.setU32(ba, 0, (int)Long.parseLong(circID));
        System.arraycopy(p, 0, ba, 4, p.length);
        Cmd c = sendAndWaitForResponse(CMD_EXTENDCIRCUIT, ba);
        return Bytes.getU32(c.body, 0);
    }

    public void attachStream(String streamID, String circID) 
        throws IOException {
        byte[] ba = new byte[8];
        Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
        Bytes.setU32(ba, 4, (int)Long.parseLong(circID));
        sendAndWaitForResponse(CMD_ATTACHSTREAM, ba);
    }

    /** Tell Tor about the server descriptor in 'desc' */
    public String postDescriptor(String desc) throws IOException {
        return new String(
             sendAndWaitForResponse(CMD_POSTDESCRIPTOR, desc.getBytes()).body);
    }

    /** Tell Tor to change the target of the stream identified by 'streamID'
     * to 'address'.
     */
    public void redirectStream(String streamID, String address) throws IOException {
        byte[] addr = address.getBytes();
        byte[] ba = new byte[addr.length+4];
        Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
        System.arraycopy(addr, 0, ba, 4, addr.length);
        sendAndWaitForResponse(CMD_REDIRECTSTREAM, ba);
    }

    /** Tell Tor to close the stream identified by 'streamID'.
     */
    public void closeStream(String streamID, byte reason, byte flags)
        throws IOException {
        byte[] ba = new byte[6];
        Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
        ba[4] = reason;
        ba[5] = flags;
        sendAndWaitForResponse(CMD_CLOSESTREAM, ba);
    }

    /** Tell Tor to close the circuit identified by 'streamID'.
     */
    public void closeCircuit(String circID, byte flags) throws IOException {
        byte[] ba = new byte[5];
        Bytes.setU32(ba, 0, (int)Long.parseLong(circID));
        ba[4] = flags;
        sendAndWaitForResponse(CMD_CLOSECIRCUIT, ba);
    }

}