package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        private const RTMFP_URL:String = "rtmfp://tor-facilitator.bamsoftware.com";

        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "tor-facilitator.bamsoftware.com",
            port: 9002
        };

        /* Local Tor client to use in case of RTMFP connection. */
        private const DEFAULT_LOCAL_TOR_CLIENT_ADDR:Object = {
            host: "127.0.0.1",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 10;

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 1000;

        // Bytes per second. Set to undefined to disable limit.
        public const RATE_LIMIT:Number = undefined;
        // Seconds.
        private const RATE_LIMIT_HISTORY:Number = 5.0;

        /* TextField for debug output. */
        private var output_text:TextField;

        /* UI shown when debug is off. */
        private var badge:Badge;

        /* Number of proxy pairs currently connected (up to
           MAX_NUM_PROXY_PAIRS). */
        private var num_proxy_pairs:int = 0;

        private var fac_addr:Object;

        private var local_addr:Object;

        public var rate_limit:RateLimit;

        public function puts(s:String):void
        {
            if (output_text) {
                output_text.appendText(s + "\n");
                output_text.scrollV = output_text.maxScrollV;
            }
        }

        public function swfcat()
        {
            // Absolute positioning.
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            badge = new Badge();

            if (RATE_LIMIT)
                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
            else
                rate_limit = new RateUnlimit();

            // 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;

            if (this.loaderInfo.parameters["debug"] || this.loaderInfo.parameters["client"]) {
                output_text = new TextField();
                output_text.width = stage.stageWidth;
                output_text.height = stage.stageHeight;
                output_text.background = true;
                output_text.backgroundColor = 0x001f0f;
                output_text.textColor = 0x44cc44;
                addChild(output_text);
            } else {
                addChild(badge);
            }

            puts("Parameters loaded.");

            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            if (this.loaderInfo.parameters["local"])
                local_addr = get_param_addr("local", DEFAULT_LOCAL_TOR_CLIENT_ADDR);
            else
                local_addr = DEFAULT_LOCAL_TOR_CLIENT_ADDR;

            if (this.loaderInfo.parameters["client"])
                client_main();
            else
                proxy_main();
        }

        /* Get an address structure from the given movie parameter, or the given
           default. Returns null on error. */
        private function get_param_addr(param:String, default_addr:Object):Object
        {
            var spec:String, addr:Object;

            spec = this.loaderInfo.parameters[param];
            if (spec)
                return parse_addr_spec(spec);
            else
                return default_addr;
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function proxy_main():void
        {
            var fac_url:String;
            var loader:URLLoader;

            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            loader = new URLLoader();
            /* Get the x-www-form-urlencoded values. */
            loader.dataFormat = URLLoaderDataFormat.VARIABLES;
            loader.addEventListener(Event.COMPLETE, fac_complete);
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(new URLRequest(fac_url));
        }

        private function fac_complete(e:Event):void
        {
            var loader:URLLoader;
            var client_spec:String;
            var relay_spec:String;
            var proxy_pair:Object;

            setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);

            loader = e.target as URLLoader;
            client_spec = loader.data.client;
            if (client_spec == "") {
                puts("No clients.");
                return;
            } else if (!client_spec) {
                puts("Error: missing \"client\" in response.");
                return;
            }
            relay_spec = loader.data.relay;
            if (!relay_spec) {
                puts("Error: missing \"relay\" in response.");
                return;
            }
            puts("Facilitator: got client:\"" + client_spec + "\" "
                + "relay:\"" + relay_spec + "\".");

            try {
                proxy_pair = make_proxy_pair(client_spec, relay_spec);
            } catch (e:ArgumentError) {
                puts("Error: " + e);
                return;
            }
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
                badge.proxy_end();
            });
            proxy_pair.connect();

            num_proxy_pairs++;
            badge.proxy_begin();
        }

        private function client_main():void
        {
            var rs:RTMFPSocket;

            rs = new RTMFPSocket(RTMFP_URL);
            rs.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Got RTMFP id " + rs.id);
                register(rs);
            });
            rs.addEventListener(RTMFPSocket.ACCEPT_EVENT, client_accept);

            rs.listen();
        }

        private function client_accept(e:Event):void {
            var rs:RTMFPSocket;
            var s_t:Socket;
            var proxy_pair:ProxyPair;

            rs = e.target as RTMFPSocket;
            s_t = new Socket();

            puts("Got RTMFP connection from " + rs.peer_id);

            proxy_pair = new ProxyPair(this, rs, function ():void {
                /* Do nothing; already connected. */
            }, s_t, function ():void {
                s_t.connect(local_addr.host, local_addr.port);
            });
            proxy_pair.connect();
        }

        private function register(rs:RTMFPSocket):void {
            var fac_url:String;
            var loader:URLLoader;
            var request:URLRequest;
            var variables:URLVariables;

            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Facilitator: registered.");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
                rs.close();
            });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
                rs.close();
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            request = new URLRequest(fac_url);
            request.method = URLRequestMethod.POST;
            request.data = new URLVariables;
            request.data["client"] = rs.id;

            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(request);
        }

        private function make_proxy_pair(client_spec:String, relay_spec:String):ProxyPair
        {
            var proxy_pair:ProxyPair;
            var addr_c:Object;
            var addr_r:Object;
            var s_c:*;
            var s_r:Socket;

            addr_r = swfcat.parse_addr_spec(relay_spec);
            if (!addr_r)
                throw new ArgumentError("Relay spec must be in the form \"host:port\".");

            addr_c = swfcat.parse_addr_spec(client_spec);
            if (addr_c) {
                s_c = new Socket();
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(addr_c.host, addr_c.port);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + addr_c.host + ":" + addr_c.port + ","
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            if (client_spec.match(/^[0-9A-Fa-f]{64}$/)) {
                s_c = new RTMFPSocket(RTMFP_URL);
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(client_spec);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + client_spec.substr(0, 4) + "...,"
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            throw new ArgumentError("Can't parse client spec \"" + client_spec + "\".");
        }

        /* 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;
        }
    }
}

import flash.text.TextFormat;
import flash.text.TextField;
import flash.utils.getTimer;

class Badge extends flash.display.Sprite
{
    /* Number of proxy pairs currently connected. */
    private var num_proxy_pairs:int = 0;
    /* Number of proxy pairs ever connected. */
    private var total_proxy_pairs:int = 0;

    [Embed(source="badge.png")]
    private var BadgeImage:Class;
    private var tot_client_count_tf:TextField;
    private var tot_client_count_fmt:TextFormat;
    private var cur_client_count_tf:TextField;
    private var cur_client_count_fmt:TextFormat;

    public function Badge()
    {
        /* Setup client counter for badge. */
        tot_client_count_fmt = new TextFormat();
        tot_client_count_fmt.color = 0xFFFFFF;
        tot_client_count_fmt.align = "center";
        tot_client_count_fmt.font = "courier-new";
        tot_client_count_fmt.bold = true;
        tot_client_count_fmt.size = 10;
        tot_client_count_tf = new TextField();
        tot_client_count_tf.width = 20;
        tot_client_count_tf.height = 17;
        tot_client_count_tf.background = false;
        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
        tot_client_count_tf.x=47;
        tot_client_count_tf.y=0;

        cur_client_count_fmt = new TextFormat();
        cur_client_count_fmt.color = 0xFFFFFF;
        cur_client_count_fmt.align = "center";
        cur_client_count_fmt.font = "courier-new";
        cur_client_count_fmt.bold = true;
        cur_client_count_fmt.size = 10;
        cur_client_count_tf = new TextField();
        cur_client_count_tf.width = 20;
        cur_client_count_tf.height = 17;
        cur_client_count_tf.background = false;
        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
        cur_client_count_tf.x=47;
        cur_client_count_tf.y=6;

        addChild(new BadgeImage());
        addChild(tot_client_count_tf);
        addChild(cur_client_count_tf);

        /* Update the client counter on badge. */
        update_client_count();
    }

    public function proxy_begin():void
    {
        num_proxy_pairs++;
        total_proxy_pairs++;
        update_client_count();
    }

    public function proxy_end():void
    {
        num_proxy_pairs--;
        update_client_count();
    }

    private function update_client_count():void
    {
        /* Update total client count. */
        if (String(total_proxy_pairs).length == 1)
            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
        else
            tot_client_count_tf.text = String(total_proxy_pairs);

        /* Update current client count. */
        cur_client_count_tf.text = "";
        for(var i:Number = 0; i < num_proxy_pairs; i++)
            cur_client_count_tf.appendText(".");
    }
}

class RateLimit
{
    public function RateLimit()
    {
    }

    public function update(n:Number):Boolean
    {
        return true;
    }

    public function when():Number
    {
        return 0.0;
    }

    public function is_limited():Boolean
    {
        return false;
    }
}

class RateUnlimit extends RateLimit
{
    public function RateUnlimit()
    {
    }

    public override function update(n:Number):Boolean
    {
        return true;
    }

    public override function when():Number
    {
        return 0.0;
    }

    public override function is_limited():Boolean
    {
        return false;
    }
}

class BucketRateLimit extends RateLimit
{
    private var amount:Number;
    private var capacity:Number;
    private var time:Number;
    private var last_update:uint;

    public function BucketRateLimit(capacity:Number, time:Number)
    {
        this.amount = 0.0;
        /* capacity / time is the rate we are aiming for. */
        this.capacity = capacity;
        this.time = time;
        this.last_update = getTimer();
    }

    private function age():void
    {
        var now:uint;
        var delta:Number;

        now = getTimer();
        delta = (now - last_update) / 1000.0;
        last_update = now;

        amount -= delta * capacity / time;
        if (amount < 0.0)
            amount = 0.0;
    }

    public override function update(n:Number):Boolean
    {
        age();
        amount += n;

        return amount <= capacity;
    }

    public override function when():Number
    {
        age();
        return (amount - capacity) / (capacity / time);
    }

    public override function is_limited():Boolean
    {
        age();
        return amount > capacity;
    }
}

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        private const RTMFP_URL:String = "rtmfp://tor-facilitator.bamsoftware.com";

        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "tor-facilitator.bamsoftware.com",
            port: 9002
        };

        /* Local Tor client to use in case of RTMFP connection. */
        private const DEFAULT_LOCAL_TOR_CLIENT_ADDR:Object = {
            host: "127.0.0.1",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 10;

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 1000;

        // Bytes per second. Set to undefined to disable limit.
        public const RATE_LIMIT:Number = undefined;
        // Seconds.
        private const RATE_LIMIT_HISTORY:Number = 5.0;

        /* TextField for debug output. */
        private var output_text:TextField;

        /* UI shown when debug is off. */
        private var badge:Badge;

        /* Number of proxy pairs currently connected (up to
           MAX_NUM_PROXY_PAIRS). */
        private var num_proxy_pairs:int = 0;

        private var fac_addr:Object;

        private var local_addr:Object;

        public var rate_limit:RateLimit;

        public function puts(s:String):void
        {
            if (output_text) {
                output_text.appendText(s + "\n");
                output_text.scrollV = output_text.maxScrollV;
            }
        }

        public function swfcat()
        {
            // Absolute positioning.
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            badge = new Badge();

            if (RATE_LIMIT)
                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
            else
                rate_limit = new RateUnlimit();

            // 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;

            if (this.loaderInfo.parameters["debug"] || this.loaderInfo.parameters["client"]) {
                output_text = new TextField();
                output_text.width = stage.stageWidth;
                output_text.height = stage.stageHeight;
                output_text.background = true;
                output_text.backgroundColor = 0x001f0f;
                output_text.textColor = 0x44cc44;
                addChild(output_text);
            } else {
                addChild(badge);
            }

            puts("Parameters loaded.");

            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            local_addr = get_param_addr("local", DEFAULT_LOCAL_TOR_CLIENT_ADDR);
            if (!local_addr) {
                puts("Error: Local spec must be in the form \"host:port\".");
                return;
            }

            if (this.loaderInfo.parameters["client"])
                client_main();
            else
                proxy_main();
        }

        /* Get an address structure from the given movie parameter, or the given
           default. Returns null on error. */
        private function get_param_addr(param:String, default_addr:Object):Object
        {
            var spec:String, addr:Object;

            spec = this.loaderInfo.parameters[param];
            if (spec)
                return parse_addr_spec(spec);
            else
                return default_addr;
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function proxy_main():void
        {
            var fac_url:String;
            var loader:URLLoader;

            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            loader = new URLLoader();
            /* Get the x-www-form-urlencoded values. */
            loader.dataFormat = URLLoaderDataFormat.VARIABLES;
            loader.addEventListener(Event.COMPLETE, fac_complete);
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(new URLRequest(fac_url));
        }

        private function fac_complete(e:Event):void
        {
            var loader:URLLoader;
            var client_spec:String;
            var relay_spec:String;
            var proxy_pair:Object;

            setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);

            loader = e.target as URLLoader;
            client_spec = loader.data.client;
            if (client_spec == "") {
                puts("No clients.");
                return;
            } else if (!client_spec) {
                puts("Error: missing \"client\" in response.");
                return;
            }
            relay_spec = loader.data.relay;
            if (!relay_spec) {
                puts("Error: missing \"relay\" in response.");
                return;
            }
            puts("Facilitator: got client:\"" + client_spec + "\" "
                + "relay:\"" + relay_spec + "\".");

            try {
                proxy_pair = make_proxy_pair(client_spec, relay_spec);
            } catch (e:ArgumentError) {
                puts("Error: " + e);
                return;
            }
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
                badge.proxy_end();
            });
            proxy_pair.connect();

            num_proxy_pairs++;
            badge.proxy_begin();
        }

        private function client_main():void
        {
            var rs:RTMFPSocket;

            rs = new RTMFPSocket(RTMFP_URL);
            rs.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Got RTMFP id " + rs.id);
                register(rs);
            });
            rs.addEventListener(RTMFPSocket.ACCEPT_EVENT, client_accept);

            rs.listen();
        }

        private function client_accept(e:Event):void {
            var rs:RTMFPSocket;
            var s_t:Socket;
            var proxy_pair:ProxyPair;

            rs = e.target as RTMFPSocket;
            s_t = new Socket();

            puts("Got RTMFP connection from " + rs.peer_id);

            proxy_pair = new ProxyPair(this, rs, function ():void {
                /* Do nothing; already connected. */
            }, s_t, function ():void {
                s_t.connect(local_addr.host, local_addr.port);
            });
            proxy_pair.connect();
        }

        private function register(rs:RTMFPSocket):void {
            var fac_url:String;
            var loader:URLLoader;
            var request:URLRequest;
            var variables:URLVariables;

            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Facilitator: registered.");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
                rs.close();
            });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
                rs.close();
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            request = new URLRequest(fac_url);
            request.method = URLRequestMethod.POST;
            request.data = new URLVariables;
            request.data["client"] = rs.id;

            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(request);
        }

        private function make_proxy_pair(client_spec:String, relay_spec:String):ProxyPair
        {
            var proxy_pair:ProxyPair;
            var addr_c:Object;
            var addr_r:Object;
            var s_c:*;
            var s_r:Socket;

            addr_r = swfcat.parse_addr_spec(relay_spec);
            if (!addr_r)
                throw new ArgumentError("Relay spec must be in the form \"host:port\".");

            addr_c = swfcat.parse_addr_spec(client_spec);
            if (addr_c) {
                s_c = new Socket();
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(addr_c.host, addr_c.port);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + addr_c.host + ":" + addr_c.port + ","
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            if (client_spec.match(/^[0-9A-Fa-f]{64}$/)) {
                s_c = new RTMFPSocket(RTMFP_URL);
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(client_spec);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + client_spec.substr(0, 4) + "...,"
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            throw new ArgumentError("Can't parse client spec \"" + client_spec + "\".");
        }

        /* 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;
        }
    }
}

import flash.text.TextFormat;
import flash.text.TextField;
import flash.utils.getTimer;

class Badge extends flash.display.Sprite
{
    /* Number of proxy pairs currently connected. */
    private var num_proxy_pairs:int = 0;
    /* Number of proxy pairs ever connected. */
    private var total_proxy_pairs:int = 0;

    [Embed(source="badge.png")]
    private var BadgeImage:Class;
    private var tot_client_count_tf:TextField;
    private var tot_client_count_fmt:TextFormat;
    private var cur_client_count_tf:TextField;
    private var cur_client_count_fmt:TextFormat;

    public function Badge()
    {
        /* Setup client counter for badge. */
        tot_client_count_fmt = new TextFormat();
        tot_client_count_fmt.color = 0xFFFFFF;
        tot_client_count_fmt.align = "center";
        tot_client_count_fmt.font = "courier-new";
        tot_client_count_fmt.bold = true;
        tot_client_count_fmt.size = 10;
        tot_client_count_tf = new TextField();
        tot_client_count_tf.width = 20;
        tot_client_count_tf.height = 17;
        tot_client_count_tf.background = false;
        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
        tot_client_count_tf.x=47;
        tot_client_count_tf.y=0;

        cur_client_count_fmt = new TextFormat();
        cur_client_count_fmt.color = 0xFFFFFF;
        cur_client_count_fmt.align = "center";
        cur_client_count_fmt.font = "courier-new";
        cur_client_count_fmt.bold = true;
        cur_client_count_fmt.size = 10;
        cur_client_count_tf = new TextField();
        cur_client_count_tf.width = 20;
        cur_client_count_tf.height = 17;
        cur_client_count_tf.background = false;
        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
        cur_client_count_tf.x=47;
        cur_client_count_tf.y=6;

        addChild(new BadgeImage());
        addChild(tot_client_count_tf);
        addChild(cur_client_count_tf);

        /* Update the client counter on badge. */
        update_client_count();
    }

    public function proxy_begin():void
    {
        num_proxy_pairs++;
        total_proxy_pairs++;
        update_client_count();
    }

    public function proxy_end():void
    {
        num_proxy_pairs--;
        update_client_count();
    }

    private function update_client_count():void
    {
        /* Update total client count. */
        if (String(total_proxy_pairs).length == 1)
            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
        else
            tot_client_count_tf.text = String(total_proxy_pairs);

        /* Update current client count. */
        cur_client_count_tf.text = "";
        for(var i:Number = 0; i < num_proxy_pairs; i++)
            cur_client_count_tf.appendText(".");
    }
}

class RateLimit
{
    public function RateLimit()
    {
    }

    public function update(n:Number):Boolean
    {
        return true;
    }

    public function when():Number
    {
        return 0.0;
    }

    public function is_limited():Boolean
    {
        return false;
    }
}

class RateUnlimit extends RateLimit
{
    public function RateUnlimit()
    {
    }

    public override function update(n:Number):Boolean
    {
        return true;
    }

    public override function when():Number
    {
        return 0.0;
    }

    public override function is_limited():Boolean
    {
        return false;
    }
}

class BucketRateLimit extends RateLimit
{
    private var amount:Number;
    private var capacity:Number;
    private var time:Number;
    private var last_update:uint;

    public function BucketRateLimit(capacity:Number, time:Number)
    {
        this.amount = 0.0;
        /* capacity / time is the rate we are aiming for. */
        this.capacity = capacity;
        this.time = time;
        this.last_update = getTimer();
    }

    private function age():void
    {
        var now:uint;
        var delta:Number;

        now = getTimer();
        delta = (now - last_update) / 1000.0;
        last_update = now;

        amount -= delta * capacity / time;
        if (amount < 0.0)
            amount = 0.0;
    }

    public override function update(n:Number):Boolean
    {
        age();
        amount += n;

        return amount <= capacity;
    }

    public override function when():Number
    {
        age();
        return (amount - capacity) / (capacity / time);
    }

    public override function is_limited():Boolean
    {
        age();
        return amount > capacity;
    }
}

Fix error handling for the "local" parameter.

get_param_addr already handles a default value. It signals a parsing
error if the supplied value has a syntax error.


package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        private const RTMFP_URL:String = "rtmfp://tor-facilitator.bamsoftware.com";

        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "tor-facilitator.bamsoftware.com",
            port: 9002
        };

        /* Local Tor client to use in case of RTMFP connection. */
        private const DEFAULT_LOCAL_TOR_CLIENT_ADDR:Object = {
            host: "127.0.0.1",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 10;

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 1000;

        // Bytes per second. Set to undefined to disable limit.
        public static const RATE_LIMIT:Number = undefined;
        // Seconds.
        private static const RATE_LIMIT_HISTORY:Number = 5.0;

        /* TextField for debug output. */
        private var output_text:TextField;

        /* UI shown when debug is off. */
        private var badge:Badge;

        /* Number of proxy pairs currently connected (up to
           MAX_NUM_PROXY_PAIRS). */
        private var num_proxy_pairs:int = 0;

        private var fac_addr:Object;

        private var local_addr:Object;

        public var rate_limit:RateLimit;

        public function puts(s:String):void
        {
            if (output_text) {
                output_text.appendText(s + "\n");
                output_text.scrollV = output_text.maxScrollV;
            }
        }

        public function swfcat()
        {
            // Absolute positioning.
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            badge = new Badge();

            if (RATE_LIMIT)
                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
            else
                rate_limit = new RateUnlimit();

            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            if (this.loaderInfo.parameters["debug"] || this.loaderInfo.parameters["client"]) {
                output_text = new TextField();
                output_text.width = stage.stageWidth;
                output_text.height = stage.stageHeight;
                output_text.background = true;
                output_text.backgroundColor = 0x001f0f;
                output_text.textColor = 0x44cc44;
                addChild(output_text);
            } else {
                addChild(badge);
            }

            puts("Parameters loaded.");

            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            local_addr = get_param_addr("local", DEFAULT_LOCAL_TOR_CLIENT_ADDR);
            if (!local_addr) {
                puts("Error: Local spec must be in the form \"host:port\".");
                return;
            }

            if (this.loaderInfo.parameters["client"])
                client_main();
            else
                proxy_main();
        }

        /* Get an address structure from the given movie parameter, or the given
           default. Returns null on error. */
        private function get_param_addr(param:String, default_addr:Object):Object
        {
            var spec:String;

            spec = this.loaderInfo.parameters[param];
            if (spec)
                return parse_addr_spec(spec);
            else
                return default_addr;
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function proxy_main():void
        {
            var fac_url:String;
            var loader:URLLoader;

            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            loader = new URLLoader();
            /* Get the x-www-form-urlencoded values. */
            loader.dataFormat = URLLoaderDataFormat.VARIABLES;
            loader.addEventListener(Event.COMPLETE, fac_complete);
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(new URLRequest(fac_url));
        }

        private function fac_complete(e:Event):void
        {
            var loader:URLLoader;
            var client_spec:String;
            var relay_spec:String;
            var proxy_pair:Object;

            setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);

            loader = e.target as URLLoader;
            client_spec = loader.data.client;
            if (client_spec == "") {
                puts("No clients.");
                return;
            } else if (!client_spec) {
                puts("Error: missing \"client\" in response.");
                return;
            }
            relay_spec = loader.data.relay;
            if (!relay_spec) {
                puts("Error: missing \"relay\" in response.");
                return;
            }
            puts("Facilitator: got client:\"" + client_spec + "\" "
                + "relay:\"" + relay_spec + "\".");

            try {
                proxy_pair = make_proxy_pair(client_spec, relay_spec);
            } catch (e:ArgumentError) {
                puts("Error: " + e);
                return;
            }
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
                badge.proxy_end();
            });
            proxy_pair.connect();

            num_proxy_pairs++;
            badge.proxy_begin();
        }

        private function client_main():void
        {
            var rs:RTMFPSocket;

            rs = new RTMFPSocket(RTMFP_URL);
            rs.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Got RTMFP id " + rs.id);
                register(rs);
            });
            rs.addEventListener(RTMFPSocket.ACCEPT_EVENT, client_accept);

            rs.listen();
        }

        private function client_accept(e:Event):void {
            var rs:RTMFPSocket;
            var s_t:Socket;
            var proxy_pair:ProxyPair;

            rs = e.target as RTMFPSocket;
            s_t = new Socket();

            puts("Got RTMFP connection from " + rs.peer_id);

            proxy_pair = new ProxyPair(this, rs, function ():void {
                /* Do nothing; already connected. */
            }, s_t, function ():void {
                s_t.connect(local_addr.host, local_addr.port);
            });
            proxy_pair.connect();
        }

        private function register(rs:RTMFPSocket):void {
            var fac_url:String;
            var loader:URLLoader;
            var request:URLRequest;

            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Facilitator: registered.");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
                rs.close();
            });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
                rs.close();
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            request = new URLRequest(fac_url);
            request.method = URLRequestMethod.POST;
            request.data = new URLVariables();
            request.data["client"] = rs.id;

            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(request);
        }

        private function make_proxy_pair(client_spec:String, relay_spec:String):ProxyPair
        {
            var proxy_pair:ProxyPair;
            var addr_c:Object;
            var addr_r:Object;
            var s_c:*;
            var s_r:Socket;

            addr_r = swfcat.parse_addr_spec(relay_spec);
            if (!addr_r)
                throw new ArgumentError("Relay spec must be in the form \"host:port\".");

            addr_c = swfcat.parse_addr_spec(client_spec);
            if (addr_c) {
                s_c = new Socket();
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(addr_c.host, addr_c.port);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + addr_c.host + ":" + addr_c.port + ","
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            if (client_spec.match(/^[0-9A-Fa-f]{64}$/)) {
                s_c = new RTMFPSocket(RTMFP_URL);
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(client_spec);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + client_spec.substr(0, 4) + "...,"
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            throw new ArgumentError("Can't parse client spec \"" + client_spec + "\".");
        }

        /* 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;
        }
    }
}

import flash.text.TextFormat;
import flash.text.TextField;
import flash.utils.getTimer;

class Badge extends flash.display.Sprite
{
    /* Number of proxy pairs currently connected. */
    private var num_proxy_pairs:int = 0;
    /* Number of proxy pairs ever connected. */
    private var total_proxy_pairs:int = 0;

    [Embed(source="badge.png")]
    private var BadgeImage:Class;
    private var tot_client_count_tf:TextField;
    private var tot_client_count_fmt:TextFormat;
    private var cur_client_count_tf:TextField;
    private var cur_client_count_fmt:TextFormat;

    public function Badge()
    {
        /* Setup client counter for badge. */
        tot_client_count_fmt = new TextFormat();
        tot_client_count_fmt.color = 0xFFFFFF;
        tot_client_count_fmt.align = "center";
        tot_client_count_fmt.font = "courier-new";
        tot_client_count_fmt.bold = true;
        tot_client_count_fmt.size = 10;
        tot_client_count_tf = new TextField();
        tot_client_count_tf.width = 20;
        tot_client_count_tf.height = 17;
        tot_client_count_tf.background = false;
        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
        tot_client_count_tf.x=47;
        tot_client_count_tf.y=0;

        cur_client_count_fmt = new TextFormat();
        cur_client_count_fmt.color = 0xFFFFFF;
        cur_client_count_fmt.align = "center";
        cur_client_count_fmt.font = "courier-new";
        cur_client_count_fmt.bold = true;
        cur_client_count_fmt.size = 10;
        cur_client_count_tf = new TextField();
        cur_client_count_tf.width = 20;
        cur_client_count_tf.height = 17;
        cur_client_count_tf.background = false;
        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
        cur_client_count_tf.x=47;
        cur_client_count_tf.y=6;

        addChild(new BadgeImage());
        addChild(tot_client_count_tf);
        addChild(cur_client_count_tf);

        /* Update the client counter on badge. */
        update_client_count();
    }

    public function proxy_begin():void
    {
        num_proxy_pairs++;
        total_proxy_pairs++;
        update_client_count();
    }

    public function proxy_end():void
    {
        num_proxy_pairs--;
        update_client_count();
    }

    private function update_client_count():void
    {
        /* Update total client count. */
        if (String(total_proxy_pairs).length == 1)
            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
        else
            tot_client_count_tf.text = String(total_proxy_pairs);

        /* Update current client count. */
        cur_client_count_tf.text = "";
        for(var i:Number = 0; i < num_proxy_pairs; i++)
            cur_client_count_tf.appendText(".");
    }
}

class RateLimit
{
    public function RateLimit()
    {
    }

    public function update(n:Number):Boolean
    {
        return true;
    }

    public function when():Number
    {
        return 0.0;
    }

    public function is_limited():Boolean
    {
        return false;
    }
}

class RateUnlimit extends RateLimit
{
    public function RateUnlimit()
    {
    }

    public override function update(n:Number):Boolean
    {
        return true;
    }

    public override function when():Number
    {
        return 0.0;
    }

    public override function is_limited():Boolean
    {
        return false;
    }
}

class BucketRateLimit extends RateLimit
{
    private var amount:Number;
    private var capacity:Number;
    private var time:Number;
    private var last_update:uint;

    public function BucketRateLimit(capacity:Number, time:Number)
    {
        this.amount = 0.0;
        /* capacity / time is the rate we are aiming for. */
        this.capacity = capacity;
        this.time = time;
        this.last_update = getTimer();
    }

    private function age():void
    {
        var now:uint;
        var delta:Number;

        now = getTimer();
        delta = (now - last_update) / 1000.0;
        last_update = now;

        amount -= delta * capacity / time;
        if (amount < 0.0)
            amount = 0.0;
    }

    public override function update(n:Number):Boolean
    {
        age();
        amount += n;

        return amount <= capacity;
    }

    public override function when():Number
    {
        age();
        return (amount - capacity) / (capacity / time);
    }

    public override function is_limited():Boolean
    {
        age();
        return amount > capacity;
    }
}

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        private const RTMFP_URL:String = "rtmfp://tor-facilitator.bamsoftware.com";

        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "tor-facilitator.bamsoftware.com",
            port: 9002
        };

        /* Local Tor client to use in case of RTMFP connection. */
        private const DEFAULT_LOCAL_TOR_CLIENT_ADDR:Object = {
            host: "127.0.0.1",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 10;

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 1000;

        // Bytes per second. Set to undefined to disable limit.
        public static const RATE_LIMIT:Number = undefined;
        // Seconds.
        private static const RATE_LIMIT_HISTORY:Number = 5.0;

        /* TextField for debug output. */
        private var output_text:TextField;

        /* UI shown when debug is off. */
        private var badge:Badge;

        /* Number of proxy pairs currently connected (up to
           MAX_NUM_PROXY_PAIRS). */
        private var num_proxy_pairs:int = 0;
        /* References to active proxy pairs, to prevent them from being
           garbage collected. */
        private var proxy_pairs:Array;

        private var fac_addr:Object;

        private var local_addr:Object;

        public var rate_limit:RateLimit;

        public function puts(s:String):void
        {
            if (output_text) {
                output_text.appendText(s + "\n");
                output_text.scrollV = output_text.maxScrollV;
            }
        }

        public function swfcat()
        {
            // Absolute positioning.
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            badge = new Badge();

            proxy_pairs = [];

            if (RATE_LIMIT)
                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
            else
                rate_limit = new RateUnlimit();

            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            if (this.loaderInfo.parameters["debug"] || this.loaderInfo.parameters["client"]) {
                output_text = new TextField();
                output_text.width = stage.stageWidth;
                output_text.height = stage.stageHeight;
                output_text.background = true;
                output_text.backgroundColor = 0x001f0f;
                output_text.textColor = 0x44cc44;
                addChild(output_text);
            } else {
                addChild(badge);
            }

            puts("Parameters loaded.");

            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            local_addr = get_param_addr("local", DEFAULT_LOCAL_TOR_CLIENT_ADDR);
            if (!local_addr) {
                puts("Error: Local spec must be in the form \"host:port\".");
                return;
            }

            if (this.loaderInfo.parameters["client"])
                client_main();
            else
                proxy_main();
        }

        /* Get an address structure from the given movie parameter, or the given
           default. Returns null on error. */
        private function get_param_addr(param:String, default_addr:Object):Object
        {
            var spec:String;

            spec = this.loaderInfo.parameters[param];
            if (spec)
                return parse_addr_spec(spec);
            else
                return default_addr;
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function proxy_main():void
        {
            var fac_url:String;
            var loader:URLLoader;

            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            loader = new URLLoader();
            /* Get the x-www-form-urlencoded values. */
            loader.dataFormat = URLLoaderDataFormat.VARIABLES;
            loader.addEventListener(Event.COMPLETE, fac_complete);
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(new URLRequest(fac_url));
        }

        private function fac_complete(e:Event):void
        {
            var loader:URLLoader;
            var client_spec:String;
            var relay_spec:String;
            var proxy_pair:Object;

            setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);

            loader = e.target as URLLoader;
            client_spec = loader.data.client;
            if (client_spec == "") {
                puts("No clients.");
                return;
            } else if (!client_spec) {
                puts("Error: missing \"client\" in response.");
                return;
            }
            relay_spec = loader.data.relay;
            if (!relay_spec) {
                puts("Error: missing \"relay\" in response.");
                return;
            }
            puts("Facilitator: got client:\"" + client_spec + "\" "
                + "relay:\"" + relay_spec + "\".");

            try {
                proxy_pair = make_proxy_pair(client_spec, relay_spec);
            } catch (e:ArgumentError) {
                puts("Error: " + e);
                return;
            }
            proxy_pairs.push(proxy_pair);
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
                /* Delete from the list of active proxy pairs. */
                proxy_pairs.splice(proxy_pairs.indexOf(proxy_pair), 1);
                badge.proxy_end();
            });
            proxy_pair.connect();

            num_proxy_pairs++;
            badge.proxy_begin();
        }

        private function client_main():void
        {
            var rs:RTMFPSocket;

            rs = new RTMFPSocket(RTMFP_URL);
            rs.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Got RTMFP id " + rs.id);
                register(rs);
            });
            rs.addEventListener(RTMFPSocket.ACCEPT_EVENT, client_accept);

            rs.listen();
        }

        private function client_accept(e:Event):void {
            var rs:RTMFPSocket;
            var s_t:Socket;
            var proxy_pair:ProxyPair;

            rs = e.target as RTMFPSocket;
            s_t = new Socket();

            puts("Got RTMFP connection from " + rs.peer_id);

            proxy_pair = new ProxyPair(this, rs, function ():void {
                /* Do nothing; already connected. */
            }, s_t, function ():void {
                s_t.connect(local_addr.host, local_addr.port);
            });
            proxy_pair.connect();
        }

        private function register(rs:RTMFPSocket):void {
            var fac_url:String;
            var loader:URLLoader;
            var request:URLRequest;

            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Facilitator: registered.");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
                rs.close();
            });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
                rs.close();
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            request = new URLRequest(fac_url);
            request.method = URLRequestMethod.POST;
            request.data = new URLVariables();
            request.data["client"] = rs.id;

            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(request);
        }

        private function make_proxy_pair(client_spec:String, relay_spec:String):ProxyPair
        {
            var proxy_pair:ProxyPair;
            var addr_c:Object;
            var addr_r:Object;
            var s_c:*;
            var s_r:Socket;

            addr_r = swfcat.parse_addr_spec(relay_spec);
            if (!addr_r)
                throw new ArgumentError("Relay spec must be in the form \"host:port\".");

            addr_c = swfcat.parse_addr_spec(client_spec);
            if (addr_c) {
                s_c = new Socket();
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(addr_c.host, addr_c.port);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + addr_c.host + ":" + addr_c.port + ","
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            if (client_spec.match(/^[0-9A-Fa-f]{64}$/)) {
                s_c = new RTMFPSocket(RTMFP_URL);
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(client_spec);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + client_spec.substr(0, 4) + "...,"
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            throw new ArgumentError("Can't parse client spec \"" + client_spec + "\".");
        }

        /* 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;
        }
    }
}

import flash.text.TextFormat;
import flash.text.TextField;
import flash.utils.getTimer;

class Badge extends flash.display.Sprite
{
    /* Number of proxy pairs currently connected. */
    private var num_proxy_pairs:int = 0;
    /* Number of proxy pairs ever connected. */
    private var total_proxy_pairs:int = 0;

    [Embed(source="badge.png")]
    private var BadgeImage:Class;
    private var tot_client_count_tf:TextField;
    private var tot_client_count_fmt:TextFormat;
    private var cur_client_count_tf:TextField;
    private var cur_client_count_fmt:TextFormat;

    public function Badge()
    {
        /* Setup client counter for badge. */
        tot_client_count_fmt = new TextFormat();
        tot_client_count_fmt.color = 0xFFFFFF;
        tot_client_count_fmt.align = "center";
        tot_client_count_fmt.font = "courier-new";
        tot_client_count_fmt.bold = true;
        tot_client_count_fmt.size = 10;
        tot_client_count_tf = new TextField();
        tot_client_count_tf.width = 20;
        tot_client_count_tf.height = 17;
        tot_client_count_tf.background = false;
        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
        tot_client_count_tf.x=47;
        tot_client_count_tf.y=0;

        cur_client_count_fmt = new TextFormat();
        cur_client_count_fmt.color = 0xFFFFFF;
        cur_client_count_fmt.align = "center";
        cur_client_count_fmt.font = "courier-new";
        cur_client_count_fmt.bold = true;
        cur_client_count_fmt.size = 10;
        cur_client_count_tf = new TextField();
        cur_client_count_tf.width = 20;
        cur_client_count_tf.height = 17;
        cur_client_count_tf.background = false;
        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
        cur_client_count_tf.x=47;
        cur_client_count_tf.y=6;

        addChild(new BadgeImage());
        addChild(tot_client_count_tf);
        addChild(cur_client_count_tf);

        /* Update the client counter on badge. */
        update_client_count();
    }

    public function proxy_begin():void
    {
        num_proxy_pairs++;
        total_proxy_pairs++;
        update_client_count();
    }

    public function proxy_end():void
    {
        num_proxy_pairs--;
        update_client_count();
    }

    private function update_client_count():void
    {
        /* Update total client count. */
        if (String(total_proxy_pairs).length == 1)
            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
        else
            tot_client_count_tf.text = String(total_proxy_pairs);

        /* Update current client count. */
        cur_client_count_tf.text = "";
        for(var i:Number = 0; i < num_proxy_pairs; i++)
            cur_client_count_tf.appendText(".");
    }
}

class RateLimit
{
    public function RateLimit()
    {
    }

    public function update(n:Number):Boolean
    {
        return true;
    }

    public function when():Number
    {
        return 0.0;
    }

    public function is_limited():Boolean
    {
        return false;
    }
}

class RateUnlimit extends RateLimit
{
    public function RateUnlimit()
    {
    }

    public override function update(n:Number):Boolean
    {
        return true;
    }

    public override function when():Number
    {
        return 0.0;
    }

    public override function is_limited():Boolean
    {
        return false;
    }
}

class BucketRateLimit extends RateLimit
{
    private var amount:Number;
    private var capacity:Number;
    private var time:Number;
    private var last_update:uint;

    public function BucketRateLimit(capacity:Number, time:Number)
    {
        this.amount = 0.0;
        /* capacity / time is the rate we are aiming for. */
        this.capacity = capacity;
        this.time = time;
        this.last_update = getTimer();
    }

    private function age():void
    {
        var now:uint;
        var delta:Number;

        now = getTimer();
        delta = (now - last_update) / 1000.0;
        last_update = now;

        amount -= delta * capacity / time;
        if (amount < 0.0)
            amount = 0.0;
    }

    public override function update(n:Number):Boolean
    {
        age();
        amount += n;

        return amount <= capacity;
    }

    public override function when():Number
    {
        age();
        return (amount - capacity) / (capacity / time);
    }

    public override function is_limited():Boolean
    {
        age();
        return amount > capacity;
    }
}

Store references to proxy pairs to avoid garbage collection.

Adding an event listener is not enough to keep a proxy pair alive. Pairs
could be garbage-collected at unpredictable times. We were able to
provoke this by running more than one pair at a time with servers on
localhost; perhaps it was the greater memory pressure (64 KB blocks at a
time) that exposed it.


//
// $Id: SafeSubscriber.java 4602 2007-02-24 00:39:27Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.util {

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.Subscriber;

/**
 * A class that safely handles the asynchronous subscription to a
 * distributed object when it is not know if the subscription will
 * complete before the subscriber decides they no longer wish to be
 * subscribed.
 */
public class SafeSubscriber implements Subscriber
{
    private static const log :Log = Log.getLog(SafeSubscriber);
    /**
     * Creates a safe subscriber for the specified distributed object
     * which will interact with the specified subscriber.
     */
    public function SafeSubscriber (oid :int, subscriber :Subscriber)
    {
        // make sure they're not fucking us around
        if (oid <= 0) {
            throw new ArgumentError(
                "Invalid oid provided to safesub [oid=" + oid + "]");
        }
        if (subscriber == null) {
            throw new ArgumentError(
                "Null subscriber provided to safesub [oid=" + oid + "]");
        }

        _oid = oid;
        _subscriber = subscriber;
    }

    /**
     * Returns true if we are currently subscribed to our object (or in
     * the process of obtaining a subscription).
     */
    public function isActive () :Boolean
    {
        return _active;
    }

    /**
     * Initiates the subscription process.
     */
    public function subscribe (omgr :DObjectManager) :void
    {
        if (_active) {
            log.warning("Active safesub asked to resubscribe " + this + ".");
            return;
        }

        // note that we are now again in the "wishing to be subscribed" state
        _active = true;

        // make sure we dont have an object reference (which should be
        // logically impossible)
        if (_object != null) {
            log.warning("Incroyable! A safesub has an object and was " +
                        "non-active!? " + this + ".");
            // make do in the face of insanity
            _subscriber.objectAvailable(_object);
            return;
        }

        if (_pending) {
            // we were previously asked to subscribe, then they asked to
            // unsubscribe and now they've asked to subscribe again, all
            // before the original subscription even completed; we need do
            // nothing here except as the original subscription request
            // will eventually come through and all will be well
            return;
        }

        // we're not pending and we just became active, that means we need
        // to request to subscribe to our object
        _pending = true;
        omgr.subscribeToObject(_oid, this);
    }

    /**
     * Terminates the object subscription. If the initial subscription has
     * not yet completed, the desire to terminate will be noted and the
     * subscription will be terminated as soon as it completes.
     */
    public function unsubscribe (omgr :DObjectManager) :void
    {
        if (!_active) {
            // we may be non-active and have no object which could mean
            // that subscription failed; in which case we don't want to
            // complain about anything, just quietly ignore the
            // unsubscribe request
            if (_object == null && !_pending) {
                return;
            }
            log.warning("Inactive safesub asked to unsubscribe " + this + ".");
        }

        // note that we no longer desire to be subscribed
        _active = false;

        if (_pending) {
            // make sure we don't have an object reference
            if (_object != null) {
                log.warning("Incroyable! A safesub has an object and is " +
                            "pending!? " + this + ".");
            } else {
                // nothing to do but wait for the subscription to complete
                // at which point we'll pitch the object post-haste
                return;
            }
        }

        // make sure we have our object
        if (_object == null) {
            log.warning("Zut alors! A safesub _was_ active and not " +
                        "pending yet has no object!? " + this + ".");
            // nothing to do since we're apparently already unsubscribed
            return;
        }

        // finally effect our unsubscription
        _object = null;
        omgr.unsubscribeFromObject(_oid, this);
    }

    // documentation inherited from interface
    public function objectAvailable (object :DObject) :void
    {
        // make sure life is not too cruel
        if (_object != null) {
            log.warning("Madre de dios! Our object came available but " +
                        "we've already got one!? " + this);
            // go ahead and pitch the old one, God knows what's going on
            _object = null;
        }
        if (!_pending) {
            log.warning("J.C. on a pogo stick! Our object came available " +
                        "but we're not pending!? " + this);
            // go with our badselves, it's the only way
        }

        // we're no longer pending
        _pending = false;

        // if we are no longer active, we don't want this damned thing
        if (!_active) {
            var omgr :DObjectManager = object.getManager();
            // if the object's manager is null, that means the object is
            // already destroyed and we need not trouble ourselves with
            // unsubscription as it has already been pitched to the dogs
            if (omgr != null) {
                omgr.unsubscribeFromObject(_oid, this);
            }
            return;
        }

        // otherwise the air is fresh and clean and we can do our job
        _object = object;
        _subscriber.objectAvailable(object);
    }

    // documentation inherited from interface
    public function requestFailed (oid :int, cause :ObjectAccessError) :void
    {
        // do the right thing with our pending state
        if (!_pending) {
            log.warning("Criminy creole! Our subscribe failed but we're " +
                        "not pending!? " + this);
            // go with our badselves, it's the only way
        }
        _pending = false;

        // if we're active, let our subscriber know that the shit hit the fan
        if (_active) {
            // deactivate ourselves as we never got our object (and thus
            // the real subscriber need not call unsubscribe())
            _active = false;
            _subscriber.requestFailed(oid, cause);
        }
    }

    /**
     * Returns a string representation of this instance.
     */
    public function toString () :String
    {
        return "[oid=" + _oid + ", active=" + _active + ", pending=" + _pending + ", ]";
    }

    protected var _oid :int
    protected var _subscriber :Subscriber;
    protected var _object :DObject;;
    protected var _active :Boolean;
    protected var _pending :Boolean;
}
}

//
// $Id: SafeSubscriber.java 4602 2007-02-24 00:39:27Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.util {

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.Subscriber;

/**
 * A class that safely handles the asynchronous subscription to a
 * distributed object when it is not know if the subscription will
 * complete before the subscriber decides they no longer wish to be
 * subscribed.
 */
public class SafeSubscriber implements Subscriber
{
    private static const log :Log = Log.getLog(SafeSubscriber);
    /**
     * Creates a safe subscriber for the specified distributed object
     * which will interact with the specified subscriber.
     */
    public function SafeSubscriber (oid :int, subscriber :Subscriber)
    {
        // make sure they're not fucking us around
        if (oid <= 0) {
            throw new ArgumentError(
                "Invalid oid provided to safesub [oid=" + oid + "]");
        }
        if (subscriber == null) {
            throw new ArgumentError(
                "Null subscriber provided to safesub [oid=" + oid + "]");
        }

        _oid = oid;
        _subscriber = subscriber;
    }

    /**
     * Returns true if we are currently subscribed to our object (or in
     * the process of obtaining a subscription).
     */
    public function isActive () :Boolean
    {
        return _active;
    }

    /**
     * Initiates the subscription process.
     */
    public function subscribe (omgr :DObjectManager) :void
    {
        if (_active) {
            log.warning("Active safesub asked to resubscribe " + this + ".");
            return;
        }

        // note that we are now again in the "wishing to be subscribed" state
        _active = true;

        // make sure we dont have an object reference (which should be
        // logically impossible)
        if (_object != null) {
            log.warning("Incroyable! A safesub has an object and was " +
                        "non-active!? " + this + ".");
            // make do in the face of insanity
            _subscriber.objectAvailable(_object);
            return;
        }

        if (_pending) {
            // we were previously asked to subscribe, then they asked to
            // unsubscribe and now they've asked to subscribe again, all
            // before the original subscription even completed; we need do
            // nothing here except as the original subscription request
            // will eventually come through and all will be well
            return;
        }

        // we're not pending and we just became active, that means we need
        // to request to subscribe to our object
        _pending = true;
        omgr.subscribeToObject(_oid, this);
    }

    /**
     * Terminates the object subscription. If the initial subscription has
     * not yet completed, the desire to terminate will be noted and the
     * subscription will be terminated as soon as it completes.
     */
    public function unsubscribe (omgr :DObjectManager) :void
    {
        if (!_active) {
            // we may be non-active and have no object which could mean
            // that subscription failed; in which case we don't want to
            // complain about anything, just quietly ignore the
            // unsubscribe request
            if (_object == null && !_pending) {
                return;
            }
            log.warning("Inactive safesub asked to unsubscribe " + this + ".");
            Log.dumpStack();
        }

        // note that we no longer desire to be subscribed
        _active = false;

        if (_pending) {
            // make sure we don't have an object reference
            if (_object != null) {
                log.warning("Incroyable! A safesub has an object and is " +
                            "pending!? " + this + ".");
                Log.dumpStack();
            } else {
                // nothing to do but wait for the subscription to complete
                // at which point we'll pitch the object post-haste
                return;
            }
        }

        // make sure we have our object
        if (_object == null) {
            log.warning("Zut alors! A safesub _was_ active and not " +
                        "pending yet has no object!? " + this + ".");
            Log.dumpStack();
            // nothing to do since we're apparently already unsubscribed
            return;
        }

        // finally effect our unsubscription
        _object = null;
        omgr.unsubscribeFromObject(_oid, this);
    }

    // documentation inherited from interface
    public function objectAvailable (object :DObject) :void
    {
        // make sure life is not too cruel
        if (_object != null) {
            log.warning("Madre de dios! Our object came available but " +
                        "we've already got one!? " + this);
            // go ahead and pitch the old one, God knows what's going on
            _object = null;
        }
        if (!_pending) {
            log.warning("J.C. on a pogo stick! Our object came available " +
                        "but we're not pending!? " + this);
            // go with our badselves, it's the only way
        }

        // we're no longer pending
        _pending = false;

        // if we are no longer active, we don't want this damned thing
        if (!_active) {
            var omgr :DObjectManager = object.getManager();
            // if the object's manager is null, that means the object is
            // already destroyed and we need not trouble ourselves with
            // unsubscription as it has already been pitched to the dogs
            if (omgr != null) {
                omgr.unsubscribeFromObject(_oid, this);
            }
            return;
        }

        // otherwise the air is fresh and clean and we can do our job
        _object = object;
        _subscriber.objectAvailable(object);
    }

    // documentation inherited from interface
    public function requestFailed (oid :int, cause :ObjectAccessError) :void
    {
        // do the right thing with our pending state
        if (!_pending) {
            log.warning("Criminy creole! Our subscribe failed but we're " +
                        "not pending!? " + this);
            // go with our badselves, it's the only way
        }
        _pending = false;

        // if we're active, let our subscriber know that the shit hit the fan
        if (_active) {
            // deactivate ourselves as we never got our object (and thus
            // the real subscriber need not call unsubscribe())
            _active = false;
            _subscriber.requestFailed(oid, cause);
        }
    }

    /**
     * Returns a string representation of this instance.
     */
    public function toString () :String
    {
        return "[oid=" + _oid + ", active=" + _active + ", pending=" + _pending + ", ]";
    }

    protected var _oid :int
    protected var _subscriber :Subscriber;
    protected var _object :DObject;;
    protected var _active :Boolean;
    protected var _pending :Boolean;
}
}

Use Log.dumpStack() where the Java source used Thread.dumpStack().

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4633 542714f4-19e9-0310-aa3c-eee0fc999fb1


package aerys.minko.scene.controller.light
{
	import aerys.minko.scene.data.LightDataProvider;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.light.AbstractLight;
	import aerys.minko.scene.node.light.DirectionalLight;
	import aerys.minko.type.enum.ShadowMappingType;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class DirectionalLightController extends LightShadowController
	{
		private static const SCREEN_TO_UV	: Matrix4x4	= new Matrix4x4(
			.5,		.0,		.0,		.0,
			.0, 	-.5,	.0,		.0,
			.0,		.0,		1.,		.0,
			.5, 	.5,		.0,		1.
		);

		private var _worldPosition		: Vector4;
		private var _worldDirection		: Vector4;
		private var _worldToScreen		: Matrix4x4;
		private var _worldToUV			: Matrix4x4;
		private var _projection			: Matrix4x4;

		public function DirectionalLightController()
		{
			super(DirectionalLight, ShadowMappingType.MATRIX);

			initialize();
		}

		private function initialize() : void
		{
			_worldPosition = new Vector4();
			_worldDirection = new Vector4();
			_worldToScreen = new Matrix4x4();
			_worldToUV = new Matrix4x4();
			_projection = new Matrix4x4();
		}

		override protected function targetAddedHandler(ctrl		: LightController,
													  target	: AbstractLight) : void
		{
			super.targetAddedHandler(ctrl, target);

			lightData.setLightProperty('worldPosition', _worldPosition);
			lightData.setLightProperty('worldDirection', _worldDirection);
			lightData.setLightProperty('worldToScreen', _worldToScreen);
			lightData.setLightProperty('worldToUV', _worldToUV);
			lightData.setLightProperty('projection', _projection);
		}

		override protected function lightAddedToScene(scene : Scene) : void
		{
			super.lightAddedToScene(scene);

			updateProjectionMatrix();
			lightLocalToWorldChangedHandler(light, light.getLocalToWorldTransform());
			light.localToWorldTransformChanged.add(lightLocalToWorldChangedHandler);
		}

		override protected function lightRemovedFromScene(scene : Scene) : void
		{
			super.lightRemovedFromScene(scene);

			light.localToWorldTransformChanged.remove(lightLocalToWorldChangedHandler);
		}

		override protected function lightDataChangedHandler(lightData		: LightDataProvider,
															propertyName	: String) : void
		{
			super.lightDataChangedHandler(lightData, propertyName);

			propertyName = LightDataProvider.getPropertyName(propertyName);

			if (propertyName == 'shadowWidth' || propertyName == 'shadowZFar')
				updateProjectionMatrix();
		}

		private function lightLocalToWorldChangedHandler(light 			: AbstractLight,
														 localToWorld 	: Matrix4x4) : void
		{
			// compute position
			localToWorld.getTranslation(_worldPosition);

			// compute direction
			_worldDirection	= localToWorld.deltaTransformVector(Vector4.Z_AXIS, _worldDirection);
			_worldDirection.normalize();

			// update world to screen/uv
			_worldToScreen.lock().copyFrom(localToWorld).invert().append(_projection).unlock();
			_worldToUV.lock().copyFrom(_worldToScreen).append(SCREEN_TO_UV).unlock();
		}

		private function updateProjectionMatrix() : void
		{
			var zFar 	: Number 	= lightData.getLightProperty('shadowZFar');
			var width	: Number	= lightData.getLightProperty('shadowWidth');

			_projection.initialize(
				2. / width, 	0., 			0.,			0.,
				0., 			2. / width, 	0.,			0.,
				0., 			0., 			2. / zFar, 	0.,
				0., 			0., 			0.,			1.
			);

			_worldToScreen.lock()
				.copyFrom(light.getWorldToLocalTransform())
				.append(_projection)
				.unlock();

			_worldToUV.lock()
				.copyFrom(_worldToScreen)
				.append(SCREEN_TO_UV)
				.unlock();
		}
	}
}

package aerys.minko.scene.controller.light
{
	import aerys.minko.scene.data.LightDataProvider;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.light.AbstractLight;
	import aerys.minko.scene.node.light.DirectionalLight;
	import aerys.minko.type.enum.ShadowMappingType;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class DirectionalLightController extends LightShadowController
	{
		private static const SCREEN_TO_UV	: Matrix4x4	= new Matrix4x4(
			.5,		.0,		.0,		.0,
			.0, 	-.5,	.0,		.0,
			.0,		.0,		1.,		.0,
			.5, 	.5,		.0,		1.
		);

		private var _worldPosition		: Vector4;
		private var _worldDirection		: Vector4;
		private var _worldToScreen		: Matrix4x4;
		private var _worldToUV			: Matrix4x4;
		private var _projection			: Matrix4x4;

		public function DirectionalLightController()
		{
			super(DirectionalLight, ShadowMappingType.MATRIX);

			initialize();
		}

		private function initialize() : void
		{
			_worldPosition = new Vector4();
			_worldDirection = new Vector4();
			_worldToScreen = new Matrix4x4();
			_worldToUV = new Matrix4x4();
			_projection = new Matrix4x4();
		}

		override protected function targetAddedHandler(ctrl		: LightController,
													  target	: AbstractLight) : void
		{
			super.targetAddedHandler(ctrl, target);

			lightData.setLightProperty('worldPosition', _worldPosition);
			lightData.setLightProperty('worldDirection', _worldDirection);
			lightData.setLightProperty('worldToScreen', _worldToScreen);
			lightData.setLightProperty('worldToUV', _worldToUV);
			lightData.setLightProperty('projection', _projection);
		}

		override protected function lightAddedToScene(scene : Scene) : void
		{
			super.lightAddedToScene(scene);

			updateProjectionMatrix();
			lightLocalToWorldChangedHandler(light, light.getLocalToWorldTransform());
			light.localToWorldTransformChanged.add(lightLocalToWorldChangedHandler);
		}

		override protected function lightRemovedFromScene(scene : Scene) : void
		{
			super.lightRemovedFromScene(scene);

			light.localToWorldTransformChanged.remove(lightLocalToWorldChangedHandler);
		}

		override protected function lightDataChangedHandler(lightData		: LightDataProvider,
															propertyName	: String) : void
		{
			super.lightDataChangedHandler(lightData, propertyName);

			propertyName = LightDataProvider.getPropertyName(propertyName);

			if (propertyName == 'shadowWidth' || propertyName == 'shadowZFar')
				updateProjectionMatrix();
		}

		private function lightLocalToWorldChangedHandler(light 			: AbstractLight,
														 localToWorld 	: Matrix4x4) : void
		{
			// compute position
			localToWorld.getTranslation(_worldPosition);

			// compute direction
			localToWorld.deltaTransformVector(Vector4.Z_AXIS, _worldDirection);
			_worldDirection.normalize();

			// update world to screen/uv
			_worldToScreen.lock().copyFrom(localToWorld).invert().append(_projection).unlock();
			_worldToUV.lock().copyFrom(_worldToScreen).append(SCREEN_TO_UV).unlock();
		}

		private function updateProjectionMatrix() : void
		{
			var zFar 	: Number 	= lightData.getLightProperty('shadowZFar');
			var width	: Number	= lightData.getLightProperty('shadowWidth');

			_projection.initialize(
				2. / width, 	0., 			0.,			0.,
				0., 			2. / width, 	0.,			0.,
				0., 			0., 			2. / zFar, 	0.,
				0., 			0., 			0.,			1.
			);

			_worldToScreen.lock()
				.copyFrom(light.getWorldToLocalTransform())
				.append(_projection)
				.unlock();

			_worldToUV.lock()
				.copyFrom(_worldToScreen)
				.append(SCREEN_TO_UV)
				.unlock();
		}
	}
}

remove useless =

remove useless =