/**
 * TLSPRF
 * 
 * An ActionScript 3 implementation of a pseudo-random generator
 * that follows the TLS specification
 * Copyright (c) 2007 Henri Torgemane
 * 
 * See LICENSE.txt for full license information.
 */
package com.hurlant.crypto.prng
{
    import flash.utils.ByteArray;
    import com.hurlant.crypto.hash.HMAC;
    import com.hurlant.crypto.hash.MD5;
    import com.hurlant.crypto.hash.SHA1;
    import com.hurlant.util.Memory;
    import com.hurlant.util.Hex;
    import flash.utils.IDataOutput;

    /**
     * There's "Random", and then there's TLS Random.
     * .
     * Still Pseudo-random, though.
     */
    public class TLSPRF
    {
        // XXX WAY TOO MANY STRUCTURES HERE
        
        // seed
        private var seed:ByteArray;
        // P_MD5's secret
        private var s1:ByteArray;
        // P_SHA-1's secret
        private var s2:ByteArray;
        // HMAC_MD5's A
        private var a1:ByteArray;
        // HMAC_SHA1's A
        private var a2:ByteArray;
        // Pool for P_MD5
        private var p1:ByteArray;
        // Pool for P_SHA1
        private var p2:ByteArray;
        // Data for HMAC_MD5
        private var d1:ByteArray;
        // Data for HMAC_SHA1
        private var d2:ByteArray;
        
        
        private var hmac_md5:HMAC;
        private var hmac_sha1:HMAC;
        
        public function TLSPRF(secret:ByteArray, label:String, seed:ByteArray) {
            var l:int = Math.ceil(secret.length/2);
            var s1:ByteArray = new ByteArray;
            var s2:ByteArray = new ByteArray;
            s1.writeBytes(secret, 0, l);
            s2.writeBytes(secret, secret.length-l, l);
            var s:ByteArray = new ByteArray;
            s.writeUTFBytes(label);
            s.writeBytes(seed);
            this.seed = s;
            this.s1 = s1;
            this.s2 = s2;
            hmac_md5 = new HMAC(new MD5);
            hmac_sha1 = new HMAC(new SHA1);
            
            this.a1 = hmac_md5.compute(s1, this.seed);
            this.a2 = hmac_sha1.compute(s2, this.seed);
            
            p1 = new ByteArray;
            p2 = new ByteArray;
            
            d1 = new ByteArray;
            d2 = new ByteArray;
            d1.position = MD5.HASH_SIZE;
            d1.writeBytes(this.seed);
            d2.position = SHA1.HASH_SIZE;
            d2.writeBytes(this.seed);
        }
        
        // XXX HORRIBLY SLOW. REWRITE.
        public function nextBytes(buffer:IDataOutput, length:int):void {
            while (length--) {
                buffer.writeByte(nextByte());
            }
        }
        public function nextByte():int {
            if (p1.bytesAvailable==0) {
                more_md5();
            }
            if (p2.bytesAvailable==0) {
                more_sha1();
            }
            return p1.readUnsignedByte()^p2.readUnsignedByte();
        }
        public function dispose():void {
            seed = dba(seed);
            s1 = dba(s1);
            s2 = dba(s2);
            a1 = dba(a1);
            a2 = dba(a2);
            p1 = dba(p1);
            p2 = dba(p2);
            d1 = dba(d1);
            d2 = dba(d2);
            hmac_md5.dispose();
            hmac_md5 = null;
            hmac_sha1.dispose();
            hmac_sha1 = null;
            Memory.gc();
        }
        public function toString():String {
            return "tls-prf";
        }
        private function dba(ba:ByteArray):ByteArray {
            for (var i:uint=0;i<ba.length;i++) {
                ba[i]=0;
            }
            ba.length=0;
            return null;
        }
        private function more_md5():void {
            d1.position=0;
            d1.writeBytes(a1);
            var p:int = p1.position;
            var more:ByteArray = hmac_md5.compute(s1, d1);
            a1 = hmac_md5.compute(s1, a1);
            p1.writeBytes(more);
            p1.position=p;
        }
        private function more_sha1():void {
            d2.position=0;
            d2.writeBytes(a2);
            var p:int = p2.position;
            var more:ByteArray = hmac_sha1.compute(s2, d2);
            a2 = hmac_sha1.compute(s2, a2);
            p2.writeBytes(more);
            p2.position=p;
        }
        
    }
}