 import flash.events.SecurityErrorEvent;
 import flash.net.Socket;
 import flash.utils.ByteArray;
+import flash.utils.clearTimeout;
+import flash.utils.getTimer;
+import flash.utils.setTimeout;
+
+class RateLimit
+{
+    private var amount:Number;
+    private var capacity:Number;
+    private var time:Number;
+    private var last_update:uint;
+
+    public function RateLimit(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 function update(n:Number):Boolean
+    {
+        age();
+        amount += n;
+
+        return amount <= capacity;
+    }
+
+    public function when():Number
+    {
+        age();
+        return (amount - capacity) / (capacity / time);
+    }
+
+    public function is_limited():Boolean
+    {
+        age();
+        return amount > capacity;
+    }
+}
 
 /* An instance of a client-relay connection. */
 class ProxyPair extends EventDispatcher

        this.ui = ui;
        this.addr_c = addr_c;
        this.addr_r = addr_r;
    }

    public function connect():void

    // Socket to relay.
    private var s_r:Socket;

    // Parent swfcat, for UI updates and rate meter.
    private var ui:swfcat;

    // Pending byte read counts for relay and client sockets.
    private var r2c_schedule:Array;
    private var c2r_schedule:Array;
    // Callback id.
    private var flush_id:uint;

    public function log(msg:String):void
    {
