[tor-commits] [flashproxy/master] Working version of rtmfpcat

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


commit 9b6bf0774eb5e713bc1c317c8bc28773daabe5b8
Author: Nate Hardison <nate at rescomp-09-154551.stanford.edu>
Date:   Tue May 10 02:47:26 2011 -0700

    Working version of rtmfpcat
---
 Connector.as                                  |  183 --------------
 Makefile                                      |    2 +-
 Proxy.as                                      |  261 --------------------
 RTMFPRelay.as                                 |   99 --------
 RTMFPRelayReactor.as                          |   13 -
 Utils.as                                      |   24 --
 com/jscat/Connector.as                        |  188 --------------
 com/jscat/Utils.as                            |   25 --
 com/jscat/facilitator.py                      |  146 -----------
 com/jscat/rtmfp/RTMFPSocket.as                |  216 ----------------
 com/jscat/rtmfp/RTMFPSocketClient.as          |   57 -----
 com/jscat/rtmfp/events/RTMFPSocketEvent.as    |   25 --
 com/rtmfpcat/Makefile                         |   11 +
 com/rtmfpcat/README                           |  118 +++++++++
 com/rtmfpcat/Utils.as                         |   25 ++
 com/rtmfpcat/connector.py                     |  326 +++++++++++++++++++++++++
 com/rtmfpcat/facilitator.py                   |  146 +++++++++++
 com/rtmfpcat/rtmfp/RTMFPSocket.as             |  216 ++++++++++++++++
 com/rtmfpcat/rtmfp/RTMFPSocketClient.as       |   57 +++++
 com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as |   25 ++
 com/rtmfpcat/rtmfpcat.as                      |  208 ++++++++++++++++
 21 files changed, 1133 insertions(+), 1238 deletions(-)

diff --git a/Connector.as b/Connector.as
deleted file mode 100644
index fc18692..0000000
--- a/Connector.as
+++ /dev/null
@@ -1,183 +0,0 @@
-package
-{
-    import flash.display.Sprite;
-    import flash.text.TextField;
-    import flash.net.Socket;
-    import flash.events.Event;
-    import flash.events.EventDispatcher;
-    import flash.events.IOErrorEvent;
-    import flash.events.NetStatusEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.net.NetConnection;
-    import flash.net.NetStream;
-    import flash.utils.ByteArray;
-    import flash.utils.clearInterval;
-    import flash.utils.setInterval;
-    import flash.utils.setTimeout;
-    
-    import RTMFPRelay;
-    import RTMFPRelayReactor;
-    import Utils;
-    
-    public class Connector extends Sprite implements RTMFPRelayReactor {
-      
-      /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
-         crossdomain policy. */
-      private const DEFAULT_TOR_ADDR:Object = {
-          host: "173.255.221.44",
-          port: 9001
-      };
-      
-      private var output_text:TextField;
-
-      // Socket to Tor relay.
-      private var s_t:Socket;
-      // Socket to facilitator.
-      private var s_f:Socket;
-      // RTMFP data relay
-      private var relay:RTMFPRelay;
-      
-      private var fac_addr:Object;
-      private var tor_addr:Object;
-      
-      public function Connector() {
-        output_text = new TextField();
-        output_text.width = 400;
-        output_text.height = 300;
-        output_text.background = true;
-        output_text.backgroundColor = 0x001f0f;
-        output_text.textColor = 0x44CC44;
-        addChild(output_text);
-
-        puts("Starting.");
-        
-        // Wait until the query string parameters are loaded.
-        this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
-      }
-      
-      private function facilitator_is(host:String, port:int):void
-      {
-        if (s_f != null && s_f.connected) {
-          puts("Error: already connected to Facilitator!");
-          return;
-        }
-        
-        s_f = new Socket();
-        
-        s_f.addEventListener(Event.CONNECT, function (e:Event):void {
-          puts("Facilitator: connected.");
-          onConnectionEvent();
-        });
-        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 " + host + ":" + port + ".");
-        s_f.connect(host, port);
-      }
-      
-      private function tor_relay_is(host:String, port:int):void
-      {
-        if (s_t != null && s_t.connected) {
-          puts("Error: already connected to Tor relay!");
-          return;
-        }
-        
-        s_t = new Socket();
-
-        s_t.addEventListener(Event.CONNECT, function (e:Event):void {
-          puts("Tor: connected.");
-          onConnectionEvent();
-        });
-        s_t.addEventListener(Event.CLOSE, function (e:Event):void {
-          puts("Tor: closed.");
-        });
-        s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-          puts("Tor: I/O error: " + e.text + ".");
-        });
-        s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-          puts("Tor: security error: " + e.text + ".");
-        });
-
-        puts("Tor: connecting to " + host + ":" + port + ".");
-        s_t.connect(host, port);
-      }
-
-      private function puts(s:String):void
-      {
-          output_text.appendText(s + "\n");
-          output_text.scrollV = output_text.maxScrollV;
-      }
-      
-      private function loaderinfo_complete(e:Event):void
-      {
-        var fac_spec:String;
-        var tor_spec:String;
-
-        puts("Parameters loaded.");
-        
-        fac_spec = this.loaderInfo.parameters["facilitator"];
-        if (!fac_spec) {
-            puts("Error: no \"facilitator\" specification provided.");
-            return;
-        }
-        puts("Facilitator spec: \"" + fac_spec + "\"");
-        fac_addr = Utils.parse_addr_spec(fac_spec);
-        if (!fac_addr) {
-            puts("Error: Facilitator spec must be in the form \"host:port\".");
-            return;
-        }
-
-        relay = new RTMFPRelay(this);
-
-        tor_addr = DEFAULT_TOR_ADDR;
-        tor_relay_is(tor_addr.host, tor_addr.port);
-        facilitator_is(fac_addr.host, fac_addr.port);
-      }
-      
-      public function onConnectionEvent():void
-      {
-        if (s_f != null && s_f.connected && s_t != null && s_t.connected && 
-            relay != null && relay.connected) {
-              s_f.writeUTFBytes("POST / HTTP/1.1\r\n\r\nclient=%3A"+ relay.cirrus_id + "\r\n");
-        }
-      }
-      
-      public function onIOErrorEvent(event:IOErrorEvent):void
-      {
-        puts("Cirrus: I/O error: " + event.text + ".");
-      }
-      
-      public function onNetStatusEvent(event:NetStatusEvent):void
-      {
-        switch (event.info.code) {
-  				case "NetConnection.Connect.Success" :
-  					puts("Cirrus: connected with ID " + relay.cirrus_id + ".");
-  					onConnectionEvent();
-  					break;
-  				case "NetStream.Connect.Success" :
-  				  puts("Peer: connected.");
-  					break;
-  				case "NetStream.Publish.BadName" :
-  					puts(event.info.code);
-  					break;
-  				case "NetStream.Connect.Closed" :
-  					puts(event.info.code);
-  					break;
-  			}
-      }
-      
-      public function onSecurityErrorEvent(event:SecurityErrorEvent):void
-      {
-        puts("Cirrus: security error: " + event.text + ".");
-      }
-  }
-}
-    
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 92047fb..0c3beba 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 MXMLC ?= mxmlc
 
-TARGETS = swfcat.swf Connector.swf
+TARGETS = swfcat.swf com/rtmfpcat/rtmfpcat.swf
 
 all: $(TARGETS)
 
diff --git a/Proxy.as b/Proxy.as
deleted file mode 100644
index 01b2fe3..0000000
--- a/Proxy.as
+++ /dev/null
@@ -1,261 +0,0 @@
-package
-{
-    import flash.display.Sprite;
-    import flash.text.TextField;
-    import flash.net.Socket;
-    import flash.events.Event;
-    import flash.events.EventDispatcher;
-    import flash.events.IOErrorEvent;
-    import flash.events.NetStatusEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.net.NetConnection;
-    import flash.net.NetStream;
-    import flash.utils.ByteArray;
-    import flash.utils.clearInterval;
-    import flash.utils.setInterval;
-    import flash.utils.setTimeout;
-
-    public class Proxy extends Sprite
-    {
-        /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
-           crossdomain policy. */
-        private const DEFAULT_TOR_ADDR:Object = {
-            host: "173.255.221.44",
-            port: 9001
-        };
-
-		    /* Address of the Cirrus rendezvous service with Nate's
-		       developer key appended */
-		    private static const CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net/" + RTMFP::CIRRUS_KEY + "/";
-		
-        // The name of our data stream
-        public static const DATA:String = "data";
-		
-        private var output_text:TextField;
-
-        // Socket to Tor relay.
-        private var s_t:Socket;
-        // Socket to facilitator.
-        private var s_f:Socket;
-
-        /* Connection to the Cirrus rendezvous service that will hook
-           us up with the Tor client */
-        public var cirrus:NetConnection;
-		
-		    // The Tor client's peerID (used when establishing the client_stream)
-		    private var client_id:String;
-		
-		    // The data streams to be established with the Tor client
-		    public var send_stream:NetStream;
-		    public var recv_stream:NetStream;
-
-        private var fac_addr:Object;
-        private var tor_addr:Object;
-
-        private function puts(s:String):void
-        {
-            output_text.appendText(s + "\n");
-            output_text.scrollV = output_text.maxScrollV;
-        }
-
-        public function Proxy()
-        {
-            output_text = new TextField();
-            output_text.width = 400;
-            output_text.height = 300;
-            output_text.background = true;
-            output_text.backgroundColor = 0x001f0f;
-            output_text.textColor = 0x44CC44;
-            addChild(output_text);
-
-            puts("Starting.");
-            // Wait until the query string parameters are loaded.
-            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
-        }
-
-        private function loaderinfo_complete(e:Event):void
-        {
-            var fac_spec:String;
-            var tor_spec:String;
-
-            puts("Parameters loaded.");
-            fac_spec = this.loaderInfo.parameters["facilitator"];
-            if (!fac_spec) {
-                puts("Error: no \"facilitator\" specification provided.");
-                return;
-            }
-            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;
-            }
-
-            tor_addr = DEFAULT_TOR_ADDR;
-
-            go();
-        }
-
-        /* We connect first to the Tor relay; once that happens we connect to
-           the facilitator to get a client address; once we have the address
-           of a waiting client then we connect to the client and BAM! we're in business. */
-        private function go():void
-        {
-            s_t = new Socket();
-
-            s_t.addEventListener(Event.CONNECT, tor_connected);
-            s_t.addEventListener(Event.CLOSE, function (e:Event):void {
-                puts("Tor: closed.");
-            });
-            s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-                puts("Tor: I/O error: " + e.text + ".");
-            });
-            s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-                puts("Tor: security error: " + e.text + ".");
-
-            });
-
-            puts("Tor: connecting to " + tor_addr.host + ":" + tor_addr.port + ".");
-            s_t.connect(tor_addr.host, tor_addr.port);
-        }
-
-        private function tor_connected(e:Event):void
-        {
-            /* Got a connection to tor, now let's get served a client from the facilitator */
-            s_f = new Socket();
-
-            puts("Tor: connected.");
-            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 + ".");
-                if (s_t.connected)
-                    s_t.close();
-            });
-            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-                puts("Facilitator: security error: " + e.text + ".");
-                if (s_t.connected)
-                    s_t.close();
-            });
-
-            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
-            s_f.connect(fac_addr.host, fac_addr.port);
-        }
-
-        private function fac_connected(e:Event):void
-        {
-            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;
-              }*/
-
-				      /* Now we have a client, so start up a connection to the Cirrus rendezvous point */
-				      cirrus = new NetConnection();
-				      cirrus.addEventListener(NetStatusEvent.NET_STATUS, net_status_event_handler);
-				      cirrus.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-					      if (s_t.connected) s_t.close();
-					      if (s_f.connected) s_f.close();
-				      });
-				
-				      cirrus.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityError):void {
-					      if (s_t.connected) s_t.close();
-					      if (s_f.connected) s_f.close();
-				      });
-				
-				      cirrus.connect(CIRRUS_ADDRESS);
-            });
-
-            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
-        }
-		
-		private function net_status_event_handler(e:NetStatusEvent):void
-		{
-			switch (e.info.code) {
-				case "NetConnection.Connect.Success" :
-					// Cirrus is now connected
-					cirrus_connected(e);
-			}
-			
-		}
-		
-		private function cirrus_connected(e:Event):void
-		{
-			if (client_id == null) {
-				puts("Error: Client ID doesn't exist.");
-				return;
-			} else if (client_id == cirrus.nearID) {
-				puts("Error: Client ID is our ID.");
-				return;
-			}
-					
-			send_stream = new NetStream(cirrus, NetStream.DIRECT_CONNECTIONS);
-			send_stream.addEventListener(NetStatusEvent.NET_STATUS, net_status_event_handler);
-			send_stream.publish(DATA);
-			
-			recv_stream = new NetStream(cirrus, client_id);
-			recv_stream.addEventListener(NetStatusEvent.NET_STATUS, net_status_event_handler);
-			recv_stream.play(DATA);
-		}
-
-        /*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);
-            });
-        }*/
-		
-		private function close_connections():void
-		{
-			if (s_t.connected) s_t.close();
-			if (s_f.connected) s_f.close();
-			if (cirrus.connected) cirrus.close();
-			if (send_stream != null) send_stream.close();
-			if (recv_stream != null) recv_stream.close();
-		}
-
-        /* 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
-        {
-            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;
-        }
-    }
-}
diff --git a/RTMFPRelay.as b/RTMFPRelay.as
deleted file mode 100644
index a33ebbf..0000000
--- a/RTMFPRelay.as
+++ /dev/null
@@ -1,99 +0,0 @@
-package
-{
-    import flash.display.Sprite;
-    import flash.text.TextField;
-    import flash.net.Socket;
-    import flash.events.Event;
-    import flash.events.EventDispatcher;
-    import flash.events.IOErrorEvent;
-    import flash.events.NetStatusEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.net.NetConnection;
-    import flash.net.NetStream;
-    import flash.utils.ByteArray;
-    import flash.utils.clearInterval;
-    import flash.utils.setInterval;
-    import flash.utils.setTimeout;
-
-    public class RTMFPRelay extends Sprite
-    {
-		    private static const CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net";
-		    private static const CIRRUS_DEV_KEY:String = RTMFP::CIRRUS_KEY;
-		
-        /* The name of the "media" to pass between peers */
-        private static const DATA:String = "data";
-		
-        /* Connection to the Cirrus rendezvous service */
-        private var cirrus_conn:NetConnection;
-		
-		    /* ID of the peer to connect to */
-		    private var peer_id:String;
-		
-		    /* Data streams to be established with peer */
-		    private var send_stream:NetStream;
-		    private var recv_stream:NetStream;
-		    
-		    private var notifiee:RTMFPRelayReactor;
-
-        public function RTMFPRelay(notifiee:RTMFPRelayReactor)
-        {   
-          this.notifiee = notifiee;
-          
-          cirrus_conn = new NetConnection();
-			    cirrus_conn.addEventListener(NetStatusEvent.NET_STATUS, notifiee.onNetStatusEvent);
-			    cirrus_conn.addEventListener(IOErrorEvent.IO_ERROR, notifiee.onIOErrorEvent);
-			    cirrus_conn.addEventListener(SecurityErrorEvent.SECURITY_ERROR, notifiee.onSecurityErrorEvent);
-			
-			    cirrus_conn.connect(CIRRUS_ADDRESS + "/" + CIRRUS_DEV_KEY);
-        }
-
-        public function get cirrus_id():String
-        {
-          if (cirrus_conn != null && cirrus_conn.connected) {
-            return cirrus_conn.nearID;
-          }
-          
-          return null;
-        }
-        
-        public function get connected():Boolean
-        {
-          return (cirrus_conn != null && cirrus_conn.connected);
-        }
-
-        public function data_is(data:ByteArray):void
-        {
-          
-          
-        }
-        
-        public function get peer():String
-        {
-          return this.peer_id;
-        }
-
-        public function set peer(peer_id:String):void
-        {
-          if (peer_id == null) {
-            throw new Error("Peer ID is null.")
-    			} else if (peer_id == cirrus_conn.nearID) {
-    				throw new Error("Peer ID cannot be the same as our ID.");
-    			} else if (this.peer_id == peer_id) {
-    			  throw new Error("Already connected to peer " + peer_id + ".");
-    			} else if (this.recv_stream != null) {
-            throw new Error("Cannot connect to a second peer.");
-          }
-          
-          this.peer_id = peer_id;
-          
-          send_stream = new NetStream(cirrus_conn, NetStream.DIRECT_CONNECTIONS);
-    			send_stream.addEventListener(NetStatusEvent.NET_STATUS, notifiee.onNetStatusEvent);
-    			send_stream.publish(DATA);
-
-    			recv_stream = new NetStream(cirrus_conn, peer_id);
-    			recv_stream.addEventListener(NetStatusEvent.NET_STATUS, notifiee.onNetStatusEvent);
-    			recv_stream.play(DATA);
-        }
-    }
-}
diff --git a/RTMFPRelayReactor.as b/RTMFPRelayReactor.as
deleted file mode 100644
index 5c84779..0000000
--- a/RTMFPRelayReactor.as
+++ /dev/null
@@ -1,13 +0,0 @@
-package {
-
-  import flash.events.IOErrorEvent;
-  import flash.events.NetStatusEvent;
-  import flash.events.SecurityErrorEvent;
-  
-  public interface RTMFPRelayReactor {
-    function onIOErrorEvent(event:IOErrorEvent):void;
-    function onNetStatusEvent(event:NetStatusEvent):void;
-    function onSecurityErrorEvent(event:SecurityErrorEvent):void
-  }
-  
-}
\ No newline at end of file
diff --git a/Utils.as b/Utils.as
deleted file mode 100644
index 75ed44c..0000000
--- a/Utils.as
+++ /dev/null
@@ -1,24 +0,0 @@
-package {
-  
-  public class Utils {
-    
-    /* Parse an address in the form "host:port". Returns an Object with
-       keys "host" (String) and "port" (int). Returns null on error. */
-    public static 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;
-    }
-    
-  }
-  
-}
\ No newline at end of file
diff --git a/com/jscat/Connector.as b/com/jscat/Connector.as
deleted file mode 100644
index dab7d0a..0000000
--- a/com/jscat/Connector.as
+++ /dev/null
@@ -1,188 +0,0 @@
-package
-{
-    import flash.display.Sprite;
-    import flash.text.TextField;
-    import flash.net.Socket;
-    import flash.events.Event;
-    import flash.events.EventDispatcher;
-    import flash.events.IOErrorEvent;
-    import flash.events.NetStatusEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.utils.ByteArray;
-    
-    import rtmfp.RTMFPSocket;
-    import rtmfp.events.RTMFPSocketEvent;
-    import Utils;
-    
-    public class Connector extends Sprite {
-      
-      /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
-         crossdomain policy. */
-      private const DEFAULT_TOR_RELAY:Object = {
-          host: "173.255.221.44",
-          port: 9001
-      };
-      
-      private var output_text:TextField;
-      
-      private var s_f:Socket;
-      private var s_r:RTMFPSocket;
-      private var s_t:Socket;
-      
-      private var fac_addr:Object;
-      private var tor_addr:Object;
-      
-      public function Connector()
-      {
-        output_text = new TextField();
-        output_text.width = 400;
-        output_text.height = 300;
-        output_text.background = true;
-        output_text.backgroundColor = 0x001f0f;
-        output_text.textColor = 0x44CC44;
-        addChild(output_text);
-
-        puts("Starting.");
-        
-        this.loaderInfo.addEventListener(Event.COMPLETE, onLoaderInfoComplete);
-      }
-      
-      protected function puts(s:String):void
-      {
-          output_text.appendText(s + "\n");
-          output_text.scrollV = output_text.maxScrollV;
-      }
-      
-      private function onLoaderInfoComplete(e:Event):void
-      {
-        var fac_spec:String;
-        var tor_spec:String;
-
-        puts("Parameters loaded.");
-        
-        fac_spec = this.loaderInfo.parameters["facilitator"];
-        if (!fac_spec) {
-          puts("Error: no \"facilitator\" specification provided.");
-          return;
-        }
-        puts("Facilitator spec: \"" + fac_spec + "\"");
-        fac_addr = Utils.parseAddrSpec(fac_spec);
-        if (!fac_addr) {
-            puts("Error: Facilitator spec must be in the form \"host:port\".");
-            return;
-        }
-        
-        tor_spec = this.loaderInfo.parameters["tor"];
-        if (!tor_spec) {
-          puts("Error: No Tor specification provided.");
-          return;
-        }
-        puts("Tor spec: \"" + tor_spec + "\"")
-        tor_addr = Utils.parseAddrSpec(tor_spec);
-        if (!tor_addr) {
-          puts("Error: Tor spec must be in the form \"host:port\".");
-          return;
-        }
-        
-        s_r = new RTMFPSocket();
-        s_r.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, onRTMFPSocketConnect);
-        s_r.addEventListener(RTMFPSocketEvent.CONNECT_FAIL, function (e:Event):void {
-          puts("Error: failed to connect to Cirrus.");
-        });
-        s_r.addEventListener(RTMFPSocketEvent.PUBLISH_START, function(e:RTMFPSocketEvent):void {
-          
-        });
-        s_r.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function(e:RTMFPSocketEvent):void {
-          
-        });
-        s_r.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function(e:RTMFPSocketEvent):void {
-          
-        });
-        s_r.addEventListener(RTMFPSocketEvent.PEERING_SUCCESS, function(e:RTMFPSocketEvent):void {
-          
-        });
-        s_r.addEventListener(RTMFPSocketEvent.PEERING_FAIL, function(e:RTMFPSocketEvent):void {
-          
-        });
-        s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
-          var bytes:ByteArray = new ByteArray();
-          s_r.readBytes(bytes);
-          puts("RTMFP: read " + bytes.length + " bytes.");
-          s_t.writeBytes(bytes);
-        });
-        
-        s_r.connect();
-      }
-      
-      private function onRTMFPSocketConnect(event:RTMFPSocketEvent):void
-      {
-        puts("Cirrus: connected with id " + s_r.id + ".");
-        s_t = new Socket();
-        s_t.addEventListener(Event.CONNECT, onTorSocketConnect);
-        s_t.addEventListener(Event.CLOSE, function (e:Event):void {
-          
-        });
-        s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-          puts("Tor: I/O error: " + e.text + ".");
-        });
-        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 + " bytes.");
-          s_r.writeBytes(bytes);
-        });
-        s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-          puts("Tor: security error: " + e.text + ".");
-        });
-
-        s_t.connect(tor_addr.host, tor_addr.port);
-        onTorSocketConnect(new Event(""));
-      }
-      
-      private function onTorSocketConnect(event:Event):void
-      {
-        puts("Tor: connected to " + tor_addr.host + ":" + tor_addr.port + ".");
-        
-        s_f = new Socket();
-        s_f.addEventListener(Event.CONNECT, onFacilitatorSocketConnect);
-        s_f.addEventListener(Event.CLOSE, function (e:Event):void {
-          
-        });
-        s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
-          puts("Facilitator: I/O error: " + e.text + ".");
-        });
-        s_f.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
-          var clientID:String = s_f.readMultiByte(e.bytesLoaded, "utf-8");
-          puts("Facilitator: got \"" + clientID + "\"");
-          puts("Connecting to " + clientID + ".");
-          s_r.peer = clientID;
-        });
-        s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
-          puts("Facilitator: security error: " + e.text + ".");
-        });
-
-        s_f.connect(fac_addr.host, fac_addr.port);
-      }
-      
-      private function onFacilitatorSocketConnect(event:Event):void
-      {
-        puts("Facilitator: connected to " + fac_addr.host + ":" + fac_addr.port + ".");
-        onConnectionEvent();
-      }
-      
-      private function onConnectionEvent():void
-      {
-        if (s_f != null && s_f.connected && s_t != null && /*s_t.connected && */
-            s_r != null && s_r.connected) {
-              if (this.loaderInfo.parameters["proxy"]) {
-                s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
-              } else {
-                var str:String = "POST / HTTP/1.0\r\n\r\nclient=" + s_r.id + "\r\n"
-                puts(str);
-                s_f.writeUTFBytes(str);
-              }
-        }
-      }
-  }
-}
\ No newline at end of file
diff --git a/com/jscat/Utils.as b/com/jscat/Utils.as
deleted file mode 100644
index 48f9a62..0000000
--- a/com/jscat/Utils.as
+++ /dev/null
@@ -1,25 +0,0 @@
-package
-{
-  
-  public class Utils {
-    
-    /* Parse an address in the form "host:port". Returns an Object with
-       keys "host" (String) and "port" (int). Returns null on error. */
-    public static function parseAddrSpec(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;
-    }
-    
-  }
-  
-}
\ No newline at end of file
diff --git a/com/jscat/facilitator.py b/com/jscat/facilitator.py
deleted file mode 100755
index 8c6a44e..0000000
--- a/com/jscat/facilitator.py
+++ /dev/null
@@ -1,146 +0,0 @@
-#!/usr/bin/env python
-
-import BaseHTTPServer
-import getopt
-import cgi
-import re
-import sys
-import socket
-from collections import deque
-
-DEFAULT_ADDRESS = "0.0.0.0"
-DEFAULT_PORT = 9002
-
-def usage(f = sys.stdout):
-	print >> f, """\
-Usage: %(progname)s <OPTIONS> [HOST] [PORT]
-Flash bridge facilitator: Register client addresses with HTTP POST requests
-and serve them out again with HTTP GET. Listen on HOST and PORT, by default
-%(addr)s %(port)d.
-  -h, --help		   show this help.\
-""" % {
-	"progname": sys.argv[0],
-	"addr": DEFAULT_ADDRESS,
-	"port": DEFAULT_PORT,
-}
-
-REGS = deque()
-
-class Reg(object):
-	def __init__(self, id):
-		self.id = id
-
-	def __unicode__(self):
-	  return u"%s" % (self.id)
-
-	def __str__(self):
-		return unicode(self).encode("UTF-8")
-
-	def __cmp__(self, other):
-		return cmp((self.id), (other.id))
-
-	@staticmethod
-	def parse(spec, defhost = None, defport = None):
-		host = None
-		port = None
-		m = re.match(r'^\[(.+)\]:(\d*)$', spec)
-		if m:
-			host, port = m.groups()
-			af = socket.AF_INET6
-		else:
-			m = re.match(r'^(.*):(\d*)$', spec)
-			if m:
-				host, port = m.groups()
-				if host:
-					af = socket.AF_INET
-				else:
-					# Has to be guessed from format of defhost.
-					af = 0
-		host = host or defhost
-		port = port or defport
-		if not (host and port):
-			raise ValueError("Bad address specification \"%s\"" % spec)
-
-		try:
-			addrs = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
-		except socket.gaierror, e:
-			raise ValueError("Bad host or port: \"%s\" \"%s\": %s" % (host, port, str(e)))
-		if not addrs:
-			raise ValueError("Bad host or port: \"%s\" \"%s\"" % (host, port))
-
-		af = addrs[0][0]
-		host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
-		return Reg(af, host, int(port))
-
-def fetch_reg():
-	"""Get a client registration, or None if none is available."""
-	if not REGS:
-		return None
-	return REGS.popleft()
-
-class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
-	def do_GET(self):
-		print "From " + str(self.client_address) + " received: GET:",
-		reg = fetch_reg()
-		if reg:
-			print "Handing out " + str(reg) + ". Clients: " + str(len(REGS))
-			self.request.send(str(reg))
-		else:
-			print "Registration list is empty"
-			self.request.send("Registration list empty")
-
-	def do_POST(self):
-		print "From " + str(self.client_address) + " received: POST:",
-		data = self.rfile.readline().strip()
-		print data + " :",
-		try:
-			vals = cgi.parse_qs(data, False, True)
-		except ValueError, e:
-			print "Syntax error in POST:", str(e)
-			return
-
-		client_specs = vals.get("client")
-		if client_specs is None or len(client_specs) != 1:
-			print "In POST: need exactly one \"client\" param"
-			return
-		val = client_specs[0]
-
-		try:
-			reg = Reg(val)
-		except ValueError, e:
-			print "Can't parse client \"%s\": %s" % (val, str(e))
-			return
-
-		if reg not in list(REGS):
-			REGS.append(reg)
-			print "Registration " + str(reg) + " added. Registrations: " + str(len(REGS))
-		else:
-			print "Registration " + str(reg) + " already present. Registrations: " + str(len(REGS))
-
-opts, args = getopt.gnu_getopt(sys.argv[1:], "h", ["help"])
-for o, a in opts:
-	if o == "-h" or o == "--help":
-		usage()
-		sys.exit()
-
-if len(args) == 0:
-	address = (DEFAULT_ADDRESS, DEFAULT_PORT)
-elif len(args) == 1:
-	# Either HOST or PORT may be omitted; figure out which one.
-	if args[0].isdigit():
-		address = (DEFAULT_ADDRESS, args[0])
-	else:
-		address = (args[0], DEFAULT_PORT)
-elif len(args) == 2:
-	address = (args[0], args[1])
-else:
-	usage(sys.stderr)
-	sys.exit(1)
-
-# Setup the server
-server = BaseHTTPServer.HTTPServer(address, Handler)
-
-print "Starting Facilitator on " + str(address) + "..."
-
-# Run server... Single threaded serving of requests...
-server.serve_forever()
diff --git a/com/jscat/rtmfp/RTMFPSocket.as b/com/jscat/rtmfp/RTMFPSocket.as
deleted file mode 100644
index 4b83784..0000000
--- a/com/jscat/rtmfp/RTMFPSocket.as
+++ /dev/null
@@ -1,216 +0,0 @@
-package rtmfp
-{
-    import flash.events.Event;
-    import flash.events.EventDispatcher;
-    import flash.events.IOErrorEvent;
-    import flash.events.NetStatusEvent;
-    import flash.events.ProgressEvent;
-    import flash.events.SecurityErrorEvent;
-    import flash.net.NetConnection;
-    import flash.net.NetStream;
-    import flash.utils.ByteArray;
-    import flash.utils.clearInterval;
-    import flash.utils.setInterval;
-    import flash.utils.setTimeout;
-    
-    import rtmfp.RTMFPSocketClient;
-    import rtmfp.events.RTMFPSocketEvent;
-
-    [Event(name="connectSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-  	[Event(name="connectFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-  	[Event(name="publishStart", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-  	[Event(name="peerConnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-  	[Event(name="peeringSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-  	[Event(name="peeringFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-  	[Event(name="peerDisconnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
-    public class RTMFPSocket extends EventDispatcher
-    {
-        /* The name of the "media" to pass between peers */
-        private static const DATA:String = "data";
-		    private static const DEFAULT_CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net";
-		    private static const DEFAULT_CIRRUS_KEY:String = RTMFP::CIRRUS_KEY;
-		    private static const DEFAULT_CONNECT_TIMEOUT:uint = 4000;
-		
-        /* Connection to the Cirrus rendezvous service */
-        private var connection:NetConnection;
-		
-		    /* ID of the peer to connect to */
-		    private var peerID:String;
-		
-		    /* Data streams to be established with peer */
-		    private var sendStream:NetStream;
-		    private var recvStream:NetStream;
-		    
-		    /* Timeouts */
-		    private var connectionTimeout:int;
-		    private var peerConnectTimeout:uint;
-
-        public function RTMFPSocket(){}
-        
-        public function connect(addr:String = DEFAULT_CIRRUS_ADDRESS, key:String = DEFAULT_CIRRUS_KEY):void
-        {
-          connection = new NetConnection();
-			    connection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatusEvent);
-			    connection.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEvent);
-			    connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
-          connection.connect(addr + "/" + key);
-			    connectionTimeout = setInterval(fail, DEFAULT_CONNECT_TIMEOUT);
-        }
-
-        public function get id():String
-        {
-          if (connection != null && connection.connected) {
-            return connection.nearID;
-          }
-          
-          return null;
-        }
-        
-        public function get connected():Boolean
-        {
-          return (connection != null && connection.connected);
-        }
-        
-        public function readBytes(bytes:ByteArray):void
-        {
-          recvStream.client.bytes.readBytes(bytes);
-        }
-
-        public function writeBytes(bytes:ByteArray):void
-        {
-          sendStream.send("dataAvailable", bytes);
-        }
-        
-        public function get peer():String
-        {
-          return this.peerID;
-        }
-
-        public function set peer(peerID:String):void
-        {
-          if (peerID == null || peerID.length == 0) {
-            throw new Error("Peer ID is null/empty.")
-    			} else if (peerID == connection.nearID) {
-    				throw new Error("Peer ID cannot be the same as our ID.");
-    			} else if (this.peerID == peerID) {
-    			  throw new Error("Already connected to peer " + peerID + ".");
-    			} else if (this.recvStream != null) {
-            throw new Error("Cannot connect to a second peer.");
-          }
-          
-          this.peerID = peerID;
-
-    			recvStream = new NetStream(connection, peerID);
-    			var client:RTMFPSocketClient = new RTMFPSocketClient();
-    			client.addEventListener(ProgressEvent.SOCKET_DATA, onDataAvailable, false, 0, true);
-    			client.addEventListener(RTMFPSocketClient.PEER_CONNECT_ACKNOWLEDGED, onPeerConnectAcknowledged, false, 0, true);
-    			recvStream.client = client;
-    			recvStream.addEventListener(NetStatusEvent.NET_STATUS, onRecvStreamEvent);
-    			recvStream.play(DATA);
-    			setTimeout(onPeerConnectTimeout, peerConnectTimeout, recvStream);
-        }
-        
-        private function startPublishStream():void
-        {
-          sendStream = new NetStream(connection, NetStream.DIRECT_CONNECTIONS);
-    			sendStream.addEventListener(NetStatusEvent.NET_STATUS, onSendStreamEvent);
-    			var o:Object = new Object();
-    			o.onPeerConnect = onPeerConnect;
-    			sendStream.client = o;
-    			sendStream.publish(DATA);
-        }
-        
-        private function fail():void
-        {
-          clearInterval(connectionTimeout);
-    			dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_FAIL));
-        }
-        
-        private function onDataAvailable(event:ProgressEvent):void
-        {
-          dispatchEvent(event);
-        }
-        
-        private function onIOErrorEvent(event:IOErrorEvent):void
-        {
-          fail();
-        }
-        
-        private function onNetStatusEvent(event:NetStatusEvent):void
-        {
-          switch (event.info.code) {
-      			case "NetConnection.Connect.Success" :
-      				clearInterval(connectionTimeout);
-      				startPublishStream();
-      				dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_SUCCESS));
-      				break;
-      			case "NetStream.Connect.Success" :
-      				break;
-      			case "NetStream.Publish.BadName" :
-      			  fail();
-      				break;
-      			case "NetStream.Connect.Closed" :
-      			  // we've disconnected from the peer
-      			  // can reset to accept another
-      			  // clear the publish stream and re-publish another
-      			  dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_DISCONNECTED, recvStream));
-      				break;
-      		} 
-        }
-        
-        private function onPeerConnect(peer:NetStream):Boolean
-        {
-          // establish a bidirectional stream with the peer
-          if (peerID == null) {
-            this.peer = peer.farID;
-          }
-          
-          // disallow additional peers connecting to us
-          if (peer.farID != peerID) return false;
-          
-          peer.send("setPeerConnectAcknowledged");
-          dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_CONNECTED, peer));
-          
-          return true;
-        }
-        
-        private function onPeerConnectAcknowledged(event:Event):void
-        {
-    			dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_SUCCESS, recvStream));
-        }
-        
-        private function onPeerConnectTimeout(peer:NetStream):void
-        {
-          if (!recvStream.client) return;
-    			if (!RTMFPSocketClient(recvStream.client).peerConnectAcknowledged) {
-    				dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_FAIL, recvStream));
-    			}
-        }
-        
-        private function onSecurityErrorEvent(event:SecurityErrorEvent):void
-        {
-          fail();
-        }
-        
-        private function onSendStreamEvent(event:NetStatusEvent):void
-        {
-    			switch (event.info.code) {
-    				case ("NetStream.Publish.Start") :
-    				  dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PUBLISH_START));
-    				  break;
-    				case ("NetStream.Play.Reset") :
-    				case ("NetStream.Play.Start") :
-    					break;
-    			}
-    		}
-    		private function onRecvStreamEvent(event:NetStatusEvent):void
-    		{
-    			switch (event.info.code) {
-    				case ("NetStream.Publish.Start") :
-    				case ("NetStream.Play.Reset") :
-    				case ("NetStream.Play.Start") :
-    					break;
-    			}
-    		}
-    }
-}
diff --git a/com/jscat/rtmfp/RTMFPSocketClient.as b/com/jscat/rtmfp/RTMFPSocketClient.as
deleted file mode 100644
index d9fcffa..0000000
--- a/com/jscat/rtmfp/RTMFPSocketClient.as
+++ /dev/null
@@ -1,57 +0,0 @@
-package rtmfp
-{
-	import flash.events.Event;
-	import flash.events.EventDispatcher;
-	import flash.events.ProgressEvent;
-	import flash.utils.ByteArray;
-
-	[Event(name="peerConnectAcknowledged", type="flash.events.Event")]
-	public dynamic class RTMFPSocketClient extends EventDispatcher {
-		public static const PEER_CONNECT_ACKNOWLEDGED:String = "peerConnectAcknowledged";
-		
-		private var _bytes:ByteArray;
-		private var _peerID:String;
-		private var _peerConnectAcknowledged:Boolean;
-		
-		public function RTMFPSocketClient()
-		{
-			super();
-			_bytes = new ByteArray();
-			_peerID = null;
-			_peerConnectAcknowledged = false;
-		}
-		
-		public function get bytes():ByteArray
-		{
-		  return _bytes;
-		}
-		
-		public function dataAvailable(bytes:ByteArray):void
-		{
-		  this._bytes.clear();
-		  bytes.readBytes(this._bytes);
-		  dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA, false, false, this._bytes.bytesAvailable, this._bytes.length));
-		}
-		
-		public function get peerConnectAcknowledged():Boolean
-		{
-		  return _peerConnectAcknowledged;
-		}
-		
-		public function setPeerConnectAcknowledged():void
-		{
-			_peerConnectAcknowledged = true;
-			dispatchEvent(new Event(PEER_CONNECT_ACKNOWLEDGED));
-		}
-		
-		public function get peerID():String
-		{
-		  return _peerID;
-		}
-		
-		public function set peerID(id:String):void
-		{
-		  _peerID = id;
-		}
-	}
-}
\ No newline at end of file
diff --git a/com/jscat/rtmfp/events/RTMFPSocketEvent.as b/com/jscat/rtmfp/events/RTMFPSocketEvent.as
deleted file mode 100644
index c5b4af1..0000000
--- a/com/jscat/rtmfp/events/RTMFPSocketEvent.as
+++ /dev/null
@@ -1,25 +0,0 @@
-package rtmfp.events
-{
-	import flash.events.Event;
-	import flash.net.NetStream;
-
-	public class RTMFPSocketEvent extends Event
-	{
-		public static const CONNECT_SUCCESS:String   = "connectSuccess";
-		public static const CONNECT_FAIL:String      = "connectFail";
-		public static const PUBLISH_START:String     = "publishStart";
-		public static const PEER_CONNECTED:String    = "peerConnected";
-		public static const PEER_DISCONNECTED:String = "peerDisconnected";
-		public static const PEERING_SUCCESS:String   = "peeringSuccess";
-		public static const PEERING_FAIL:String      = "peeringFail";
-		
-		public var stream:NetStream;
-		
-		public function RTMFPSocketEvent(type:String, streamVal:NetStream = null, bubbles:Boolean = false, cancelable:Boolean = false)
-		{
-			super(type, bubbles, cancelable);
-			stream = streamVal;
-		}
-		
-	}
-}
\ No newline at end of file
diff --git a/com/rtmfpcat/Makefile b/com/rtmfpcat/Makefile
new file mode 100644
index 0000000..fbcfc20
--- /dev/null
+++ b/com/rtmfpcat/Makefile
@@ -0,0 +1,11 @@
+MXMLC ?= mxmlc
+
+TARGETS = rtmfpcat.swf
+
+all: $(TARGETS)
+
+%.swf: %.as
+	$(MXMLC) -output $@ $^
+
+clean:
+	rm -f $(TARGETS)
diff --git a/com/rtmfpcat/README b/com/rtmfpcat/README
new file mode 100644
index 0000000..0432a63
--- /dev/null
+++ b/com/rtmfpcat/README
@@ -0,0 +1,118 @@
+== Introduction
+
+This is a set of tools that make it possible to connect Tor through an
+Adobe Flash proxy running on another computer. The Flash proxy can be
+run just by opening a web page in a computer that has Flash Player
+installed.
+
+This RTMFP version leverages the NAT-punching capabilities of Adobe's
+Cirrus server, making it possible for clients behind a NAT to still
+get access to Tor. The big operational difference between this version
+and the swfcat version is that now the client must maintain a rtmfpcat
+running in client mode open in his/her browser. The client's rtmfpcat
+talks to the rtmfpcat proxy running on another computer (location uncertain)
+via the UDP-based RTMFP to funnel data to a Tor relay/bridge.
+
+There are five main parts. Our terminology for each part is in quotes.
+1. The Tor "client," running on someone's localhost.
+2. A "connector," which waits for connections from the client's rtmfpcat and
+   the Tor client, and joins them together.
+3. A Flash "proxy," running in someone's web browser. This piece is
+   called rtmfpcat (totally ripped off of swfcat) because it is like a
+   netcat implemented in Flash. The rtmfpcat exists on both the local
+   and remote ends since it's the easiest way to take advantage of RTMFP.
+   We could get rid of the client-side rtmfpcat by making the connector
+   speak RTMFP, but that's too much work for now. 
+4. A "facilitator," a pseudo-HTTP server that keeps a list of clients
+   that want a connection, and hands them out to proxies.
+5. A Tor "relay," which is just a normal Tor relay except that its host
+   must also serve a Flash crossdomain policy.
+
+== Quick start
+
+Will put up a demo page soon.
+
+=== Building
+
+Download the (free software) Flex SDK.
+	http://opensource.adobe.com/wiki/display/flexsdk/Flex+SDK
+Put its bin directory in your PATH. The important executable is mxmlc.
+To build, run
+	$ make
+Copy the resulting rtmfpcat.swf file to a web server.
+
+On the computer that will be the facilitator, run
+     sudo ./crossdomaind.py
+	 ./facilitator.py
+crossdomaind.py needs to be run on any server that will accept
+connections from a Flash proxy. It serves a chunk of data on port 843.
+The facilitator runs on port 9002 by default. Note that this is a different
+facilitator script than the swfcat one, since this facilitator needs to
+deal with Cirrus client IDs instead of ip:port tuples.
+
+On the client, run
+	./connector.py
+This is a modified form of the swfcat connector.py that has different
+defaults, equivalent to passing 127.0.0.1:9001 for [LOCAL][:PORT] and
+127.0.0.1:3333 [REMOTE][:PORT] to the swfcat connector.
+
+Also on the client, open up the browser to rtmfpcat.swf. Passing no
+arguments should give you good defaults (expects the facilitator running
+on Nate's server). rtmfpcat will connect to the Cirrus server to
+obtain a client ID which it then registers with the facilitator.
+
+In a browser somewhere, open rtmfpcat.swf and pass the "?proxy=true" query
+string, telling it to operate in proxy mode.
+	http://www.example.com/rtmfpcat.swf?proxy=true
+This rtmfpcat will also connect to the Cirrus server to obtain a client ID,
+and then it will ping the facilitator to check if there are any registered
+client IDs. If there is one, it will open a RTMFP connection (coordinated
+by the Cirrus server) to the client and an additional connection to a 
+hardcoded Tor relay (David's bridge, nicknamed eRYaZuvY02FpExln).
+
+Back on the client, start Tor with the following configuration:
+	UseBridges 1
+	Bridge 127.0.0.1:9001
+	Socks4Proxy 127.0.0.1:9001
+
+You will be able to see byte counts flowing in both browsers displaying
+rtmfpcat.swf (client and proxy), and eventually be able to build a circuit.
+
+== Rationale
+
+The purpose of this project is to create many, generally ephemeral
+bridge IP addresses, with the goal of outpacing a censor's ability to
+block them. Rather than increasing the number of bridges at static
+addresses, we aim to make existing bridges reachable by a larger and
+changing pool of addresses.
+
+== Design notes
+
+The Tor relay address is hardcoded in rtmfpcat.as. It could be any relay,
+with the caveat that the server also has to serve a crossdomain policy.
+
+Client rtmfpcats register with the facilitator by sending an HTTP-like message:
+	POST / HTTP/1.0\r\n
+	\r\n
+	client=<CIRRUS-CLIENT-ID>
+
+The <CIRRUS-CLIENT-ID> is returned by Adobe's developer Cirrus server
+as soon as the rtfmpcat can connect to it. Each rtmfpcat needs to connect
+to a server like this to get one of these client IDs, since the Cirrus
+server uses these to coordinate RTMFP connections (including NAT punching).
+The need to communicate with a Cirrus server in addition to a facilitator is
+one of the major weaknesses of this design.
+
+The proxy rtmfpcat gets a client id using something like HTTP:
+	GET / HTTP/1.0\r\n
+	\r\n
+The server sends back an id specification (no HTTP header):
+    51ae8ed56c3705e4ad3755cdd3328c27720984778bfff71d9ec9f2647331d39b
+
+== ActionScript programming
+
+A good tutorial on ActionScript programming with the Flex tools, with
+sample code:
+
+http://www.senocular.com/flash/tutorials/as3withmxmlc/
+http://www.senocular.com/flash/tutorials/as3withmxmlc/AS3Flex2b3StarterFiles.zip
diff --git a/com/rtmfpcat/Utils.as b/com/rtmfpcat/Utils.as
new file mode 100644
index 0000000..48f9a62
--- /dev/null
+++ b/com/rtmfpcat/Utils.as
@@ -0,0 +1,25 @@
+package
+{
+  
+  public class Utils {
+    
+    /* Parse an address in the form "host:port". Returns an Object with
+       keys "host" (String) and "port" (int). Returns null on error. */
+    public static function parseAddrSpec(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;
+    }
+    
+  }
+  
+}
\ No newline at end of file
diff --git a/com/rtmfpcat/connector.py b/com/rtmfpcat/connector.py
new file mode 100755
index 0000000..63bbc5c
--- /dev/null
+++ b/com/rtmfpcat/connector.py
@@ -0,0 +1,326 @@
+#!/usr/bin/env python
+
+import getopt
+import httplib
+import re
+import select
+import socket
+import struct
+import sys
+import time
+import urllib
+import xml.sax.saxutils
+
+DEFAULT_REMOTE_ADDRESS = "127.0.0.1"
+DEFAULT_REMOTE_PORT = 3333
+DEFAULT_LOCAL_ADDRESS = "127.0.0.1"
+DEFAULT_LOCAL_PORT = 9001
+DEFAULT_FACILITATOR_PORT = 9002
+
+def usage(f = sys.stdout):
+    print >> f, """\
+Usage: %(progname)s -f FACILITATOR[:PORT] [LOCAL][:PORT] [REMOTE][:PORT]
+Wait for connections on a local and a remote port. When any pair of connections
+exists, data is ferried between them until one side is closed. By default
+LOCAL is "%(local)s" and REMOTE is "%(remote)s".
+
+The local connection acts as a SOCKS4a proxy, but the host and port in the SOCKS
+request are ignored and the local connection is always joined to a remote
+connection.
+
+If the -f option is given, then the REMOTE address is advertised to the given
+FACILITATOR.
+  -f, --facilitator=HOST[:PORT]  advertise willingness to receive connections to
+                                   HOST:PORT. By default PORT is %(fac_port)d.
+  -h, --help                     show this help.\
+""" % {
+    "progname": sys.argv[0],
+    "local": format_addr((DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT)),
+    "remote": format_addr((DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT)),
+    "fac_port": DEFAULT_FACILITATOR_PORT,
+}
+
+def parse_addr_spec(spec, defhost = None, defport = None):
+    host = None
+    port = None
+    m = None
+    # IPv6 syntax.
+    if not m:
+        m = re.match(r'^\[(.+)\]:(\d+)$', spec)
+        if m:
+            host, port = m.groups()
+            af = socket.AF_INET6
+    if not m:
+        m = re.match(r'^\[(.+)\]:?$', spec)
+        if m:
+            host, = m.groups()
+            af = socket.AF_INET6
+    # IPv4 syntax.
+    if not m:
+        m = re.match(r'^(.+):(\d+)$', spec)
+        if m:
+            host, port = m.groups()
+            af = socket.AF_INET
+    if not m:
+        m = re.match(r'^:?(\d+)$', spec)
+        if m:
+            port, = m.groups()
+            af = 0
+    if not m:
+        host = spec
+        af = 0
+    host = host or defhost
+    port = port or defport
+    if not (host and port):
+        raise ValueError("Bad address specification \"%s\"" % spec)
+    return host, int(port)
+
+def format_addr(addr):
+    host, port = addr
+    if not host:
+        return u":%d" % port
+    # Numeric IPv6 address?
+    try:
+        addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
+        af = addrs[0][0]
+    except socket.gaierror, e:
+        af = 0
+    if af == socket.AF_INET6:
+        return u"[%s]:%d" % (host, port)
+    else:
+        return u"%s:%d" % (host, port)
+
+facilitator_addr = None
+
+opts, args = getopt.gnu_getopt(sys.argv[1:], "f:h", ["facilitator", "help"])
+for o, a in opts:
+    if o == "-f" or o == "--facilitator":
+        facilitator_addr = parse_addr_spec(a, None, DEFAULT_FACILITATOR_PORT)
+    elif o == "-h" or o == "--help":
+        usage()
+        sys.exit()
+
+if len(args) == 0:
+    local_addr = (DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT)
+    remote_addr = (DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT)
+elif len(args) == 1:
+    local_addr = parse_addr_spec(args[0], DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT)
+    remote_addr = (DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT)
+elif len(args) == 2:
+    local_addr = parse_addr_spec(args[0], DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT)
+    remote_addr = parse_addr_spec(args[1], DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT)
+else:
+    usage(sys.stderr)
+    sys.exit(1)
+
+
+class RemotePending(object):
+    """A class encapsulating a socket and a time of connection."""
+    def __init__(self, fd):
+        self.fd = fd
+        self.birthday = time.time()
+
+    def fileno(self):
+        return self.fd.fileno()
+
+    def is_expired(self, timeout):
+        return time.time() - self.birthday > timeout
+
+def listen_socket(addr):
+    """Return a nonblocking socket listening on the given address."""
+    addrinfo = socket.getaddrinfo(addr[0], addr[1], 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)[0]
+    s = socket.socket(addrinfo[0], addrinfo[1], addrinfo[2])
+    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    s.bind(addr)
+    s.listen(10)
+    s.setblocking(0)
+    return s
+
+# How long to wait for a crossdomain policy request before deciding that this is
+# a normal socket.
+CROSSDOMAIN_TIMEOUT = 2.0
+
+# Local socket, accepting SOCKS requests from localhost
+local_s = listen_socket(local_addr)
+# Remote socket, accepting both crossdomain policy requests and remote proxy
+# connections.
+remote_s = listen_socket(remote_addr)
+
+# Sockets that may be crossdomain policy requests or may be normal remote
+# connections.
+crossdomain_pending = []
+# Remote connection sockets.
+remotes = []
+# New local sockets waiting to finish their SOCKS negotiation.
+socks_pending = []
+# Local Tor sockets, after SOCKS negotiation.
+locals = []
+
+# Bidirectional mapping between local sockets and remote sockets.
+local_for = {}
+remote_for = {}
+
+
+def handle_policy_request(fd):
+    print "handle_policy_request"
+    addr = fd.getpeername()
+    data = fd.recv(100)
+    if data == "<policy-file-request/>\0":
+        print "Sending crossdomain policy to %s." % format_addr(addr)
+        fd.sendall("""
+<cross-domain-policy>
+<allow-access-from domain="*" to-ports="%s"/>
+</cross-domain-policy>
+\0""" % xml.sax.saxutils.escape(str(remote_addr[1])))
+    elif data == "":
+        print "No data from %s." % format_addr(addr)
+    else:
+        print "Unexpected data from %s." % format_addr(addr)
+
+def grab_string(s, pos):
+    """Grab a NUL-terminated string from the given string, starting at the given
+    offset. Return (pos, str) tuple, or (pos, None) on error."""
+    i = pos
+    while i < len(s):
+        if s[i] == '\0':
+            return (i + 1, s[pos:i])
+        i += 1
+    return pos, None
+
+def parse_socks_request(data):
+    try:
+        ver, cmd, dport, o1, o2, o3, o4 = struct.unpack(">BBHBBBB", data[:8])
+    except struct.error:
+        print "Couldn't unpack SOCKS4 header."
+        return None
+    if ver != 4:
+        print "SOCKS header has wrong version (%d)." % ver
+        return None
+    if cmd != 1:
+        print "SOCKS header had wrong command (%d)." % cmd
+        return None
+    pos, userid = grab_string(data, 8)
+    if userid is None:
+        print "Couldn't read userid from SOCKS header."
+        return None
+    if o1 == 0 and o2 == 0 and o3 == 0 and o4 != 0:
+        pos, dest = grab_string(data, pos)
+        if dest is None:
+            print "Couldn't read destination from SOCKS4a header."
+            return None
+    else:
+        dest = "%d.%d.%d.%d" % (o1, o2, o3, o4)
+    return dest, dport
+
+def handle_socks_request(fd):
+    print "handle_socks_request"
+    addr = fd.getpeername()
+    data = fd.recv(100)
+    dest_addr = parse_socks_request(data)
+    if dest_addr is None:
+        # Error reply.
+        fd.sendall(struct.pack(">BBHBBBB", 0, 91, 0, 0, 0, 0, 0))
+        return False
+    print "Got SOCKS request for %s." % format_addr(dest_addr)
+    fd.sendall(struct.pack(">BBHBBBB", 0, 90, dest_addr[1], 127, 0, 0, 1))
+    # Note we throw away the requested address and port.
+    return True
+
+def handle_remote_connection(fd):
+    print "handle_remote_connection"
+    match_proxies()
+
+def handle_local_connection(fd):
+    print "handle_local_connection"
+    if facilitator_addr:
+        register(facilitator_addr, remote_addr[1])
+    match_proxies()
+
+def register(addr, port):
+    spec = format_addr((None, port))
+    print "Registering \"%s\" with %s." % (spec, format_addr(addr))
+    http = httplib.HTTPConnection(*addr)
+    http.request("POST", "/", urllib.urlencode({"client": spec}))
+    http.close()
+
+def match_proxies():
+    while locals and remotes:
+        remote = remotes.pop(0)
+        local = locals.pop(0)
+        remote_addr, remote_port = remote.getpeername()
+        local_addr, local_port = local.getpeername()
+        print "Linking %s and %s." % (format_addr(local.getpeername()), format_addr(remote.getpeername()))
+        remote_for[local] = remote
+        local_for[remote] = local
+
+if facilitator_addr:
+    register(facilitator_addr, remote_addr[1])
+
+while True:
+    rset = [remote_s, local_s] + crossdomain_pending + socks_pending + remote_for.keys() + local_for.keys() + remotes
+    rset, _, _ = select.select(rset, [], [], CROSSDOMAIN_TIMEOUT)
+    for fd in rset:
+        if fd == remote_s:
+            remote_c, addr = fd.accept()
+            print "Remote connection from %s." % format_addr(addr)
+            crossdomain_pending.append(RemotePending(remote_c))
+        elif fd == local_s:
+            local_c, addr = fd.accept()
+            print "Local connection from %s." % format_addr(addr)
+            socks_pending.append(local_c)
+            if facilitator_addr:
+                register(facilitator_addr, remote_addr[1])
+        elif fd in crossdomain_pending:
+            print "Data from crossdomain-pending %s." % format_addr(addr)
+            handle_policy_request(fd.fd)
+            fd.fd.close()
+            crossdomain_pending.remove(fd)
+        elif fd in socks_pending:
+            print "SOCKS request from %s." % format_addr(addr)
+            if handle_socks_request(fd):
+                locals.append(fd)
+                handle_local_connection(fd)
+            else:
+                fd.close()
+            socks_pending.remove(fd)
+        elif fd in local_for:
+            local = local_for[fd]
+            data = fd.recv(1024)
+            if not data:
+                print "EOF from remote %s." % format_addr(fd.getpeername())
+                fd.close()
+                local.close()
+                del local_for[fd]
+                del remote_for[local]
+                if facilitator_addr:
+                    register(facilitator_addr, remote_addr[1])
+            else:
+                local.sendall(data)
+        elif fd in remote_for:
+            remote = remote_for[fd]
+            data = fd.recv(1024)
+            if not data:
+                print "EOF from local %s." % format_addr(fd.getpeername())
+                fd.close()
+                remote.close()
+                del remote_for[fd]
+                del local_for[remote]
+            else:
+                remote.sendall(data)
+        elif fd in remotes:
+            data = fd.recv(1024)
+            if not data:
+                print "EOF from unconnected remote %s." % format_addr(fd.getpeername())
+            else:
+                print "Data from unconnected remote %s." % format_addr(fd.getpeername())
+            fd.close()
+            remotes.remove(fd)
+        match_proxies()
+    while crossdomain_pending:
+        pending = crossdomain_pending[0]
+        if not pending.is_expired(CROSSDOMAIN_TIMEOUT):
+            break
+        print "Expired pending crossdomain from %s." % format_addr(pending.fd.getpeername())
+        crossdomain_pending.pop(0)
+        remotes.append(pending.fd)
+        handle_remote_connection(pending.fd)
diff --git a/com/rtmfpcat/facilitator.py b/com/rtmfpcat/facilitator.py
new file mode 100755
index 0000000..8c6a44e
--- /dev/null
+++ b/com/rtmfpcat/facilitator.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+
+import BaseHTTPServer
+import getopt
+import cgi
+import re
+import sys
+import socket
+from collections import deque
+
+DEFAULT_ADDRESS = "0.0.0.0"
+DEFAULT_PORT = 9002
+
+def usage(f = sys.stdout):
+	print >> f, """\
+Usage: %(progname)s <OPTIONS> [HOST] [PORT]
+Flash bridge facilitator: Register client addresses with HTTP POST requests
+and serve them out again with HTTP GET. Listen on HOST and PORT, by default
+%(addr)s %(port)d.
+  -h, --help		   show this help.\
+""" % {
+	"progname": sys.argv[0],
+	"addr": DEFAULT_ADDRESS,
+	"port": DEFAULT_PORT,
+}
+
+REGS = deque()
+
+class Reg(object):
+	def __init__(self, id):
+		self.id = id
+
+	def __unicode__(self):
+	  return u"%s" % (self.id)
+
+	def __str__(self):
+		return unicode(self).encode("UTF-8")
+
+	def __cmp__(self, other):
+		return cmp((self.id), (other.id))
+
+	@staticmethod
+	def parse(spec, defhost = None, defport = None):
+		host = None
+		port = None
+		m = re.match(r'^\[(.+)\]:(\d*)$', spec)
+		if m:
+			host, port = m.groups()
+			af = socket.AF_INET6
+		else:
+			m = re.match(r'^(.*):(\d*)$', spec)
+			if m:
+				host, port = m.groups()
+				if host:
+					af = socket.AF_INET
+				else:
+					# Has to be guessed from format of defhost.
+					af = 0
+		host = host or defhost
+		port = port or defport
+		if not (host and port):
+			raise ValueError("Bad address specification \"%s\"" % spec)
+
+		try:
+			addrs = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
+		except socket.gaierror, e:
+			raise ValueError("Bad host or port: \"%s\" \"%s\": %s" % (host, port, str(e)))
+		if not addrs:
+			raise ValueError("Bad host or port: \"%s\" \"%s\"" % (host, port))
+
+		af = addrs[0][0]
+		host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
+		return Reg(af, host, int(port))
+
+def fetch_reg():
+	"""Get a client registration, or None if none is available."""
+	if not REGS:
+		return None
+	return REGS.popleft()
+
+class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
+	def do_GET(self):
+		print "From " + str(self.client_address) + " received: GET:",
+		reg = fetch_reg()
+		if reg:
+			print "Handing out " + str(reg) + ". Clients: " + str(len(REGS))
+			self.request.send(str(reg))
+		else:
+			print "Registration list is empty"
+			self.request.send("Registration list empty")
+
+	def do_POST(self):
+		print "From " + str(self.client_address) + " received: POST:",
+		data = self.rfile.readline().strip()
+		print data + " :",
+		try:
+			vals = cgi.parse_qs(data, False, True)
+		except ValueError, e:
+			print "Syntax error in POST:", str(e)
+			return
+
+		client_specs = vals.get("client")
+		if client_specs is None or len(client_specs) != 1:
+			print "In POST: need exactly one \"client\" param"
+			return
+		val = client_specs[0]
+
+		try:
+			reg = Reg(val)
+		except ValueError, e:
+			print "Can't parse client \"%s\": %s" % (val, str(e))
+			return
+
+		if reg not in list(REGS):
+			REGS.append(reg)
+			print "Registration " + str(reg) + " added. Registrations: " + str(len(REGS))
+		else:
+			print "Registration " + str(reg) + " already present. Registrations: " + str(len(REGS))
+
+opts, args = getopt.gnu_getopt(sys.argv[1:], "h", ["help"])
+for o, a in opts:
+	if o == "-h" or o == "--help":
+		usage()
+		sys.exit()
+
+if len(args) == 0:
+	address = (DEFAULT_ADDRESS, DEFAULT_PORT)
+elif len(args) == 1:
+	# Either HOST or PORT may be omitted; figure out which one.
+	if args[0].isdigit():
+		address = (DEFAULT_ADDRESS, args[0])
+	else:
+		address = (args[0], DEFAULT_PORT)
+elif len(args) == 2:
+	address = (args[0], args[1])
+else:
+	usage(sys.stderr)
+	sys.exit(1)
+
+# Setup the server
+server = BaseHTTPServer.HTTPServer(address, Handler)
+
+print "Starting Facilitator on " + str(address) + "..."
+
+# Run server... Single threaded serving of requests...
+server.serve_forever()
diff --git a/com/rtmfpcat/rtmfp/RTMFPSocket.as b/com/rtmfpcat/rtmfp/RTMFPSocket.as
new file mode 100644
index 0000000..4b83784
--- /dev/null
+++ b/com/rtmfpcat/rtmfp/RTMFPSocket.as
@@ -0,0 +1,216 @@
+package rtmfp
+{
+    import flash.events.Event;
+    import flash.events.EventDispatcher;
+    import flash.events.IOErrorEvent;
+    import flash.events.NetStatusEvent;
+    import flash.events.ProgressEvent;
+    import flash.events.SecurityErrorEvent;
+    import flash.net.NetConnection;
+    import flash.net.NetStream;
+    import flash.utils.ByteArray;
+    import flash.utils.clearInterval;
+    import flash.utils.setInterval;
+    import flash.utils.setTimeout;
+    
+    import rtmfp.RTMFPSocketClient;
+    import rtmfp.events.RTMFPSocketEvent;
+
+    [Event(name="connectSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+  	[Event(name="connectFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+  	[Event(name="publishStart", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+  	[Event(name="peerConnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+  	[Event(name="peeringSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+  	[Event(name="peeringFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+  	[Event(name="peerDisconnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")]
+    public class RTMFPSocket extends EventDispatcher
+    {
+        /* The name of the "media" to pass between peers */
+        private static const DATA:String = "data";
+		    private static const DEFAULT_CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net";
+		    private static const DEFAULT_CIRRUS_KEY:String = RTMFP::CIRRUS_KEY;
+		    private static const DEFAULT_CONNECT_TIMEOUT:uint = 4000;
+		
+        /* Connection to the Cirrus rendezvous service */
+        private var connection:NetConnection;
+		
+		    /* ID of the peer to connect to */
+		    private var peerID:String;
+		
+		    /* Data streams to be established with peer */
+		    private var sendStream:NetStream;
+		    private var recvStream:NetStream;
+		    
+		    /* Timeouts */
+		    private var connectionTimeout:int;
+		    private var peerConnectTimeout:uint;
+
+        public function RTMFPSocket(){}
+        
+        public function connect(addr:String = DEFAULT_CIRRUS_ADDRESS, key:String = DEFAULT_CIRRUS_KEY):void
+        {
+          connection = new NetConnection();
+			    connection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatusEvent);
+			    connection.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEvent);
+			    connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
+          connection.connect(addr + "/" + key);
+			    connectionTimeout = setInterval(fail, DEFAULT_CONNECT_TIMEOUT);
+        }
+
+        public function get id():String
+        {
+          if (connection != null && connection.connected) {
+            return connection.nearID;
+          }
+          
+          return null;
+        }
+        
+        public function get connected():Boolean
+        {
+          return (connection != null && connection.connected);
+        }
+        
+        public function readBytes(bytes:ByteArray):void
+        {
+          recvStream.client.bytes.readBytes(bytes);
+        }
+
+        public function writeBytes(bytes:ByteArray):void
+        {
+          sendStream.send("dataAvailable", bytes);
+        }
+        
+        public function get peer():String
+        {
+          return this.peerID;
+        }
+
+        public function set peer(peerID:String):void
+        {
+          if (peerID == null || peerID.length == 0) {
+            throw new Error("Peer ID is null/empty.")
+    			} else if (peerID == connection.nearID) {
+    				throw new Error("Peer ID cannot be the same as our ID.");
+    			} else if (this.peerID == peerID) {
+    			  throw new Error("Already connected to peer " + peerID + ".");
+    			} else if (this.recvStream != null) {
+            throw new Error("Cannot connect to a second peer.");
+          }
+          
+          this.peerID = peerID;
+
+    			recvStream = new NetStream(connection, peerID);
+    			var client:RTMFPSocketClient = new RTMFPSocketClient();
+    			client.addEventListener(ProgressEvent.SOCKET_DATA, onDataAvailable, false, 0, true);
+    			client.addEventListener(RTMFPSocketClient.PEER_CONNECT_ACKNOWLEDGED, onPeerConnectAcknowledged, false, 0, true);
+    			recvStream.client = client;
+    			recvStream.addEventListener(NetStatusEvent.NET_STATUS, onRecvStreamEvent);
+    			recvStream.play(DATA);
+    			setTimeout(onPeerConnectTimeout, peerConnectTimeout, recvStream);
+        }
+        
+        private function startPublishStream():void
+        {
+          sendStream = new NetStream(connection, NetStream.DIRECT_CONNECTIONS);
+    			sendStream.addEventListener(NetStatusEvent.NET_STATUS, onSendStreamEvent);
+    			var o:Object = new Object();
+    			o.onPeerConnect = onPeerConnect;
+    			sendStream.client = o;
+    			sendStream.publish(DATA);
+        }
+        
+        private function fail():void
+        {
+          clearInterval(connectionTimeout);
+    			dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_FAIL));
+        }
+        
+        private function onDataAvailable(event:ProgressEvent):void
+        {
+          dispatchEvent(event);
+        }
+        
+        private function onIOErrorEvent(event:IOErrorEvent):void
+        {
+          fail();
+        }
+        
+        private function onNetStatusEvent(event:NetStatusEvent):void
+        {
+          switch (event.info.code) {
+      			case "NetConnection.Connect.Success" :
+      				clearInterval(connectionTimeout);
+      				startPublishStream();
+      				dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_SUCCESS));
+      				break;
+      			case "NetStream.Connect.Success" :
+      				break;
+      			case "NetStream.Publish.BadName" :
+      			  fail();
+      				break;
+      			case "NetStream.Connect.Closed" :
+      			  // we've disconnected from the peer
+      			  // can reset to accept another
+      			  // clear the publish stream and re-publish another
+      			  dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_DISCONNECTED, recvStream));
+      				break;
+      		} 
+        }
+        
+        private function onPeerConnect(peer:NetStream):Boolean
+        {
+          // establish a bidirectional stream with the peer
+          if (peerID == null) {
+            this.peer = peer.farID;
+          }
+          
+          // disallow additional peers connecting to us
+          if (peer.farID != peerID) return false;
+          
+          peer.send("setPeerConnectAcknowledged");
+          dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_CONNECTED, peer));
+          
+          return true;
+        }
+        
+        private function onPeerConnectAcknowledged(event:Event):void
+        {
+    			dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_SUCCESS, recvStream));
+        }
+        
+        private function onPeerConnectTimeout(peer:NetStream):void
+        {
+          if (!recvStream.client) return;
+    			if (!RTMFPSocketClient(recvStream.client).peerConnectAcknowledged) {
+    				dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_FAIL, recvStream));
+    			}
+        }
+        
+        private function onSecurityErrorEvent(event:SecurityErrorEvent):void
+        {
+          fail();
+        }
+        
+        private function onSendStreamEvent(event:NetStatusEvent):void
+        {
+    			switch (event.info.code) {
+    				case ("NetStream.Publish.Start") :
+    				  dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PUBLISH_START));
+    				  break;
+    				case ("NetStream.Play.Reset") :
+    				case ("NetStream.Play.Start") :
+    					break;
+    			}
+    		}
+    		private function onRecvStreamEvent(event:NetStatusEvent):void
+    		{
+    			switch (event.info.code) {
+    				case ("NetStream.Publish.Start") :
+    				case ("NetStream.Play.Reset") :
+    				case ("NetStream.Play.Start") :
+    					break;
+    			}
+    		}
+    }
+}
diff --git a/com/rtmfpcat/rtmfp/RTMFPSocketClient.as b/com/rtmfpcat/rtmfp/RTMFPSocketClient.as
new file mode 100644
index 0000000..d9fcffa
--- /dev/null
+++ b/com/rtmfpcat/rtmfp/RTMFPSocketClient.as
@@ -0,0 +1,57 @@
+package rtmfp
+{
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.events.ProgressEvent;
+	import flash.utils.ByteArray;
+
+	[Event(name="peerConnectAcknowledged", type="flash.events.Event")]
+	public dynamic class RTMFPSocketClient extends EventDispatcher {
+		public static const PEER_CONNECT_ACKNOWLEDGED:String = "peerConnectAcknowledged";
+		
+		private var _bytes:ByteArray;
+		private var _peerID:String;
+		private var _peerConnectAcknowledged:Boolean;
+		
+		public function RTMFPSocketClient()
+		{
+			super();
+			_bytes = new ByteArray();
+			_peerID = null;
+			_peerConnectAcknowledged = false;
+		}
+		
+		public function get bytes():ByteArray
+		{
+		  return _bytes;
+		}
+		
+		public function dataAvailable(bytes:ByteArray):void
+		{
+		  this._bytes.clear();
+		  bytes.readBytes(this._bytes);
+		  dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA, false, false, this._bytes.bytesAvailable, this._bytes.length));
+		}
+		
+		public function get peerConnectAcknowledged():Boolean
+		{
+		  return _peerConnectAcknowledged;
+		}
+		
+		public function setPeerConnectAcknowledged():void
+		{
+			_peerConnectAcknowledged = true;
+			dispatchEvent(new Event(PEER_CONNECT_ACKNOWLEDGED));
+		}
+		
+		public function get peerID():String
+		{
+		  return _peerID;
+		}
+		
+		public function set peerID(id:String):void
+		{
+		  _peerID = id;
+		}
+	}
+}
\ No newline at end of file
diff --git a/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as b/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as
new file mode 100644
index 0000000..c5b4af1
--- /dev/null
+++ b/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as
@@ -0,0 +1,25 @@
+package rtmfp.events
+{
+	import flash.events.Event;
+	import flash.net.NetStream;
+
+	public class RTMFPSocketEvent extends Event
+	{
+		public static const CONNECT_SUCCESS:String   = "connectSuccess";
+		public static const CONNECT_FAIL:String      = "connectFail";
+		public static const PUBLISH_START:String     = "publishStart";
+		public static const PEER_CONNECTED:String    = "peerConnected";
+		public static const PEER_DISCONNECTED:String = "peerDisconnected";
+		public static const PEERING_SUCCESS:String   = "peeringSuccess";
+		public static const PEERING_FAIL:String      = "peeringFail";
+		
+		public var stream:NetStream;
+		
+		public function RTMFPSocketEvent(type:String, streamVal:NetStream = null, bubbles:Boolean = false, cancelable:Boolean = false)
+		{
+			super(type, bubbles, cancelable);
+			stream = streamVal;
+		}
+		
+	}
+}
\ No newline at end of file
diff --git a/com/rtmfpcat/rtmfpcat.as b/com/rtmfpcat/rtmfpcat.as
new file mode 100644
index 0000000..a2e3e61
--- /dev/null
+++ b/com/rtmfpcat/rtmfpcat.as
@@ -0,0 +1,208 @@
+package
+{
+    import flash.display.Sprite;
+    import flash.text.TextField;
+    import flash.net.Socket;
+    import flash.events.Event;
+    import flash.events.EventDispatcher;
+    import flash.events.IOErrorEvent;
+    import flash.events.NetStatusEvent;
+    import flash.events.ProgressEvent;
+    import flash.events.SecurityErrorEvent;
+    import flash.utils.ByteArray;
+    import flash.utils.setTimeout;
+    
+    import rtmfp.RTMFPSocket;
+    import rtmfp.events.RTMFPSocketEvent;
+    import Utils;
+    
+    public class rtmfpcat extends Sprite {
+      
+      /* Nate's facilitator -- also serving a crossdomain policy */
+      private const DEFAULT_FAC_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 bridge (nickname eRYaZuvY02FpExln) that also serves a
+         crossdomain policy. */
+      private const DEFAULT_TOR_PROXY_ADDR:Object = {
+        host: "69.164.193.231",
+        port: 9001
+      };
+      
+      // Milliseconds.
+      private const FACILITATOR_POLL_INTERVAL:int = 10000;
+      
+      private var output_text:TextField;
+      
+      private var s_f:Socket;
+      private var s_r:RTMFPSocket;
+      private var s_t:Socket;
+      
+      private var fac_addr:Object;
+      private var tor_addr:Object;
+      
+      private var proxy_mode:Boolean;
+      
+      public function rtmfpcat()
+      {
+        output_text = new TextField();
+        output_text.width = 400;
+        output_text.height = 300;
+        output_text.background = true;
+        output_text.backgroundColor = 0x001f0f;
+        output_text.textColor = 0x44CC44;
+        addChild(output_text);
+
+        puts("Starting.");
+        
+        this.loaderInfo.addEventListener(Event.COMPLETE, onLoaderInfoComplete);
+      }
+      
+      protected function puts(s:String):void
+      {
+          output_text.appendText(s + "\n");
+          output_text.scrollV = output_text.maxScrollV;
+      }
+      
+      private function onLoaderInfoComplete(e:Event):void
+      {
+        var fac_spec:String;
+        var tor_spec:String;
+
+        puts("Parameters loaded.");
+        
+        proxy_mode = (this.loaderInfo.parameters["proxy"] != null);
+        
+        fac_spec = this.loaderInfo.parameters["facilitator"];
+        if (!fac_spec) {
+          puts("No \"facilitator\" specification provided...using default.");
+          fac_addr = DEFAULT_FAC_ADDR;
+        } else {
+          puts("Facilitator spec: \"" + fac_spec + "\"");
+          fac_addr = Utils.parseAddrSpec(fac_spec);
+        }
+        
+        if (!fac_addr) {
+            puts("Error: Facilitator spec must be in the form \"host:port\".");
+            return;
+        }
+        
+        tor_spec = this.loaderInfo.parameters["tor"];
+        if (!tor_spec) {
+          puts("No Tor specification provided...using default.");
+          if (proxy_mode) tor_addr = DEFAULT_TOR_PROXY_ADDR;
+          else tor_addr = DEFAULT_TOR_CLIENT_ADDR;
+        } else {
+          puts("Tor spec: \"" + tor_spec + "\"")
+          tor_addr = Utils.parseAddrSpec(tor_spec);
+        }
+
+        if (!tor_addr) {
+          puts("Error: Tor spec must be in the form \"host:port\".");
+          return;
+        }
+
+        establishRTMFPConnection();
+      }
+      
+      private function establishRTMFPConnection():void
+      {
+        s_r = new RTMFPSocket();
+        s_r.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, function (e:Event):void {
+          puts("Cirrus: connected with id " + s_r.id + ".");
+          establishFacilitatorConnection();
+        });
+        s_r.addEventListener(RTMFPSocketEvent.CONNECT_FAIL, function (e:Event):void {
+          puts("Error: failed to connect to Cirrus.");
+        });
+        s_r.addEventListener(RTMFPSocketEvent.PUBLISH_START, function(e:RTMFPSocketEvent):void {
+          puts("Publishing started.");
+        });
+        s_r.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function(e:RTMFPSocketEvent):void {
+          puts("Peer connected.");
+        });
+        s_r.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function(e:RTMFPSocketEvent):void {
+          puts("Peer disconnected.");
+        });
+        s_r.addEventListener(RTMFPSocketEvent.PEERING_SUCCESS, function(e:RTMFPSocketEvent):void {
+          puts("Peering success.");
+          establishTorConnection();
+        });
+        s_r.addEventListener(RTMFPSocketEvent.PEERING_FAIL, function(e:RTMFPSocketEvent):void {
+          puts("Peering fail.");
+        });
+        s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
+          var bytes:ByteArray = new ByteArray();
+          s_r.readBytes(bytes);
+          puts("RTMFP: read " + bytes.length + " bytes.");
+          s_t.writeBytes(bytes);
+        });
+        
+        s_r.connect();
+      }
+      
+      private function establishTorConnection():void
+      {
+        s_t = new Socket();
+        s_t.addEventListener(Event.CONNECT, function (e:Event):void {
+          puts("Tor: connected to " + tor_addr.host + ":" + tor_addr.port + ".");
+        });
+        s_t.addEventListener(Event.CLOSE, function (e:Event):void {
+          puts("Tor: closed connection.");
+        });
+        s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
+          puts("Tor: I/O error: " + e.text + ".");
+        });
+        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 + " bytes.");
+          s_r.writeBytes(bytes);
+        });
+        s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
+          puts("Tor: security error: " + e.text + ".");
+        });
+
+        s_t.connect(tor_addr.host, tor_addr.port);
+      }
+      
+      private function establishFacilitatorConnection():void
+      {
+        s_f = new Socket();
+        s_f.addEventListener(Event.CONNECT, function (e:Event):void {
+          puts("Facilitator: connected to " + fac_addr.host + ":" + fac_addr.port + ".");
+          if (proxy_mode) s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
+          else s_f.writeUTFBytes("POST / HTTP/1.0\r\n\r\nclient=" + s_r.id + "\r\n");
+        });
+        s_f.addEventListener(Event.CLOSE, function (e:Event):void {
+          puts("Facilitator: connection closed.");
+          if (proxy_mode) {
+            setTimeout(establishFacilitatorConnection, FACILITATOR_POLL_INTERVAL);
+          }
+        });
+        s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
+          puts("Facilitator: I/O error: " + e.text + ".");
+        });
+        s_f.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
+          var clientID:String = s_f.readMultiByte(e.bytesLoaded, "utf-8");
+          puts("Facilitator: got \"" + clientID + "\"");
+          if (clientID != "Registration list empty") {
+            puts("Connecting to " + clientID + ".");
+            s_r.peer = clientID;
+          }
+        });
+        s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
+          puts("Facilitator: security error: " + e.text + ".");
+        });
+
+        s_f.connect(fac_addr.host, fac_addr.port);
+      }
+  }
+}
\ No newline at end of file





More information about the tor-commits mailing list