         }
     }
 }
+
+import flash.display.Sprite;
+import flash.events.Event;
+import flash.events.IOErrorEvent;
+import flash.events.ProgressEvent;
+import flash.events.SecurityErrorEvent;
+import flash.net.Socket;
+import flash.utils.ByteArray;
+
+/* An instance of a client-relay connection. */
+class ProxyPair
+{
+    // Address ({host, port}) of client.
+    private var addr_c:Object;
+    // Address ({host, port}) of relay.
+    private var addr_r:Object;
+
+    // Socket to client.
+    private var s_c:Socket;
+    // Socket to relay.
+    private var s_r:Socket;
+
+    // Parent swfcat, for UI updates.
+    private var ui:swfcat;
+
+    private function log(msg:String):void
+    {
+        ui.puts(id() + ": " + msg)
+    }
+
+    // String describing this pair for output.
+    private function id():String
+    {
+        return "<" + this.addr_c.host + ":" + this.addr_c.port +
+            "," + this.addr_r.host + ":" + this.addr_r.port + ">";
+    }
+
+    public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object)
+    {
+        this.ui = ui;
+        this.addr_c = addr_c;
+        this.addr_r = addr_r;
+    }
+
+    public function connect():void
+    {
+        s_r = new Socket();
+
+        s_r.addEventListener(Event.CONNECT, tor_connected);
+        s_r.addEventListener(Event.CLOSE, function (e:Event):void {
+            log("Tor: closed.");
+            if (s_c.connected)
+                s_c.close();
+        });
+        s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
+            log("Tor: I/O error: " + e.text + ".");
+            if (s_c.connected)
+                s_c.close();
+        });
+        s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
+            log("Tor: security error: " + e.text + ".");
+            if (s_c.connected)
+                s_c.close();
+        });
+
+        log("Tor: connecting to " + addr_r.host + ":" + addr_r.port + ".");
+        s_r.connect(addr_r.host, addr_r.port);
+    }
+
+    private function tor_connected(e:Event):void
+    {
+        log("Tor: connected.");
+
+        s_c = new Socket();
+
+        s_c.addEventListener(Event.CONNECT, client_connected);
+        s_c.addEventListener(Event.CLOSE, function (e:Event):void {
+            log("Client: closed.");
+            if (s_r.connected)
+                s_r.close();
+        });
+        s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
+            log("Client: I/O error: " + e.text + ".");
+            if (s_r.connected)
+                s_r.close();
+        });
+        s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
+            log("Client: security error: " + e.text + ".");
+            if (s_r.connected)
+                s_r.close();
+        });
+
+        log("Client: connecting to " + addr_c.host + ":" + addr_c.port + ".");
+        s_c.connect(addr_c.host, addr_c.port);
+    }
+
+    private function client_connected(e:Event):void
+    {
+        log("Client: connected.");
+
+        s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
+            var bytes:ByteArray = new ByteArray();
+            s_r.readBytes(bytes, 0, e.bytesLoaded);
+            log("Tor: read " + bytes.length + ".");
+            s_c.writeBytes(bytes);
+        });
+        s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
+            var bytes:ByteArray = new ByteArray();
+            s_c.readBytes(bytes, 0, e.bytesLoaded);
+            log("Client: read " + bytes.length + ".");
+            s_r.writeBytes(bytes);
+        });
+    }
+}


        private function fac_connected(e:Event):void
        {
            s_c = new Socket();

            puts("Facilitator: connected.");

            s_f.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var client_spec:String;
                var client_addr:Object;

                client_spec = s_f.readMultiByte(e.bytesLoaded, "utf-8");
                puts("Facilitator: got \"" + client_spec + "\"");

                client_addr = parse_addr_spec(client_spec);
                if (!client_addr) {
                    puts("Error: Client spec must be in the form \"host:port\".");
                    return;
                }
                if (client_addr.host == "0.0.0.0" && client_addr.port == 0) {
                    puts("Error: Facilitator has no clients.");
                    return;
                }

                puts("Client: connecting to " + client_addr.host + ":" + client_addr.port + ".");
                s_c.connect(client_addr.host, client_addr.port);
                s_c.addEventListener(Event.CONNECT, client_connected);
            });

            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
        }

        private function client_connected(e:Event):void
        {
            puts("Client: connected.");

            s_t.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var bytes:ByteArray = new ByteArray();
                s_t.readBytes(bytes, 0, e.bytesLoaded);
                puts("Tor: read " + bytes.length + ".");
                s_c.writeBytes(bytes);
            });
            s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var bytes:ByteArray = new ByteArray();
                s_c.readBytes(bytes, 0, e.bytesLoaded);
                puts("Client: read " + bytes.length + ".");
                s_t.writeBytes(bytes);
            });
        }

        /* Parse an address in the form "host:port". Returns an Object with

                return;
            }

            go();
        }

        private function go():void
        {
            s_f = new Socket();

            s_f.addEventListener(Event.CONNECT, fac_connected);
            s_f.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Facilitator: closed connection.");
            });
            s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
