[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