/**
 * TLSConnectionState
 * 
 * This class encapsulates the read or write state of a TLS connection,
 * and implementes the encrypting and hashing of packets. 
 * Copyright (c) 2007 Henri Torgemane
 * 
 * See LICENSE.txt for full license information.
 */
package com.hurlant.crypto.tls {
	import flash.utils.IDataInput;
	import flash.utils.ByteArray;
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.crypto.hash.HMAC;
	import com.hurlant.crypto.hash.IHash;
	import com.hurlant.crypto.symmetric.ICipher;
	import com.hurlant.crypto.symmetric.IVMode;
	import com.hurlant.util.Hex;
	import com.hurlant.util.ArrayUtil;
	
	public class TLSConnectionState implements IConnectionState {


		// compression state
		
		// cipher state
		private var bulkCipher:uint;
		private var cipherType:uint;
		private var CIPHER_key:ByteArray;
		private var CIPHER_IV:ByteArray;
		private var cipher:ICipher;
		private var ivmode:IVMode;
		
		// mac secret
		private var macAlgorithm:uint;
		private var MAC_write_secret:ByteArray;
		private var hmac:HMAC;
		
		// sequence number. uint64
		private var seq_lo:uint;
		private var seq_hi:uint;
		


		public function TLSConnectionState(
				bulkCipher:uint=0, cipherType:uint=0, macAlgorithm:uint=0,
				mac:ByteArray=null, key:ByteArray=null, IV:ByteArray=null) {
			this.bulkCipher = bulkCipher;
			this.cipherType = cipherType;
			this.macAlgorithm = macAlgorithm;
			MAC_write_secret = mac;
			hmac = MACs.getHMAC(macAlgorithm);
			CIPHER_key = key;
			CIPHER_IV = IV;
			cipher = BulkCiphers.getCipher(bulkCipher, key, 0x0301);
			if (cipher is IVMode) {
				ivmode = cipher as IVMode;
				ivmode.IV = IV;
			}
		}
		
		public function decrypt(type:uint, length:uint, p:ByteArray):ByteArray {
			// decompression is a nop.
			
			if (cipherType == BulkCiphers.STREAM_CIPHER) {
				if (bulkCipher == BulkCiphers.NULL) {
					// no-op
				} else {
					cipher.decrypt(p);
				}
			} else {
				// block cipher
				var nextIV:ByteArray = new ByteArray;
				nextIV.writeBytes(p, p.length-CIPHER_IV.length, CIPHER_IV.length);
				
				cipher.decrypt(p);


				CIPHER_IV = nextIV;
				ivmode.IV = nextIV;
			}
			if (macAlgorithm!=MACs.NULL) {
				var data:ByteArray = new ByteArray;
				var len:uint = p.length - hmac.getHashSize();
				data.writeUnsignedInt(seq_hi);
				data.writeUnsignedInt(seq_lo);
				data.writeByte(type);
				data.writeShort(TLSSecurityParameters.PROTOCOL_VERSION);
				data.writeShort(len);
				if (len!=0) {
					data.writeBytes(p, 0, len);
				}
				var mac:ByteArray = hmac.compute(MAC_write_secret, data);
				// compare "mac" with the last X bytes of p.
				var mac_received:ByteArray = new ByteArray;
				mac_received.writeBytes(p, len, hmac.getHashSize());
				if (ArrayUtil.equals(mac, mac_received)) {
					// happy happy joy joy
				} else {
					throw new TLSError("Bad Mac Data", TLSError.bad_record_mac);
				}
				p.length = len;
				p.position = 0;
			}
			// increment seq
			seq_lo++;
			if (seq_lo==0) seq_hi++;
			return p;
		}
		public function encrypt(type:uint, p:ByteArray):ByteArray {
			var mac:ByteArray = null;
			if (macAlgorithm!=MACs.NULL) {
				var data:ByteArray = new ByteArray;
				data.writeUnsignedInt(seq_hi);
				data.writeUnsignedInt(seq_lo);
				data.writeByte(type);
				data.writeShort(TLSSecurityParameters.PROTOCOL_VERSION);
				data.writeShort(p.length);
				if (p.length!=0) {
					data.writeBytes(p, 0, p.length);
				}
				mac = hmac.compute(MAC_write_secret, data);
				p.position = p.length;
				p.writeBytes(mac);
			}
			p.position = 0;
			if (cipherType == BulkCiphers.STREAM_CIPHER) {
				// stream cipher
				if (bulkCipher == BulkCiphers.NULL) {
					// no-op
				} else {
					cipher.encrypt(p);
				}
			} else {
				// block cipher
				cipher.encrypt(p);
				// adjust IV
				var nextIV:ByteArray = new ByteArray;
				nextIV.writeBytes(p, p.length-CIPHER_IV.length, CIPHER_IV.length);
				CIPHER_IV = nextIV;
				ivmode.IV = nextIV;
			}
			// increment seq
			seq_lo++;
			if (seq_lo==0) seq_hi++;
			// compression is a nop.
			return p;
		}
		
	}
}