[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