[or-cvs] Implement (modulo bugs) v1 control protocol in java

Nick Mathewson nickm at seul.org
Thu Jun 23 21:22:06 UTC 2005


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

Modified Files:
	Bytes.java EventHandler.java PasswordDigest.java 
	TorControlConnection.java TorControlConnection0.java 
	TorControlError.java 
Added Files:
	TorControlConnection1.java 
Log Message:
Implement (modulo bugs) v1 control protocol in java

Index: Bytes.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/Bytes.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- Bytes.java	21 Jun 2005 21:49:30 -0000	1.2
+++ Bytes.java	23 Jun 2005 21:22:04 -0000	1.3
@@ -3,7 +3,9 @@
 // See LICENSE file for copying information
 package net.freehaven.tor.control;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.StringTokenizer;
 
 /**
  * Static class to do bytewise structure manipulation in Java.
@@ -79,5 +81,33 @@
         }
     }
 
+    /**
+     * Read bytes from 'ba' starting at 'pos', dividing them into strings
+     * along the character in 'split' and writing them into 'lst'
+     */
+    public static List splitStr(List lst, String str) {
+        if (lst == null)
+            lst = new ArrayList();
+        StringTokenizer st = new StringTokenizer(str);
+        while (st.hasMoreTokens())
+            lst.add(st.nextToken());
+        return lst;
+    }
+
+    private static final char[] NYBBLES = {
+        '0', '1', '2', '3', '4', '5', '6', '7',
+        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    public static final String hex(byte[] ba) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < ba.length; ++i) {
+            int b = ((int)ba[i]) & 0xff;
+            buf.append(NYBBLES[b >> 4]);
+            buf.append(NYBBLES[b&0x0f]);
+        }
+        return buf.toString();
+    }
+
     private Bytes() {};
 }

Index: EventHandler.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/EventHandler.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- EventHandler.java	21 Jun 2005 21:49:30 -0000	1.2
+++ EventHandler.java	23 Jun 2005 21:22:04 -0000	1.3
@@ -37,5 +37,11 @@
     /**
      * Invoked when Tor logs a message.
      */
-    public void message(int type, String msg);
+    public void message(String severity, String msg);
+    /**
+     * Invoked in an unspecified handler.
+     */
+    public void unrecognized(String type, String msg);
+
 }
+

Index: PasswordDigest.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/PasswordDigest.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- PasswordDigest.java	4 Jun 2005 02:42:55 -0000	1.1
+++ PasswordDigest.java	23 Jun 2005 21:22:04 -0000	1.2
@@ -97,13 +97,7 @@
     /** 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) {
-        StringBuffer buf = new StringBuffer();
-        for (int i = 0; i < ba.length; ++i) {
-            int b = ((int)ba[i]) & 0xff;
-            buf.append(NYBBLES[b >> 4]);
-            buf.append(NYBBLES[b&0x0f]);
-        }
-        return buf.toString();
+        return Bytes.hex(ba);
     }
 
 }
\ 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.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- TorControlConnection.java	21 Jun 2005 21:49:30 -0000	1.2
+++ TorControlConnection.java	23 Jun 2005 21:22:04 -0000	1.3
@@ -41,7 +41,6 @@
         this.waiters = new LinkedList();
     }
 
-
     /** Set the EventHandler object that will be notified of any
      * events Tor delivers to this connection.  To make Tor send us
      * events, call listenForEvents(). */
@@ -155,7 +154,7 @@
      * Tell Tor to extend the circuit identified by 'circID' through the
      * servers named in the list 'path'.
      */
-    public abstract int extendCircuit(String circID, String path) throws IOException;
+    public abstract String extendCircuit(String circID, String path) throws IOException;
 
     /**
      * Tell Tor to attach the stream identified by 'streamID' to the circuit
@@ -173,11 +172,11 @@
 
     /** Tell Tor to close the stream identified by 'streamID'.
      */
-    public abstract void closeStream(String streamID, byte reason, byte flags)
+    public abstract void closeStream(String streamID, byte reason)
         throws IOException;
 
     /** Tell Tor to close the circuit identified by 'streamID'.
      */
-    public abstract void closeCircuit(String circID, byte flags) throws IOException;
+    public abstract void closeCircuit(String circID, boolean ifUnused) throws IOException;
 
 }

Index: TorControlConnection0.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/TorControlConnection0.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- TorControlConnection0.java	21 Jun 2005 21:49:30 -0000	1.1
+++ TorControlConnection0.java	23 Jun 2005 21:22:04 -0000	1.2
@@ -27,8 +27,6 @@
         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. */
@@ -222,11 +220,19 @@
               handler.newDescriptors(lst);
               break;
           case EVENT_MSG_DEBUG:
+              handler.message("DEBUG", Bytes.getNulTerminatedStr(c.body, 2));
+              break;
           case EVENT_MSG_INFO:
+              handler.message("INFO", Bytes.getNulTerminatedStr(c.body, 2));
+              break;
           case EVENT_MSG_NOTICE:
+              handler.message("NOTICE", Bytes.getNulTerminatedStr(c.body, 2));
+              break;
           case EVENT_MSG_WARN:
+              handler.message("WARN", Bytes.getNulTerminatedStr(c.body, 2));
+              break;
           case EVENT_MSG_ERROR:
-              handler.message(type, Bytes.getNulTerminatedStr(c.body, 2));
+              handler.message("ERR", Bytes.getNulTerminatedStr(c.body, 2));
               break;
           default:
               throw new TorControlSyntaxError("Unrecognized event type.");
@@ -345,13 +351,13 @@
         return m;
     }
 
-    public int extendCircuit(String circID, String path) throws IOException {
+    public String 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);
+        return Integer.toString(Bytes.getU32(c.body, 0));
     }
 
     public void attachStream(String streamID, String circID) 
@@ -381,21 +387,21 @@
 
     /** Tell Tor to close the stream identified by 'streamID'.
      */
-    public void closeStream(String streamID, byte reason, byte flags)
+    public void closeStream(String streamID, byte reason)
         throws IOException {
         byte[] ba = new byte[6];
         Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
         ba[4] = reason;
-        ba[5] = flags;
+        ba[5] = (byte)0;
         sendAndWaitForResponse(CMD_CLOSESTREAM, ba);
     }
 
     /** Tell Tor to close the circuit identified by 'streamID'.
      */
-    public void closeCircuit(String circID, byte flags) throws IOException {
+    public void closeCircuit(String circID, boolean ifUnused) throws IOException {
         byte[] ba = new byte[5];
         Bytes.setU32(ba, 0, (int)Long.parseLong(circID));
-        ba[4] = flags;
+        ba[4] = (byte)(ifUnused? 1 : 0);
         sendAndWaitForResponse(CMD_CLOSECIRCUIT, ba);
     }
 

--- NEW FILE: TorControlConnection1.java ---
// $Id: TorControlConnection1.java,v 1.1 2005/06/23 21:22:04 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;
import java.util.StringTokenizer;

/** DOCDOC */
public class TorControlConnection1 extends TorControlConnection
    implements TorControlCommands
{
    protected java.io.BufferedReader input;
    protected java.io.Writer output;

    static class ReplyLine {
        public String status;
        public String msg;
        public String rest;

        ReplyLine(String status, String msg, String rest) {
            this.status = status; this.msg = msg; this.rest = rest;
        }
    }

    /** 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 TorControlConnection1(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 TorControlConnection1(java.io.InputStream i, java.io.OutputStream o)
        throws IOException {
        this(new java.io.InputStreamReader(i),
             new java.io.OutputStreamWriter(o));
    }

    public TorControlConnection1(java.io.Reader i, java.io.Writer o)
        throws IOException {
        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();
    }

    protected final void writeEscaped(String s) throws IOException {
        StringTokenizer st = new StringTokenizer(s, "\n");
        while (st.hasMoreTokens()) {
            String line = st.nextToken();
            if (line.startsWith("."))
                output.write(".");
            output.write(line);
            if (line.endsWith("\r"))
                output.write("\n");
            else
                output.write("\r\n");
        }
        output.write(".\r\n");
    }

    protected static final String quote(String s) {
        StringBuffer sb = new StringBuffer("\"");
        for (int i = 0; i < s.length(); ++i) {
            char c = s.charAt(i);
            switch (c)
                {
                case '\r':
                case '\n':
                case '\\':
                case '\"':
                    sb.append('\\');
                }
            sb.append(c);
        }
        sb.append('\"');
        return sb.toString();
    }

    protected final ArrayList readReply() throws IOException {
        ArrayList reply = new ArrayList();
        char c;
        do {
            String line = input.readLine();
            if (line.length() < 4)
                throw new TorControlSyntaxError("Line too short");
            String status = line.substring(0,3);
            c = line.charAt(3);
            String msg = line.substring(4);
            String rest = null;
            if (c == '+') {
                StringBuffer data = new StringBuffer();
                while (true) {
                    line = input.readLine();
                    if (line.equals("."))
                        break;
                    else if (line.startsWith("."))
                        line = line.substring(1);
                    data.append(line).append('\n');
                }
                rest = data.toString();
            }
            reply.add(new ReplyLine(status, msg, rest));
        } while (c != ' ');

        return reply;
    }


    /** helper: implement the main background loop. */
    protected void react() throws IOException {
        while (true) {
            ArrayList lst = readReply();
            if (((ReplyLine)lst.get(0)).status.startsWith("6"))
                handleEvent(lst);
            else {
                Waiter w;
                synchronized (waiters) {
                    w = (Waiter) waiters.removeFirst();
                }
                w.setResponse(lst);
            }
        }
    }

    protected synchronized ArrayList sendAndWaitForResponse(String s,String rest)
        throws IOException {
        Waiter w = new Waiter();
        synchronized (waiters) {
            output.write(s);
            if (rest != null)
                writeEscaped(rest);
            waiters.addLast(w);
        }
        ArrayList lst = (ArrayList) w.getResponse();
        for (Iterator i = lst.iterator(); i.hasNext(); ) {
            ReplyLine c = (ReplyLine) i.next();
            if (! c.status.startsWith("2"))
                throw new TorControlError("Error reply: "+c.msg);
        }
        return lst;
    }

    /** Helper: decode a CMD_EVENT command and dispatch it to our
     * EventHandler (if any). */
    protected void handleEvent(ArrayList events) {
        if (handler == null)
            return;

        for (Iterator i = events.iterator(); i.hasNext(); ) {
            ReplyLine line = (ReplyLine) i.next();
            int idx = line.msg.indexOf(' ');
            String tp = line.msg.substring(0, idx).toUpperCase();
            String rest = line.msg.substring(idx+1);
            if (tp.equals("CIRC")) {
                List lst = Bytes.splitStr(null, rest);
                handler.circuitStatus((String)lst.get(1),
                                      (String)lst.get(0),
                                      (String)lst.get(2));
            } else if (tp.equals("STREAM")) {
                List lst = Bytes.splitStr(null, rest);
                handler.streamStatus((String)lst.get(1),
                                     (String)lst.get(0),
                                     (String)lst.get(3));
                // XXXX circID.
            } else if (tp.equals("ORCONN")) {
                List lst = Bytes.splitStr(null, rest);
                handler.orConnStatus((String)lst.get(1), (String)lst.get(0));
            } else if (tp.equals("BW")) {
                List lst = Bytes.splitStr(null, rest);
                handler.bandwidthUsed(Integer.parseInt((String)lst.get(0)),
                                      Integer.parseInt((String)lst.get(1)));
            } else if (tp.equals("NEWDESC")) {
                List lst = Bytes.splitStr(null, rest);
                handler.newDescriptors(lst);
            } else if (tp.equals("DEBUG") ||
                       tp.equals("INFO") ||
                       tp.equals("NOTICE") ||
                       tp.equals("WARN") ||
                       tp.equals("ERR")) {
                handler.message(tp, rest);
            } else {
                handler.unrecognized(tp, rest);
            }
        }
    }

    /** Change the values of the configuration options stored in
     * 'kvList'.  (The format is "key value"). */
    public void setConf(Collection kvList) throws IOException {
        if (kvList.size() == 0)
            return;
        StringBuffer b = new StringBuffer("SETCONF");
        for (Iterator it = kvList.iterator(); it.hasNext(); ) {
            String kv = (String) it.next();
            int i = kv.indexOf(' ');
            if (i == -1)
                b.append(" ").append(kv);
            b.append(" ").append(kv.substring(0,i)).append("=")
                .append(quote(kv.substring(i+1)));
        }
        b.append("\r\n");
        sendAndWaitForResponse(b.toString(), null);
    }

    public Map getConf(Collection keys) throws IOException {
        StringBuffer sb = new StringBuffer("GETCONF");
        for (Iterator it = keys.iterator(); it.hasNext(); ) {
            String key = (String) it.next();
            sb.append(" ").append(key);
        }
        sb.append("\r\n");
        ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
        Map result = new HashMap();
        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 void setEvents(List events) throws IOException {
        StringBuffer sb = new StringBuffer("SETEVENTS");
        for (Iterator it = events.iterator(); it.hasNext(); ) {
            String event = (String) it.next();
            sb.append(" ").append(event);
        }
        sb.append("\r\n");
        sendAndWaitForResponse(sb.toString(), null);
    }

    public void authenticate(byte[] auth) throws IOException {
        String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n";
        sendAndWaitForResponse(cmd, null);
    }

    public void saveConf() throws IOException {
        sendAndWaitForResponse("SAVECONF\r\n", null);
    }

    public void signal(String signal) throws IOException {
        String cmd = "AUTHENTICATE " + signal + "\r\n";
        sendAndWaitForResponse(cmd, null);
    }

    public Map mapAddresses(Collection kvLines) throws IOException {
        StringBuffer sb = new StringBuffer("MAPADDRESS");
        for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
            String kv = (String) it.next();
            int i = kv.indexOf(' ');
            sb.append(" ").append(kv.substring(0,i)).append("=")
                .append(quote(kv.substring(i+1)));
        }
        sb.append("\r\n");
        ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
        Map result = new HashMap();
        for (Iterator it = lst.iterator(); it.hasNext(); ) {
            String kv = ((ReplyLine) it.next()).msg;
            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("GETINFO");
        for (Iterator it = keys.iterator(); it.hasNext(); ) {
            sb.append(" ").append((String)it.next());
        }
        sb.append("\r\n");
        ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
        Map m = new HashMap();
        for (Iterator it = lst.iterator(); it.hasNext(); ) {
            ReplyLine line = (ReplyLine) it.next();
            int idx = line.msg.indexOf('=');
            if (idx<0)
                break;
            String k = line.msg.substring(0,idx);
            Object v;
            if (line.rest != null) {
                v = line.rest;
            } else {
                v = line.msg.substring(idx+1);
            }
            m.put(k, v);
        }
        return m;
    }

    public String extendCircuit(String circID, String path) throws IOException {
        ArrayList lst = sendAndWaitForResponse(
                          "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null);
        return ((ReplyLine)lst.get(0)).msg;
    }

    public void attachStream(String streamID, String circID)
        throws IOException {
        sendAndWaitForResponse("ATTACHSTREAM "+streamID+" "+circID+"\r\n", null);
    }

    /** Tell Tor about the server descriptor in 'desc' */
    public String postDescriptor(String desc) throws IOException {
        ArrayList lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
        return ((ReplyLine)lst.get(0)).msg;
    }

    /** Tell Tor to change the target of the stream identified by 'streamID'
     * to 'address'.
     */
    public void redirectStream(String streamID, String address) throws IOException {
        sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n",
                               null);
    }

    /** Tell Tor to close the stream identified by 'streamID'.
     */
    public void closeStream(String streamID, byte reason)
        throws IOException {
        sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null);
    }

    /** Tell Tor to close the circuit identified by 'streamID'.
     */
    public void closeCircuit(String circID, boolean ifUnused) throws IOException {
        sendAndWaitForResponse("CLOSECIRCUIT "+circID+
                               (ifUnused?" IFUNUSED":"")+"\r\n", null);
    }

}

Index: TorControlError.java
===================================================================
RCS file: /home/or/cvsroot/control/java/net/freehaven/tor/control/TorControlError.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- TorControlError.java	4 Jun 2005 02:42:55 -0000	1.1
+++ TorControlError.java	23 Jun 2005 21:22:04 -0000	1.2
@@ -12,11 +12,16 @@
         super(s);
         errorType = type;
     }
+    public TorControlError(String s) {
+        this(-1, s);
+    }
     public int getErrorType() {
         return errorType;
     }
     public String getErrorMsg() {
         try {
+            if (errorType == -1)
+                return null;
             return TorControlCommands.ERROR_MSGS[errorType];
         } catch (ArrayIndexOutOfBoundsException ex) {
             return "Unrecongized error #"+errorType;



More information about the tor-commits mailing list