[tor-commits] [flashproxy/master] Merged rtmfpcat with swfcat, now proxy can help TCP and RTMFP clients

dcf at torproject.org dcf at torproject.org
Sun Jun 12 08:56:30 UTC 2011


commit 1d66a5de55e165334cbdd35ad5d38cf4157d9a2a
Author: Nate Hardison <nate at rescomp-09-154551.stanford.edu>
Date:   Wed Jun 8 23:56:03 2011 -0700

    Merged rtmfpcat with swfcat, now proxy can help TCP and RTMFP clients
---
 Makefile             |    6 +-
 ProxyPair.as         |  273 +++++++++++++++++++
 RTMFPProxyPair.as    |  101 +++++++
 TCPProxyPair.as      |   86 ++++++
 connector.py         |    4 +-
 facilitator.py       |    7 +-
 rtmfp/ProxyPair.as   |  279 -------------------
 rtmfp/RTMFPSocket.as |    2 +-
 rtmfpcat.as          |  353 ------------------------
 swfcat.as            |  723 ++++++++++++++++++++------------------------------
 10 files changed, 756 insertions(+), 1078 deletions(-)

diff --git a/Makefile b/Makefile
index 3595385..4e22e1d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,10 @@
 MXMLC ?= mxmlc
 
-TARGETS = rtmfpcat.swf
+TARGETS = swfcat.swf
 
 all: $(TARGETS)
 
-swfcat.swf: badge.png
-
-%.swf: %.as
+%.swf: %.as badge.png
 	$(MXMLC) -output $@ -static-link-runtime-shared-libraries -define=RTMFP::CIRRUS_KEY,\"$(CIRRUS_KEY)\" $<
 
 clean:
diff --git a/ProxyPair.as b/ProxyPair.as
new file mode 100644
index 0000000..aef4e2e
--- /dev/null
+++ b/ProxyPair.as
@@ -0,0 +1,273 @@
+package
+{
+    import flash.errors.IllegalOperationError;
+    import flash.events.Event;
+    import flash.events.EventDispatcher;
+    import flash.events.IOErrorEvent;
+    import flash.events.ProgressEvent;
+    import flash.events.SecurityErrorEvent;
+    import flash.net.Socket;
+    import flash.utils.ByteArray;
+    import flash.utils.clearTimeout;
+    import flash.utils.setTimeout;
+    
+    import swfcat;
+    
+    public class ProxyPair extends EventDispatcher
+    {
+        private var ui:swfcat;
+        
+        protected var client_addr:Object;
+        
+        /* Not defined here: subclasses should define their own
+         * protected var client_socket:Object;
+         */
+         
+        private var c2r_schedule:Array;
+        
+        private var relay_addr:Object;
+        private var relay_socket:Socket;
+        private var r2c_schedule:Array;
+        
+        // Bytes per second. Set to undefined to disable limit.
+        private const RATE_LIMIT:Number = undefined; //10000;
+        // Seconds.
+        private const RATE_LIMIT_HISrelayY:Number = 5.0;
+        
+        private var rate_limit:RateLimit;
+        
+        // Callback id.
+        private var flush_id:uint;
+        
+        public function ProxyPair(self:ProxyPair, ui:swfcat)
+        {
+            if (self != this) {
+                //only a subclass can pass a valid reference to self
+            	throw new IllegalOperationError("ProxyPair cannot be instantiated directly.");
+            }
+            
+            this.ui = ui;
+            this.c2r_schedule = new Array();
+            this.r2c_schedule = new Array();
+            
+            if (RATE_LIMIT)
+                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISrelayY, RATE_LIMIT_HISrelayY);
+            else
+                rate_limit = new RateUnlimit();
+                
+            setup_relay_socket();
+            
+            /* client_socket setup should be taken */
+            /* care of in the subclass constructor */
+        }
+        
+        public function close():void
+        {
+            if (relay_socket != null && relay_socket.connected) {
+                relay_socket.close();
+            }
+            
+            /* subclasses should override to close */
+            /* their client_socket according to impl. */
+        }
+        
+        public function get connected():Boolean
+        {
+            return (relay_socket != null && relay_socket.connected);
+            
+            /* subclasses should override to check */
+            /* connectivity of their client_socket. */
+        }
+        
+        public function set client(client_addr:Object):void
+        {
+            /* subclasses should override to */
+            /* connect the client_socket here */
+        }
+        
+        public function set relay(relay_addr:Object):void
+        {
+            this.relay_addr = relay_addr;
+            log("Relay: connecting to " + relay_addr.host + ":" + relay_addr.port + ".");
+            relay_socket.connect(relay_addr.host, relay_addr.port);
+        }
+        
+        protected function transfer_bytes(src:Object, dst:Object, num_bytes:uint):void
+        {
+            /* No-op: must be overridden by subclasses */
+        }
+        
+        private function setup_relay_socket():void
+        {
+            relay_socket = new Socket();
+            relay_socket.addEventListener(Event.CONNECT, function (e:Event):void {
+                log("Relay: connected to " + relay_addr.host + ":" + relay_addr.port + ".");
+                if (connected) {
+                    dispatchEvent(new Event(Event.CONNECT));
+                }
+            });
+            relay_socket.addEventListener(Event.CLOSE, function (e:Event):void {
+                log("Relay: closed connection.");
+                close();
+            });
+            relay_socket.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
+                log("Relay: I/O error: " + e.text + ".");
+                close();
+            });
+            relay_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
+                log("Relay: security error: " + e.text + ".");
+                close();
+            });
+            relay_socket.addEventListener(ProgressEvent.SOCKET_DATA, relay_to_client);
+        }
+        
+        protected function client_to_relay(e:ProgressEvent):void
+        {
+            c2r_schedule.push(e.bytesLoaded);
+            flush();
+        }
+        
+        private function relay_to_client(e:ProgressEvent):void
+        {
+            r2c_schedule.push(e.bytesLoaded);
+            flush();
+        }
+        
+        /* Send as much data as the rate limit currently allows. */
+        private function flush():void
+        {
+            if (flush_id)
+                clearTimeout(flush_id);
+            flush_id = undefined;
+
+            if (!connected)
+                /* Can't do anything until connected. */
+                return;
+
+            while (!rate_limit.is_limited() && (c2r_schedule.length > 0 || r2c_schedule.length > 0)) {
+                var num_bytes:uint;
+                
+                if (c2r_schedule.length > 0) {
+                    num_bytes = c2r_schedule.shift();
+                    transfer_bytes(null, relay_socket, num_bytes);
+                    rate_limit.update(num_bytes);
+                }
+                
+                if (r2c_schedule.length > 0) {
+                    num_bytes = r2c_schedule.shift();
+                    transfer_bytes(relay_socket, null, num_bytes);
+                    rate_limit.update(num_bytes);
+                }
+            }
+
+            /* Call again when safe, if necessary. */
+            if (c2r_schedule.length > 0 || r2c_schedule.length > 0)
+                flush_id = setTimeout(flush, rate_limit.when() * 1000);
+        }
+        
+        /* Helper function to write output to the
+         * swfcat console. Set as protected for
+         * subclasses */
+        protected function log(s:String):void
+        {
+            ui.puts(s);
+        }
+    }
+}
+
+import flash.utils.getTimer;
+
+class RateLimit
+{
+    public function RateLimit()
+    {
+    }
+
+    public function update(n:Number):Boolean
+    {
+        return true;
+    }
+
+    public function when():Number
+    {
+        return 0.0;
+    }
+
+    public function is_limited():Boolean
+    {
+        return false;
+    }
+}
+
+class RateUnlimit extends RateLimit
+{
+    public function RateUnlimit()
+    {
+    }
+
+    public override function update(n:Number):Boolean
+    {
+        return true;
+    }
+
+    public override function when():Number
+    {
+        return 0.0;
+    }
+
+    public override function is_limited():Boolean
+    {
+        return false;
+    }
+}
+
+class BucketRateLimit extends RateLimit
+{
+    private var amount:Number;
+    private var capacity:Number;
+    private var time:Number;
+    private var last_update:uint;
+
+    public function BucketRateLimit(capacity:Number, time:Number)
+    {
+        this.amount = 0.0;
+        /* capacity / time is the rate we are aiming for. */
+        this.capacity = capacity;
+        this.time = time;
+        this.last_update = getTimer();
+    }
+
+    private function age():void
+    {
+        var now:uint;
+        var delta:Number;
+
+        now = getTimer();
+        delta = (now - last_update) / 1000.0;
+        last_update = now;
+
+        amount -= delta * capacity / time;
+        if (amount < 0.0)
+            amount = 0.0;
+    }
+
+    public override function update(n:Number):Boolean
+    {
+        age();
+        amount += n;
+
+        return amount <= capacity;
+    }
+
+    public override function when():Number
+    {
+        age();
+        return (amount - capacity) / (capacity / time);
+    }
+
+    public override function is_limited():Boolean
+    {
+        age();
+        return amount > capacity;
+    }
+}
\ No newline at end of file
diff --git a/RTMFPProxyPair.as b/RTMFPProxyPair.as
new file mode 100644
index 0000000..346d63c
--- /dev/null
+++ b/RTMFPProxyPair.as
@@ -0,0 +1,101 @@
+package
+{
+    import flash.events.Event;
+    import flash.events.EventDispatcher;
+    import flash.events.ProgressEvent;
+    import flash.net.Socket;
+    import flash.utils.ByteArray;
+    
+    import rtmfp.CirrusSocket;
+    import rtmfp.RTMFPSocket;
+    import rtmfp.events.RTMFPSocketEvent;
+    
+    public class RTMFPProxyPair extends ProxyPair
+    {
+        private var cirrus_socket:CirrusSocket;
+        private var client_socket:RTMFPSocket;
+        private var listen_stream:String;
+        
+        public function RTMFPProxyPair(ui:swfcat, cirrus_socket:CirrusSocket, listen_stream:String)
+        {
+            super(this, ui);
+            
+            log("Starting RTMFP proxy pair on stream " + listen_stream);
+            
+            this.cirrus_socket = cirrus_socket;
+            this.listen_stream = listen_stream;
+            
+            setup_client_socket();
+        }
+        
+        override public function set client(client_addr:Object):void
+        {
+            this.client_addr = client_addr;
+            log("Client: connecting to " + client_addr.peer + " on stream " + client_addr.stream + ".");
+            client_socket.connect(client_addr.peer, client_addr.stream);
+        }
+        
+        override public function close():void
+        {
+            super.close();
+            if (client_socket != null && client_socket.connected) {
+                client_socket.close();
+            }
+            dispatchEvent(new Event(Event.CLOSE));
+        }
+        
+        override public function get connected():Boolean
+        {
+            return (super.connected && client_socket != null && client_socket.connected);
+        }
+        
+        override protected function transfer_bytes(src:Object, dst:Object, num_bytes:uint):void
+        {
+            var bytes:ByteArray = new ByteArray();
+            
+            if (src == null) {
+                src = client_socket;
+                RTMFPSocket(src).readBytes(bytes, 0, num_bytes);
+                log("RTMFPProxyPair: read " + num_bytes + " bytes from client, writing to relay.");
+                Socket(dst).writeBytes(bytes);
+            }
+            
+            if (dst == null) {
+                dst = client_socket;
+                Socket(src).readBytes(bytes, 0, num_bytes);
+                log("RTMFPProxyPair: read " + num_bytes + " bytes from relay, writing to client.");
+                RTMFPSocket(dst).writeBytes(bytes);
+            }
+        }
+        
+        private function setup_client_socket():void
+        {
+            client_socket = new RTMFPSocket(cirrus_socket);
+            client_socket.addEventListener(RTMFPSocketEvent.CONNECT_FAILED, function (e:RTMFPSocketEvent):void {
+                log("Client: connection failed to " + client_addr.peer + " on stream " + client_addr.stream + ".");
+            });
+            client_socket.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, function (e:RTMFPSocketEvent):void {
+                log("Client: connected to " + client_addr.peer + " on stream " + client_addr.stream + ".");
+                if (connected) {
+                    dispatchEvent(new Event(Event.CONNECT));
+                }
+            });
+            client_socket.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function (e:RTMFPSocketEvent):void {
+                log("Peer connected.");
+            });
+            client_socket.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function (e:RTMFPSocketEvent):void {
+                log("Client: disconnected from " + client_addr.peer + ".");
+                close();
+            });
+            client_socket.addEventListener(RTMFPSocketEvent.PLAY_STARTED, function (e:RTMFPSocketEvent):void {
+                log("Play started.");
+            });
+            client_socket.addEventListener(RTMFPSocketEvent.PUBLISH_STARTED, function (e:RTMFPSocketEvent):void {
+                log("Publishing started.");
+            });
+            client_socket.addEventListener(ProgressEvent.SOCKET_DATA, client_to_relay);
+            
+            client_socket.listen(listen_stream);
+        }
+    }
+}
\ No newline at end of file
diff --git a/TCPProxyPair.as b/TCPProxyPair.as
new file mode 100644
index 0000000..b81af3e
--- /dev/null
+++ b/TCPProxyPair.as
@@ -0,0 +1,86 @@
+package
+{
+    import flash.events.Event;
+    import flash.events.EventDispatcher;
+    import flash.events.IOErrorEvent;
+    import flash.events.ProgressEvent;
+    import flash.events.SecurityErrorEvent;
+    import flash.net.Socket;
+    import flash.utils.ByteArray;
+    
+    public class TCPProxyPair extends ProxyPair
+    {   
+        private var client_socket:Socket;
+        
+        public function TCPProxyPair(ui:swfcat)
+        {
+            super(this, ui);
+            
+            log("Starting TCP proxy pair");
+            setup_client_socket();
+        }
+        
+        override public function set client(client_addr:Object):void
+        {
+            this.client_addr = client_addr;
+            log("Client: connecting to " + client_addr.host + ":" + client_addr.port + ".");
+            client_socket.connect(client_addr.host, client_addr.port);
+        }
+        
+        override public function close():void
+        {
+            super.close();
+            if (client_socket != null && client_socket.connected) {
+                client_socket.close();
+            }
+            dispatchEvent(new Event(Event.CLOSE));
+        }
+        
+        override public function get connected():Boolean
+        {
+            return (super.connected && client_socket != null && client_socket.connected);
+        }
+        
+        override protected function transfer_bytes(src:Object, dst:Object, num_bytes:uint):void
+        {
+            var bytes:ByteArray = new ByteArray();
+            
+            if (src == null) {
+                src = client_socket;
+            }
+            
+            if (dst == null) {
+                dst = client_socket;
+            }
+            
+            Socket(src).readBytes(bytes, 0, num_bytes);
+            log("TCPProxyPair: transferring " + num_bytes + " bytes.");
+            Socket(dst).writeBytes(bytes);
+        }
+        
+        private function setup_client_socket():void
+        {
+            client_socket = new Socket();
+
+            client_socket.addEventListener(Event.CONNECT, function (e:Event):void {
+                log("Client: connected to " + client_addr.host + ":" + client_addr.port + ".");
+                if (connected) {
+                    dispatchEvent(new Event(Event.CONNECT));
+                }
+            });
+            client_socket.addEventListener(Event.CLOSE, function (e:Event):void {
+                log("Client: closed.");
+                close();
+            });
+            client_socket.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
+                log("Client: I/O error: " + e.text + ".");
+                close();
+            });
+            client_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
+                log("Client: security error: " + e.text + ".");
+                close();
+            });
+            client_socket.addEventListener(ProgressEvent.SOCKET_DATA, client_to_relay);
+        }
+    }
+}
\ No newline at end of file
diff --git a/connector.py b/connector.py
index a683de0..d20a5b6 100755
--- a/connector.py
+++ b/connector.py
@@ -13,7 +13,7 @@ import urllib
 import xml.sax.saxutils
 
 DEFAULT_REMOTE_ADDRESS = "127.0.0.1"
-DEFAULT_REMOTE_PORT = 3333
+DEFAULT_REMOTE_PORT = 9002
 DEFAULT_LOCAL_ADDRESS = "127.0.0.1"
 DEFAULT_LOCAL_PORT = 9001
 DEFAULT_FACILITATOR_PORT = 9002
@@ -281,7 +281,7 @@ def register():
     spec = format_addr((None, options.remote_addr[1]))
     log(u"Registering \"%s\" with %s." % (spec, format_addr(options.facilitator_addr)))
     http = httplib.HTTPConnection(*options.facilitator_addr)
-    http.request("POST", "/", urllib.urlencode({"client": spec}))
+    http.request("POST", "/", urllib.urlencode({"client": spec}), {"Content-Type":"application/x-www-form-urlencoded"})
     http.close()
     return True
 
diff --git a/facilitator.py b/facilitator.py
index 0ea86a1..d6c598d 100755
--- a/facilitator.py
+++ b/facilitator.py
@@ -11,6 +11,7 @@ import sys
 import threading
 import time
 import urllib
+import xml.sax.saxutils
 
 DEFAULT_ADDRESS = "0.0.0.0"
 DEFAULT_PORT = 9002
@@ -216,10 +217,6 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
                                 environ = {'REQUEST_METHOD' : 'POST',
                                            'CONTENT_TYPE' : self.headers['Content-Type']})
 
-        if self.path == "/crossdomain.xml":
-            self.send_crossdomain()
-            return
-
         client_specs = data["client"]
         if client_specs is None or client_specs.value is None:
             log(u"client %s missing \"client\" param" % format_addr(self.client_address))
@@ -257,7 +254,7 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
         crossdomain = """\
 <cross-domain-policy>
     <allow-access-from domain="*" to-ports="%s"/>
-</cross-domain-policy>\r\n""" % (address[1])
+</cross-domain-policy>\r\n""" % xml.sax.saxutils.escape(str(address[1]))
         self.send_response(200)
         self.send_header('Content-Type', 'application/xml')
         self.send_header('Content-Length', str(len(crossdomain)))
diff --git a/rtmfp/ProxyPair.as b/rtmfp/ProxyPair.as
deleted file mode 100644
index 159abde..0000000
--- a/rtmfp/ProxyPair.as
+++ /dev/null
@@ -1,279 +0,0 @@
-package rtmfp
-{
-    import flash.events.Event;
-    import flash.events.EventDispatcher;
-    import flash.events.IOErrorEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.net.Socket;
-    import flash.utils.ByteArray;
-    import flash.utils.clearTimeout;
-    import flash.utils.setTimeout;
-    
-    import rtmfp.CirrusSocket;
-    import rtmfp.RTMFPSocket;
-    import rtmfp.events.RTMFPSocketEvent;
-    
-    public class ProxyPair extends EventDispatcher
-    {   
-        private var ui:rtmfpcat;
-
-        private var s_p:RTMFPSocket;
-        private var s_r:Socket;
-        
-        private var relay_host:String;
-        private var relay_port:uint;
-        
-        private var p2r_schedule:Array;
-        private var r2p_schedule:Array;
-        
-        // Bytes per second. Set to undefined to disable limit.
-        public const RATE_LIMIT:Number = 10000;
-        // Seconds.
-        private const RATE_LIMIT_HISrelayY:Number = 5.0;
-        
-        private var rate_limit:RateLimit;
-        
-        // Callback id.
-        private var flush_id:uint;
-
-        public function ProxyPair(ui:rtmfpcat, s_c:CirrusSocket, relay_host:String, relay_port:uint)
-        {
-            this.ui = ui;
-            this.relay_host = relay_host;
-            this.relay_port = relay_port;
-            
-            this.p2r_schedule = new Array();
-            this.r2p_schedule = new Array();
-            
-            if (RATE_LIMIT)
-                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISrelayY, RATE_LIMIT_HISrelayY);
-            else
-                rate_limit = new RateUnlimit();
-            
-            setup_rtmfp_socket(s_c);
-            setup_relay_socket();
-        }
-        
-        public function close():void
-        {
-            if (s_p.connected) {
-                s_p.close();
-            }
-            if (s_r.connected) {
-                s_r.close();
-            }
-            dispatchEvent(new Event(Event.CLOSE));
-        }
-
-        public function connect(peer:String, stream:String):void
-        {        
-            s_p.connect(peer, stream);
-        }
-        
-        public function get connected():Boolean
-        {
-            return (s_p.connected && s_r.connected);
-        }
-        
-        public function listen(stream:String):void
-        {            
-            s_p.listen(stream);
-        }
-        
-        private function setup_rtmfp_socket(s_c:CirrusSocket):void
-        {
-            s_p = new RTMFPSocket(s_c);
-            s_p.addEventListener(RTMFPSocketEvent.CONNECT_FAILED, function (e:RTMFPSocketEvent):void {
-                ui.puts("Peering failed.");
-            });
-            s_p.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, function (e:RTMFPSocketEvent):void {
-                ui.puts("Peering success.");
-                s_r.connect(relay_host, relay_port);
-            });
-            s_p.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function (e:RTMFPSocketEvent):void {
-                ui.puts("Peer connected.");
-            });
-            s_p.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function (e:RTMFPSocketEvent):void {
-                ui.puts("Peer disconnected.");
-                close();
-            });
-            s_p.addEventListener(RTMFPSocketEvent.PLAY_STARTED, function (e:RTMFPSocketEvent):void {
-                ui.puts("Play started.");
-            });
-            s_p.addEventListener(RTMFPSocketEvent.PUBLISH_STARTED, function (e:RTMFPSocketEvent):void {
-                ui.puts("Publishing started.");
-            });
-            s_p.addEventListener(ProgressEvent.SOCKET_DATA, proxy_to_relay);
-        }
-        
-        private function setup_relay_socket():void
-        {
-            s_r = new Socket();
-            s_r.addEventListener(Event.CONNECT, function (e:Event):void {
-                ui.puts("Relay: connected to " + relay_host + ":" + relay_port + ".");
-                dispatchEvent(new Event(Event.CONNECT));
-            });
-            s_r.addEventListener(Event.CLOSE, function (e:Event):void {
-                ui.puts("Relay: closed connection.");
-                close();
-            });
-            s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-                ui.puts("Relay: I/O error: " + e.text + ".");
-                close();
-            });
-            s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-                ui.puts("Relay: security error: " + e.text + ".");
-                close();
-            });
-            s_r.addEventListener(ProgressEvent.SOCKET_DATA, relay_to_proxy);
-        }
-        
-        private function relay_to_proxy(e:ProgressEvent):void
-        {
-            r2p_schedule.push(e.bytesLoaded);
-            flush();
-        }
-
-        private function proxy_to_relay(e:ProgressEvent):void
-        {
-            p2r_schedule.push(e.bytesLoaded);
-            flush();
-        }
-        
-        /* Send as much data as the rate limit currently allows. */
-        private function flush():void
-        {
-            if (flush_id)
-                clearTimeout(flush_id);
-            flush_id = undefined;
-
-            if (!(s_p.connected && s_r.connected))
-                /* Can't do anything until both sockets are connected. */
-                return;
-
-            while (!rate_limit.is_limited() && (p2r_schedule.length > 0 || r2p_schedule.length > 0)) {
-                var numBytes:uint;
-                var bytes:ByteArray;
-                
-                if (p2r_schedule.length > 0) {
-                    numBytes = p2r_schedule.shift();
-                    bytes = new ByteArray();
-                    s_p.readBytes(bytes, 0, numBytes);
-                    ui.puts("ProxyPair: RTMFP: read " + bytes.length + " bytes.");
-                    s_r.writeBytes(bytes);
-                    rate_limit.update(numBytes);
-                }
-                if (r2p_schedule.length > 0) {
-                    numBytes = r2p_schedule.shift();
-                    bytes = new ByteArray();
-                    s_r.readBytes(bytes, 0, numBytes);
-                    ui.puts("ProxyPair: Relay: read " + bytes.length + " bytes.");
-                    s_p.writeBytes(bytes);
-                    rate_limit.update(numBytes);
-                }
-            }
-
-            /* Call again when safe, if necessary. */
-            if (p2r_schedule.length > 0 || r2p_schedule.length > 0)
-                flush_id = setTimeout(flush, rate_limit.when() * 1000);
-        }
-    }
-}
-
-import flash.utils.getTimer;
-
-class RateLimit
-{
-    public function RateLimit()
-    {
-    }
-
-    public function update(n:Number):Boolean
-    {
-        return true;
-    }
-
-    public function when():Number
-    {
-        return 0.0;
-    }
-
-    public function is_limited():Boolean
-    {
-        return false;
-    }
-}
-
-class RateUnlimit extends RateLimit
-{
-    public function RateUnlimit()
-    {
-    }
-
-    public override function update(n:Number):Boolean
-    {
-        return true;
-    }
-
-    public override function when():Number
-    {
-        return 0.0;
-    }
-
-    public override function is_limited():Boolean
-    {
-        return false;
-    }
-}
-
-class BucketRateLimit extends RateLimit
-{
-    private var amount:Number;
-    private var capacity:Number;
-    private var time:Number;
-    private var last_update:uint;
-
-    public function BucketRateLimit(capacity:Number, time:Number)
-    {
-        this.amount = 0.0;
-        /* capacity / time is the rate we are aiming for. */
-        this.capacity = capacity;
-        this.time = time;
-        this.last_update = getTimer();
-    }
-
-    private function age():void
-    {
-        var now:uint;
-        var delta:Number;
-
-        now = getTimer();
-        delta = (now - last_update) / 1000.0;
-        last_update = now;
-
-        amount -= delta * capacity / time;
-        if (amount < 0.0)
-            amount = 0.0;
-    }
-
-    public override function update(n:Number):Boolean
-    {
-        age();
-        amount += n;
-
-        return amount <= capacity;
-    }
-
-    public override function when():Number
-    {
-        age();
-        return (amount - capacity) / (capacity / time);
-    }
-
-    public override function is_limited():Boolean
-    {
-        age();
-        return amount > capacity;
-    }
-}
\ No newline at end of file
diff --git a/rtmfp/RTMFPSocket.as b/rtmfp/RTMFPSocket.as
index bcbee67..4efe702 100644
--- a/rtmfp/RTMFPSocket.as
+++ b/rtmfp/RTMFPSocket.as
@@ -48,7 +48,7 @@ package rtmfp
         
 	/* Tears down this RTMFPSocket, closing both its streams.
 	   To be used when destroying this object. */
-	public function close():void
+	 public function close():void
         {
             if (send_stream != null) {
                 s_c.connection.removeEventListener(NetStatusEvent.NET_STATUS, on_stream_disconnection_event);
diff --git a/rtmfpcat.as b/rtmfpcat.as
deleted file mode 100644
index 1876ef9..0000000
--- a/rtmfpcat.as
+++ /dev/null
@@ -1,353 +0,0 @@
-package
-{
-    import flash.display.Sprite;
-    import flash.display.StageAlign;
-    import flash.display.StageScaleMode;
-    import flash.text.TextField;
-    import flash.text.TextFormat;
-    import flash.events.Event;
-    import flash.utils.setTimeout;
-
-    import rtmfp.CirrusSocket;
-    import rtmfp.FacilitatorSocket;
-    import rtmfp.ProxyPair;
-    import rtmfp.events.CirrusSocketEvent;
-    import rtmfp.events.FacilitatorSocketEvent;
-
-    public class rtmfpcat extends Sprite
-    {
-        /* Adobe's Cirrus server and Nate's key */
-        private const DEFAULT_CIRRUS_ADDR:String = "rtmfp://p2p.rtmfp.net";
-        private const DEFAULT_CIRRUS_KEY:String = RTMFP::CIRRUS_KEY;
-        
-        /* Nate's facilitator -- serves a crossdomain policy */
-        private const DEFAULT_FACILITATOR_ADDR:Object = {
-            host: "128.12.179.80",
-            port: 9002
-        };
-        
-        private const DEFAULT_TOR_CLIENT_ADDR:Object = {
-            host: "127.0.0.1",
-            port: 3333
-        };
-        
-        /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
-           crossdomain policy. */
-        private const DEFAULT_TOR_RELAY_ADDR:Object = {
-            host: "173.255.221.44",
-            port: 9001
-        };
-        
-        /* Poll facilitator every 10 sec */
-        private const DEFAULT_FAC_POLL_INTERVAL:uint = 10000;
-
-        // Socket to Cirrus server
-        private var s_c:CirrusSocket;
-        // Socket to facilitator.
-        private var s_f:FacilitatorSocket;
-        // Handle local-remote traffic
-        private var p_p:ProxyPair;
-        
-        private var proxy_pairs:Array;
-
-        private var debug_mode:Boolean;
-        private var proxy_mode:Boolean;
-
-        /* TextField for debug output. */
-        private var output_text:TextField;
-        
-        /* Badge for display */
-        private var badge:InternetFreedomBadge;
-
-        private var fac_addr:Object;
-        private var relay_addr:Object;
-
-        public function rtmfpcat()
-        {
-            proxy_mode = false;
-            debug_mode = false;
-            
-            // Absolute positioning.
-            stage.scaleMode = StageScaleMode.NO_SCALE;
-            stage.align = StageAlign.TOP_LEFT;
-            
-            // Wait until the query string parameters are loaded.
-            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
-        }
-        
-        public function puts(s:String):void
-        {
-            if (output_text != null) {
-                output_text.appendText(s + "\n");
-                output_text.scrollV = output_text.maxScrollV;
-            }
-        }
-
-        private function loaderinfo_complete(e:Event):void
-        {
-            var fac_spec:String;
-            var relay_spec:String;
-
-            debug_mode = (this.loaderInfo.parameters["debug"] != null)
-            proxy_mode = (this.loaderInfo.parameters["proxy"] != null);
-            if (proxy_mode && !debug_mode) {
-                badge = new InternetFreedomBadge(this);
-                badge.display();
-            } else {
-                output_text = new TextField();
-                output_text.width = stage.stageWidth;
-                output_text.height = stage.stageHeight;
-                output_text.background = true;
-                output_text.backgroundColor = 0x001f0f;
-                output_text.textColor = 0x44cc44;
-                addChild(output_text);
-            }
-            
-            puts("Starting: parameters loaded.");
-            
-            /* TODO: use this to have multiple proxies going at once */
-            proxy_pairs = new Array();
-
-            fac_spec = this.loaderInfo.parameters["facilitator"];
-            if (fac_spec) {
-                puts("Facilitator spec: \"" + fac_spec + "\"");
-                fac_addr = parse_addr_spec(fac_spec);
-                if (!fac_addr) {
-                    puts("Error: Facilitator spec must be in the form \"host:port\".");
-                    return;
-                }
-            } else {
-                fac_addr = DEFAULT_FACILITATOR_ADDR;
-            }
-            
-            relay_spec = this.loaderInfo.parameters["relay"];
-            if (relay_spec) {
-                puts("Relay spec: \"" + relay_spec + "\"");
-                relay_addr = parse_addr_spec(relay_spec);
-                if (!relay_addr) {
-                    puts("Error: Relay spec must be in the form \"host:port\".");
-                    return;
-                }
-            } else {
-                if (proxy_mode) {
-                    relay_addr = DEFAULT_TOR_RELAY_ADDR;
-                } else {
-                    relay_addr = DEFAULT_TOR_CLIENT_ADDR;
-                }
-            }
-
-            main();
-        }
-
-        /* The main logic begins here, after start-up issues are taken care of. */
-        private function main():void
-        {
-            establish_cirrus_connection();
-        }
-
-        private function establish_cirrus_connection():void
-        {
-            s_c = new CirrusSocket();
-            s_c.addEventListener(CirrusSocketEvent.CONNECT_SUCCESS, function (e:CirrusSocketEvent):void {
-                puts("Cirrus: connected with id " + s_c.id + ".");
-                establish_facilitator_connection();
-            });
-            s_c.addEventListener(CirrusSocketEvent.CONNECT_FAILED, function (e:CirrusSocketEvent):void {
-                puts("Error: failed to connect to Cirrus.");
-            });
-            s_c.addEventListener(CirrusSocketEvent.CONNECT_CLOSED, function (e:CirrusSocketEvent):void {
-                puts("Cirrus: closed connection.");
-            });
-            
-            s_c.addEventListener(CirrusSocketEvent.HELLO_RECEIVED, function (e:CirrusSocketEvent):void {
-                puts("Cirrus: received hello from peer " + e.peer);
-                
-                /* don't bother if we already have a proxy going */
-                if (p_p != null && p_p.connected) {
-                    return;
-                }
-                
-                /* if we're in proxy mode, we should have already set
-                   up a proxy pair */
-                if (!proxy_mode) {
-                    start_proxy_pair();
-                    s_c.send_hello(e.peer);
-                } else if (!debug_mode && badge != null) {
-                    badge.total_proxy_pairs++;
-                    badge.num_proxy_pairs++;
-                }
-                p_p.connect(e.peer, e.stream);
-            });
-            
-            s_c.connect(DEFAULT_CIRRUS_ADDR, DEFAULT_CIRRUS_KEY);
-        }
-
-        private function establish_facilitator_connection():void
-        {
-            s_f = new FacilitatorSocket(fac_addr.host, fac_addr.port);
-            s_f.addEventListener(FacilitatorSocketEvent.CONNECT_FAILED, function (e:Event):void {
-                puts("Facilitator: connect failed.");
-                setTimeout(establish_facilitator_connection, DEFAULT_FAC_POLL_INTERVAL);
-            });
-            
-            if (proxy_mode) {
-                s_f.addEventListener(FacilitatorSocketEvent.REGISTRATION_RECEIVED, function (e:FacilitatorSocketEvent):void {
-                    puts("Facilitator: got registration " + e.client);
-                    start_proxy_pair();
-                    s_c.send_hello(e.client);
-                });
-                s_f.addEventListener(FacilitatorSocketEvent.REGISTRATIONS_EMPTY, function (e:Event):void {
-                    puts("Facilitator: no registrations available.");
-                    setTimeout(establish_facilitator_connection, DEFAULT_FAC_POLL_INTERVAL);
-                });
-                puts("Facilitator: getting registration.");
-                s_f.get_registration();
-            } else {
-                s_f.addEventListener(FacilitatorSocketEvent.REGISTRATION_FAILED, function (e:Event):void {
-                    puts("Facilitator: registration failed.");
-                    setTimeout(establish_facilitator_connection, DEFAULT_FAC_POLL_INTERVAL);
-                });
-                puts("Facilitator: posting registration.");
-                s_f.post_registration(s_c.id);
-            }
-        }
-        
-        private function start_proxy_pair():void
-        {
-            puts("Starting proxy pair on stream " + s_c.local_stream_name);
-            p_p = new ProxyPair(this, s_c, relay_addr.host, relay_addr.port);
-            p_p.addEventListener(Event.CONNECT, function (e:Event):void {
-                puts("ProxyPair: connected!");
-            });
-            p_p.addEventListener(Event.CLOSE, function (e:Event):void {
-                puts("ProxyPair: connection closed.");
-                p_p = null;
-                if (proxy_mode && !debug_mode && badge != null) {
-                    badge.num_proxy_pairs--;
-                }
-                establish_facilitator_connection();
-            });
-            p_p.listen(s_c.local_stream_name);
-        }
-
-        /* Parse an address in the form "host:port". Returns an Object with
-           keys "host" (String) and "port" (int). Returns null on error. */
-        private function parse_addr_spec(spec:String):Object
-        {
-            var parts:Array;
-            var addr:Object;
-
-            parts = spec.split(":", 2);
-            if (parts.length != 2 || !parseInt(parts[1]))
-                return null;
-            addr = {}
-            addr.host = parts[0];
-            addr.port = parseInt(parts[1]);
-
-            return addr;
-        }
-    }
-}
-
-import flash.text.TextField;
-import flash.text.TextFormat;
-
-class InternetFreedomBadge {
-    
-    private var ui:rtmfpcat;
-    
-    private var _num_proxy_pairs:uint;
-    private var _total_proxy_pairs:uint;
-    
-    [Embed(source="badge.png")]
-    private var BadgeImage:Class;
-    private var tot_client_count_tf:TextField;
-    private var tot_client_count_fmt:TextFormat;
-    private var cur_client_count_tf:TextField;
-    private var cur_client_count_fmt:TextFormat;
-    
-    public function InternetFreedomBadge(ui:rtmfpcat)
-    {
-        this.ui = ui;
-        _num_proxy_pairs = 0;
-        _total_proxy_pairs = 0;
-        
-        /* Setup client counter for badge. */
-        tot_client_count_fmt = new TextFormat();
-        tot_client_count_fmt.color = 0xFFFFFF;
-        tot_client_count_fmt.align = "center";
-        tot_client_count_fmt.font = "courier-new";
-        tot_client_count_fmt.bold = true;
-        tot_client_count_fmt.size = 10;
-        tot_client_count_tf = new TextField();
-        tot_client_count_tf.width = 20;
-        tot_client_count_tf.height = 17;
-        tot_client_count_tf.background = false;
-        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
-        tot_client_count_tf.x=47;
-        tot_client_count_tf.y=0;
-
-        cur_client_count_fmt = new TextFormat();
-        cur_client_count_fmt.color = 0xFFFFFF;
-        cur_client_count_fmt.align = "center";
-        cur_client_count_fmt.font = "courier-new";
-        cur_client_count_fmt.bold = true;
-        cur_client_count_fmt.size = 10;
-        cur_client_count_tf = new TextField();
-        cur_client_count_tf.width = 20;
-        cur_client_count_tf.height = 17;
-        cur_client_count_tf.background = false;
-        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
-        cur_client_count_tf.x=47;
-        cur_client_count_tf.y=6;
-
-        /* Update the client counter on badge. */
-        update_client_count();
-    }
-    
-    public function display():void
-    {
-        ui.addChild(new BadgeImage());
-        /* Tried unsuccessfully to add counter to badge. */
-        /* For now, need two addChilds :( */
-        ui.addChild(tot_client_count_tf);
-        ui.addChild(cur_client_count_tf);
-    }
-    
-    public function get num_proxy_pairs():uint
-    {
-        return _num_proxy_pairs;
-    }
-    
-    public function set num_proxy_pairs(amount:uint):void
-    {
-        _num_proxy_pairs = amount;
-        update_client_count();
-    }
-    
-    public function get total_proxy_pairs():uint
-    {
-        return _total_proxy_pairs;
-    }
-    
-    public function set total_proxy_pairs(amount:uint):void
-    {
-        _total_proxy_pairs = amount;
-        /* note: doesn't update, so be sure to update this
-           before you update num_proxy_pairs! */
-    }
-    
-    private function update_client_count():void
-    {
-        /* Update total client count. */
-        if (String(total_proxy_pairs).length == 1)
-            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
-        else
-            tot_client_count_tf.text = String(total_proxy_pairs);
-
-        /* Update current client count. */
-        cur_client_count_tf.text = "";
-        for(var i:Number = 0; i < num_proxy_pairs; i++)
-            cur_client_count_tf.appendText(".");
-    }
-}
diff --git a/swfcat.as b/swfcat.as
index 05da498..7729d2c 100644
--- a/swfcat.as
+++ b/swfcat.as
@@ -5,251 +5,268 @@ package
     import flash.display.StageScaleMode;
     import flash.text.TextField;
     import flash.text.TextFormat;
-    import flash.net.Socket;
     import flash.events.Event;
-    import flash.events.IOErrorEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.utils.ByteArray;
     import flash.utils.setTimeout;
 
+    import rtmfp.CirrusSocket;
+    import rtmfp.FacilitatorSocket;
+    import rtmfp.events.CirrusSocketEvent;
+    import rtmfp.events.FacilitatorSocketEvent;
+
     public class swfcat extends Sprite
     {
+        /* Adobe's Cirrus server and Nate's key */
+        private const DEFAULT_CIRRUS_ADDR:String = "rtmfp://p2p.rtmfp.net";
+        private const DEFAULT_CIRRUS_KEY:String = RTMFP::CIRRUS_KEY;
+        
+        /* Nate's facilitator -- serves a crossdomain policy */
+        private const DEFAULT_FACILITATOR_ADDR:Object = {
+            host: "128.12.179.80",
+            port: 9002
+        };
+        
+        private const DEFAULT_TOR_CLIENT_ADDR:Object = {
+            host: "127.0.0.1",
+            port: 9002
+        };
+        
         /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
            crossdomain policy. */
-        private const DEFAULT_RELAY_ADDR:Object = {
+        private const DEFAULT_TOR_RELAY_ADDR:Object = {
             host: "173.255.221.44",
             port: 9001
         };
-        private const DEFAULT_FACILITATOR_ADDR:Object = {
-            host: "173.255.221.44",
-            port: 9002
-        };
-
-        private const MAX_NUM_PROXY_PAIRS:uint = 1;
-
-        // Milliseconds.
-        private const FACILITATOR_POLL_INTERVAL:int = 10000;
-
-        // Bytes per second. Set to undefined to disable limit.
-        public const RATE_LIMIT:Number = undefined;
-        // Seconds.
-        private const RATE_LIMIT_HISTORY:Number = 5.0;
+        
+        /* Poll facilitator every 10 sec */
+        private const DEFAULT_FAC_POLL_INTERVAL:uint = 10000;
 
+        // Socket to Cirrus server
+        private var s_c:CirrusSocket;
         // Socket to facilitator.
-        private var s_f:Socket;
+        private var s_f:FacilitatorSocket;
+        // Handle local-remote traffic
+        private var p_p:ProxyPair;
+        
+        private var client_id:String;
+        private var proxy_pair_factory:Function;
+        
+        private var proxy_pairs:Array;
+
+        private var debug_mode:Boolean;
+        private var proxy_mode:Boolean;
 
         /* TextField for debug output. */
         private var output_text:TextField;
+        
+        /* Badge for display */
+        private var badge:InternetFreedomBadge;
 
         private var fac_addr:Object;
         private var relay_addr:Object;
 
-        /* Number of proxy pairs currently connected (up to
-           MAX_NUM_PROXY_PAIRS). */
-        private var num_proxy_pairs:int = 0;
-        /* Number of proxy pairs ever connected. */
-        private var total_proxy_pairs:int = 0;
-
-        public var rate_limit:RateLimit;
-
-        /* Badge with a client counter */
-        [Embed(source="badge.png")]
-        private var BadgeImage:Class;
-        private var tot_client_count_tf:TextField;
-        private var tot_client_count_fmt:TextFormat;
-        private var cur_client_count_tf:TextField;
-        private var cur_client_count_fmt:TextFormat;
-
-        public function puts(s:String):void
-        {
-            output_text.appendText(s + "\n");
-            output_text.scrollV = output_text.maxScrollV;
-        }
-
-        public function update_client_count():void
-        {
-            /* Update total client count. */
-            if (String(total_proxy_pairs).length == 1)
-                tot_client_count_tf.text = "0" + String(total_proxy_pairs);
-            else
-                tot_client_count_tf.text = String(total_proxy_pairs);
-
-            /* Update current client count. */
-            cur_client_count_tf.text = "";
-            for(var i:Number=0; i<num_proxy_pairs; i++)
-                cur_client_count_tf.appendText(".");;
-        }
-
         public function swfcat()
         {
+            proxy_mode = false;
+            debug_mode = false;
+            
             // Absolute positioning.
             stage.scaleMode = StageScaleMode.NO_SCALE;
             stage.align = StageAlign.TOP_LEFT;
-
-            output_text = new TextField();
-            output_text.width = stage.stageWidth;
-            output_text.height = stage.stageHeight;
-            output_text.background = true;
-            output_text.backgroundColor = 0x001f0f;
-            output_text.textColor = 0x44cc44;
-
-            /* Setup client counter for badge. */
-            tot_client_count_fmt = new TextFormat();
-            tot_client_count_fmt.color = 0xFFFFFF;
-            tot_client_count_fmt.align = "center";
-            tot_client_count_fmt.font = "courier-new";
-            tot_client_count_fmt.bold = true;
-            tot_client_count_fmt.size = 10;
-            tot_client_count_tf = new TextField();
-            tot_client_count_tf.width = 20;
-            tot_client_count_tf.height = 17;
-            tot_client_count_tf.background = false;
-            tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
-            tot_client_count_tf.x=47;
-            tot_client_count_tf.y=0;
-
-            cur_client_count_fmt = new TextFormat();
-            cur_client_count_fmt.color = 0xFFFFFF;
-            cur_client_count_fmt.align = "center";
-            cur_client_count_fmt.font = "courier-new";
-            cur_client_count_fmt.bold = true;
-            cur_client_count_fmt.size = 10;
-            cur_client_count_tf = new TextField();
-            cur_client_count_tf.width = 20;
-            cur_client_count_tf.height = 17;
-            cur_client_count_tf.background = false;
-            cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
-            cur_client_count_tf.x=47;
-            cur_client_count_tf.y=6;
-
-
-            /* Update the client counter on badge. */
-            update_client_count();
-
-            if (RATE_LIMIT)
-                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
-            else
-                rate_limit = new RateUnlimit();
-
-            puts("Starting.");
+            
             // Wait until the query string parameters are loaded.
             this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
         }
+        
+        public function puts(s:String):void
+        {
+            if (output_text != null) {
+                output_text.appendText(s + "\n");
+                output_text.scrollV = output_text.maxScrollV;
+            }
+        }
 
         private function loaderinfo_complete(e:Event):void
         {
             var fac_spec:String;
-
-            puts("Parameters loaded.");
-
-            if (this.loaderInfo.parameters["debug"])
+            var relay_spec:String;
+
+            debug_mode = (this.loaderInfo.parameters["debug"] != null)
+            proxy_mode = (this.loaderInfo.parameters["proxy"] != null);
+            if (proxy_mode && !debug_mode) {
+                badge = new InternetFreedomBadge(this);
+                badge.display();
+            } else {
+                output_text = new TextField();
+                output_text.width = stage.stageWidth;
+                output_text.height = stage.stageHeight;
+                output_text.background = true;
+                output_text.backgroundColor = 0x001f0f;
+                output_text.textColor = 0x44cc44;
                 addChild(output_text);
-            else {
-                addChild(new BadgeImage());
-                /* Tried unsuccessfully to add counter to badge. */
-                /* For now, need two addChilds :( */
-                addChild(tot_client_count_tf);
-                addChild(cur_client_count_tf);
             }
-
-            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
-            if (!fac_addr) {
-                puts("Error: Facilitator spec must be in the form \"host:port\".");
-                return;
+            
+            puts("Starting: parameters loaded.");
+            
+            /* TODO: use this to have multiple proxies going at once */
+            proxy_pairs = new Array();
+
+            fac_spec = this.loaderInfo.parameters["facilitator"];
+            if (fac_spec) {
+                puts("Facilitator spec: \"" + fac_spec + "\"");
+                fac_addr = parse_addr_spec(fac_spec);
+                if (!fac_addr) {
+                    puts("Error: Facilitator spec must be in the form \"host:port\".");
+                    return;
+                }
+            } else {
+                fac_addr = DEFAULT_FACILITATOR_ADDR;
             }
-            relay_addr = get_param_addr("relay", DEFAULT_RELAY_ADDR);
-            if (!relay_addr) {
-                puts("Error: Relay spec must be in the form \"host:port\".");
-                return;
+            
+            relay_spec = this.loaderInfo.parameters["relay"];
+            if (relay_spec) {
+                puts("Relay spec: \"" + relay_spec + "\"");
+                relay_addr = parse_addr_spec(relay_spec);
+                if (!relay_addr) {
+                    puts("Error: Relay spec must be in the form \"host:port\".");
+                    return;
+                }
+            } else {
+                if (proxy_mode) {
+                    relay_addr = DEFAULT_TOR_RELAY_ADDR;
+                } else {
+                    relay_addr = DEFAULT_TOR_CLIENT_ADDR;
+                }
             }
 
             main();
         }
 
-        /* Get an address structure from the given movie parameter, or the given
-           default. Returns null on error. */
-        private function get_param_addr(param:String, default_addr:Object):Object
-        {
-            var spec:String, addr:Object;
-
-            spec = this.loaderInfo.parameters[param];
-            if (spec)
-                return parse_addr_spec(spec);
-            else
-                return default_addr;
-        }
-
         /* The main logic begins here, after start-up issues are taken care of. */
         private function main():void
         {
-            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
-                setTimeout(main, FACILITATOR_POLL_INTERVAL);
-                return;
+            if (proxy_mode) {
+                establish_facilitator_connection();
+            } else {
+                establish_cirrus_connection();
             }
+        }
 
-            s_f = new Socket();
-
-            s_f.addEventListener(Event.CONNECT, fac_connected);
-            s_f.addEventListener(Event.CLOSE, function (e:Event):void {
-                puts("Facilitator: closed connection.");
-                setTimeout(main, FACILITATOR_POLL_INTERVAL);
+        private function establish_cirrus_connection():void
+        {
+            s_c = new CirrusSocket();
+            s_c.addEventListener(CirrusSocketEvent.CONNECT_SUCCESS, function (e:CirrusSocketEvent):void {
+                puts("Cirrus: connected with id " + s_c.id + ".");
+                if (proxy_mode) {
+                    start_proxy_pair();
+                    s_c.send_hello(client_id);
+                } else {
+                    establish_facilitator_connection();
+                }
             });
-            s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-                puts("Facilitator: I/O error: " + e.text + ".");
+            s_c.addEventListener(CirrusSocketEvent.CONNECT_FAILED, function (e:CirrusSocketEvent):void {
+                puts("Error: failed to connect to Cirrus.");
             });
-            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-                puts("Facilitator: security error: " + e.text + ".");
+            s_c.addEventListener(CirrusSocketEvent.CONNECT_CLOSED, function (e:CirrusSocketEvent):void {
+                puts("Cirrus: closed connection.");
             });
-
-            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
-            s_f.connect(fac_addr.host, fac_addr.port);
+            s_c.addEventListener(CirrusSocketEvent.HELLO_RECEIVED, function (e:CirrusSocketEvent):void {
+                puts("Cirrus: received hello from peer " + e.peer);
+                
+                /* don't bother if we already have a proxy going */
+                if (p_p != null && p_p.connected) {
+                    return;
+                }
+                
+                /* if we're in proxy mode, we should have already set
+                   up a proxy pair */
+                if (!proxy_mode) {
+                    proxy_pair_factory = rtmfp_proxy_pair_factory;
+                    start_proxy_pair();
+                    s_c.send_hello(e.peer);
+                } else if (!debug_mode && badge != null) {
+                    badge.total_proxy_pairs++;
+                    badge.num_proxy_pairs++;
+                }
+                
+                p_p.client = {peer: e.peer, stream: e.stream};
+            });
+            
+            s_c.connect(DEFAULT_CIRRUS_ADDR, DEFAULT_CIRRUS_KEY);
         }
 
-        private function fac_connected(e:Event):void
+        private function establish_facilitator_connection():void
         {
-            puts("Facilitator: connected.");
-
-            s_f.addEventListener(ProgressEvent.SOCKET_DATA, fac_data);
-
-            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
+            s_f = new FacilitatorSocket(fac_addr.host, fac_addr.port);
+            s_f.addEventListener(FacilitatorSocketEvent.CONNECT_FAILED, function (e:Event):void {
+                puts("Facilitator: connect failed.");
+                setTimeout(establish_facilitator_connection, DEFAULT_FAC_POLL_INTERVAL);
+            });
+            
+            if (proxy_mode) {
+                s_f.addEventListener(FacilitatorSocketEvent.REGISTRATION_RECEIVED, function (e:FacilitatorSocketEvent):void {
+                    var client_addr:Object = parse_addr_spec(e.client);
+                    if (client_addr == null) {
+                        puts("Facilitator: got registration " + e.client);
+                        proxy_pair_factory = rtmfp_proxy_pair_factory;
+                        if (s_c == null || !s_c.connected) {
+                            client_id = e.client;
+                            establish_cirrus_connection();
+                        } else {
+                            start_proxy_pair();
+                            s_c.send_hello(e.client);
+                        }
+                    } else {
+                        proxy_pair_factory = tcp_proxy_pair_factory;
+                        start_proxy_pair();
+                        p_p.client = client_addr;
+                    }
+                });
+                s_f.addEventListener(FacilitatorSocketEvent.REGISTRATIONS_EMPTY, function (e:Event):void {
+                    puts("Facilitator: no registrations available.");
+                    setTimeout(establish_facilitator_connection, DEFAULT_FAC_POLL_INTERVAL);
+                });
+                puts("Facilitator: getting registration.");
+                s_f.get_registration();
+            } else {
+                s_f.addEventListener(FacilitatorSocketEvent.REGISTRATION_FAILED, function (e:Event):void {
+                    puts("Facilitator: registration failed.");
+                    setTimeout(establish_facilitator_connection, DEFAULT_FAC_POLL_INTERVAL);
+                });
+                puts("Facilitator: posting registration.");
+                s_f.post_registration(s_c.id);
+            }
         }
-
-        private function fac_data(e:ProgressEvent):void
+        
+        private function start_proxy_pair():void
         {
-            var client_spec:String;
-            var client_addr:Object;
-            var proxy_pair: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;
-            }
-
-            num_proxy_pairs++;
-            total_proxy_pairs++;
-            /* Update the client count on the badge. */
-            update_client_count();
-
-            proxy_pair = new ProxyPair(this, client_addr, relay_addr);
-            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
-                proxy_pair.log("Complete.");
-                
-                num_proxy_pairs--;
-                /* Update the client count on the badge. */
-                update_client_count();
+            p_p = proxy_pair_factory();
+            p_p.addEventListener(Event.CONNECT, function (e:Event):void {
+                puts("ProxyPair: connected!");
             });
-            proxy_pair.connect();
-
+            p_p.addEventListener(Event.CLOSE, function (e:Event):void {
+                puts("ProxyPair: connection closed.");
+                p_p = null;
+                if (proxy_mode && !debug_mode && badge != null) {
+                    badge.num_proxy_pairs--;
+                }
+                establish_facilitator_connection();
+            });
+            p_p.relay = relay_addr;
+        }
+        
+        private function rtmfp_proxy_pair_factory():ProxyPair
+        {
+            return new RTMFPProxyPair(this, s_c, s_c.local_stream_name);
+        }
+        
+        private function tcp_proxy_pair_factory():ProxyPair
+        {
+            return new TCPProxyPair(this);
         }
 
         /* Parse an address in the form "host:port". Returns an Object with
            keys "host" (String) and "port" (int). Returns null on error. */
-        private static function parse_addr_spec(spec:String):Object
+        private function parse_addr_spec(spec:String):Object
         {
             var parts:Array;
             var addr:Object;
@@ -266,267 +283,105 @@ package
     }
 }
 
-import flash.display.Sprite;
-import flash.events.Event;
-import flash.events.EventDispatcher;
-import flash.events.IOErrorEvent;
-import flash.events.ProgressEvent;
-import flash.events.SecurityErrorEvent;
-import flash.net.Socket;
-import flash.utils.ByteArray;
-import flash.utils.clearTimeout;
-import flash.utils.getTimer;
-import flash.utils.setTimeout;
-
-class RateLimit
-{
-    public function RateLimit()
-    {
-    }
-
-    public function update(n:Number):Boolean
-    {
-        return true;
-    }
+import flash.text.TextField;
+import flash.text.TextFormat;
 
-    public function when():Number
-    {
-        return 0.0;
-    }
-
-    public function is_limited():Boolean
-    {
-        return false;
-    }
-}
-
-class RateUnlimit extends RateLimit
-{
-    public function RateUnlimit()
-    {
-    }
-
-    public override function update(n:Number):Boolean
-    {
-        return true;
-    }
-
-    public override function when():Number
-    {
-        return 0.0;
-    }
-
-    public override function is_limited():Boolean
-    {
-        return false;
-    }
-}
-
-class BucketRateLimit extends RateLimit
-{
-    private var amount:Number;
-    private var capacity:Number;
-    private var time:Number;
-    private var last_update:uint;
-
-    public function BucketRateLimit(capacity:Number, time:Number)
-    {
-        this.amount = 0.0;
-        /* capacity / time is the rate we are aiming for. */
-        this.capacity = capacity;
-        this.time = time;
-        this.last_update = getTimer();
-    }
-
-    private function age():void
-    {
-        var now:uint;
-        var delta:Number;
-
-        now = getTimer();
-        delta = (now - last_update) / 1000.0;
-        last_update = now;
-
-        amount -= delta * capacity / time;
-        if (amount < 0.0)
-            amount = 0.0;
-    }
-
-    public override function update(n:Number):Boolean
-    {
-        age();
-        amount += n;
-
-        return amount <= capacity;
-    }
-
-    public override function when():Number
-    {
-        age();
-        return (amount - capacity) / (capacity / time);
-    }
-
-    public override function is_limited():Boolean
-    {
-        age();
-        return amount > capacity;
-    }
-}
-
-/* An instance of a client-relay connection. */
-class ProxyPair extends EventDispatcher
-{
-    // 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 and rate meter.
+class InternetFreedomBadge {
+    
     private var ui:swfcat;
-
-    // Pending byte read counts for relay and client sockets.
-    private var r2c_schedule:Array;
-    private var c2r_schedule:Array;
-    // Callback id.
-    private var flush_id:uint;
-
-    public function log(msg:String):void
-    {
-        ui.puts(id() + ": " + msg)
-    }
-
-    // String describing this pair for output.
-    public 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)
+    
+    private var _num_proxy_pairs:uint;
+    private var _total_proxy_pairs:uint;
+    
+    [Embed(source="badge.png")]
+    private var BadgeImage:Class;
+    private var tot_client_count_tf:TextField;
+    private var tot_client_count_fmt:TextFormat;
+    private var cur_client_count_tf:TextField;
+    private var cur_client_count_fmt:TextFormat;
+    
+    public function InternetFreedomBadge(ui:swfcat)
     {
         this.ui = ui;
-        this.addr_c = addr_c;
-        this.addr_r = addr_r;
-
-        this.c2r_schedule = [];
-        this.r2c_schedule = [];
-    }
-
-    public function connect():void
-    {
-        s_r = new Socket();
-
-        s_r.addEventListener(Event.CONNECT, relay_connected);
-        s_r.addEventListener(Event.CLOSE, function (e:Event):void {
-            log("Relay: closed.");
-            if (s_c.connected)
-                s_c.close();
-            dispatchEvent(new Event(Event.COMPLETE));
-        });
-        s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-            log("Relay: I/O error: " + e.text + ".");
-            if (s_c.connected)
-                s_c.close();
-            dispatchEvent(new Event(Event.COMPLETE));
-        });
-        s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-            log("Relay: security error: " + e.text + ".");
-            if (s_c.connected)
-                s_c.close();
-            dispatchEvent(new Event(Event.COMPLETE));
-        });
-        s_r.addEventListener(ProgressEvent.SOCKET_DATA, relay_to_client);
-
-        log("Relay: connecting to " + addr_r.host + ":" + addr_r.port + ".");
-        s_r.connect(addr_r.host, addr_r.port);
+        _num_proxy_pairs = 0;
+        _total_proxy_pairs = 0;
+        
+        /* Setup client counter for badge. */
+        tot_client_count_fmt = new TextFormat();
+        tot_client_count_fmt.color = 0xFFFFFF;
+        tot_client_count_fmt.align = "center";
+        tot_client_count_fmt.font = "courier-new";
+        tot_client_count_fmt.bold = true;
+        tot_client_count_fmt.size = 10;
+        tot_client_count_tf = new TextField();
+        tot_client_count_tf.width = 20;
+        tot_client_count_tf.height = 17;
+        tot_client_count_tf.background = false;
+        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
+        tot_client_count_tf.x=47;
+        tot_client_count_tf.y=0;
+
+        cur_client_count_fmt = new TextFormat();
+        cur_client_count_fmt.color = 0xFFFFFF;
+        cur_client_count_fmt.align = "center";
+        cur_client_count_fmt.font = "courier-new";
+        cur_client_count_fmt.bold = true;
+        cur_client_count_fmt.size = 10;
+        cur_client_count_tf = new TextField();
+        cur_client_count_tf.width = 20;
+        cur_client_count_tf.height = 17;
+        cur_client_count_tf.background = false;
+        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
+        cur_client_count_tf.x=47;
+        cur_client_count_tf.y=6;
+
+        /* Update the client counter on badge. */
+        update_client_count();
     }
-
-    private function relay_connected(e:Event):void
+    
+    public function display():void
     {
-        log("Relay: 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();
-            dispatchEvent(new Event(Event.COMPLETE));
-        });
-        s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-            log("Client: I/O error: " + e.text + ".");
-            if (s_r.connected)
-                s_r.close();
-            dispatchEvent(new Event(Event.COMPLETE));
-        });
-        s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-            log("Client: security error: " + e.text + ".");
-            if (s_r.connected)
-                s_r.close();
-            dispatchEvent(new Event(Event.COMPLETE));
-        });
-        s_c.addEventListener(ProgressEvent.SOCKET_DATA, client_to_relay);
-
-        log("Client: connecting to " + addr_c.host + ":" + addr_c.port + ".");
-        s_c.connect(addr_c.host, addr_c.port);
+        ui.addChild(new BadgeImage());
+        /* Tried unsuccessfully to add counter to badge. */
+        /* For now, need two addChilds :( */
+        ui.addChild(tot_client_count_tf);
+        ui.addChild(cur_client_count_tf);
     }
-
-    private function relay_to_client(e:ProgressEvent):void
+    
+    public function get num_proxy_pairs():uint
     {
-        r2c_schedule.push(e.bytesLoaded);
-        flush();
+        return _num_proxy_pairs;
     }
-
-    private function client_to_relay(e:ProgressEvent):void
+    
+    public function set num_proxy_pairs(amount:uint):void
     {
-        c2r_schedule.push(e.bytesLoaded);
-        flush();
+        _num_proxy_pairs = amount;
+        update_client_count();
     }
-
-    private function client_connected(e:Event):void
+    
+    public function get total_proxy_pairs():uint
     {
-        log("Client: connected.");
+        return _total_proxy_pairs;
     }
-
-    private function transfer_chunk(s_from:Socket, s_to:Socket, n:uint,
-        label:String):void
+    
+    public function set total_proxy_pairs(amount:uint):void
     {
-        var bytes:ByteArray;
-
-        bytes = new ByteArray();
-        s_from.readBytes(bytes, 0, n);
-        s_to.writeBytes(bytes);
-        ui.rate_limit.update(n);
-        log(label + ": read " + bytes.length + ".");
+        _total_proxy_pairs = amount;
+        /* note: doesn't update, so be sure to update this
+           before you update num_proxy_pairs! */
     }
-
-    /* Send as much data as the rate limit currently allows. */
-    private function flush():void
+    
+    private function update_client_count():void
     {
-        if (flush_id)
-            clearTimeout(flush_id);
-        flush_id = undefined;
-
-        if (!(s_r.connected && s_c.connected))
-            /* Can't do anything until both sockets are connected. */
-            return;
-
-        while (!ui.rate_limit.is_limited() &&
-               (r2c_schedule.length > 0 || c2r_schedule.length > 0)) {
-            if (r2c_schedule.length > 0)
-                transfer_chunk(s_r, s_c, r2c_schedule.shift(), "Relay");
-            if (c2r_schedule.length > 0)
-                transfer_chunk(s_c, s_r, c2r_schedule.shift(), "Client");
-        }
-
-        /* Call again when safe, if necessary. */
-        if (r2c_schedule.length > 0 || c2r_schedule.length > 0)
-            flush_id = setTimeout(flush, ui.rate_limit.when() * 1000);
+        /* Update total client count. */
+        if (String(total_proxy_pairs).length == 1)
+            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
+        else
+            tot_client_count_tf.text = String(total_proxy_pairs);
+
+        /* Update current client count. */
+        cur_client_count_tf.text = "";
+        for(var i:Number = 0; i < num_proxy_pairs; i++)
+            cur_client_count_tf.appendText(".");
     }
 }





More information about the tor-commits mailing list