/* A JavaScript implementation of the SHA family of hashes, as defined in FIPS
 * PUB 180-2 as well as the corresponding HMAC implementation as defined in
 * FIPS PUB 198a
 *
 * Version 1.31 Copyright Brian Turek 2008-2012
 * Distributed under the BSD License
 * See http://caligatio.github.com/jsSHA/ for more information
 *
 * Several functions taken from Paul Johnson
 */
(function ()
{
	var charSize = 8,
	b64pad = "",
	hexCase = 0,

	str2binb = function(str)
	{
		var bin = [], mask = (1 << charSize) - 1,
			length = str.length * charSize, i;

		for(i = 0; i < length; i += charSize) {
			bin[i >> 5] |= (str.charCodeAt(i / charSize) & mask) <<
				(32 - charSize - (i % 32));
		}

		return bin;
	},

	hex2binb = function(str)
	{
		var bin = [], length = str.length, i, num;

		for(i = 0; i < length; i += 2) {
			num = parseInt(str.substr(i, 2), 16);
			if (!isNaN(num))
				bin[i >> 3] |= num << (24 - (4 * (i % 8)));
			else
				return "INVALID HEX STRING";
		}

		return bin;
	},

	binb2hex = function(binarray)
	{
		var hex_tab = (hexCase) ? "0123456789ABCDEF" : "0123456789abcdef",
			str = "", length = binarray.length * 4, i, srcByte;

		for(i = 0; i < length; i += 1) {
			srcByte = binarray[i >> 2] >> ((3 - (i & 3)) * 8);
			str += hex_tab.charAt((srcByte >> 4) & 0xf) +
				hex_tab.charAt(srcByte & 0xf);
		}

		return str;
	},

	binb2b64 = function(binarray)
	{
		var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
			"0123456789+/", str = "", length = binarray.length * 4, i, j,
			triplet;

		for(i = 0; i < length; i += 3) {
			triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xff) << 16) |
				(((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xff) << 8) |
				((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xff);
			for(j = 0; j < 4; j += 1) {
				if(i * 8 + j * 6 <= binarray.length * 32)
					str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3f);
				else
					str += b64pad;
			}
		}
		return str;
	},

	rotl = function(x, n)
	{
		return (x << n) | (x >>> (32 - n));
	},

	parity = function(x, y, z)
	{
		return x ^ y ^ z;
	},

	ch = function(x, y, z)
	{
		return (x & y) ^ (~x & z);
	},

	maj = function(x, y, z)
	{
		return (x & y) ^ (x & z) ^ (y & z);
	},

	safeAdd_2 = function(x, y)
	{
		var lsw = (x & 0xffff) + (y & 0xffff),
			msw = (x >>> 16) + (y >>> 16) + (lsw >>> 16);

		return ((msw & 0xffff) << 16) | (lsw & 0xffff);
	},

	safeAdd_5 = function (a, b, c, d, e)
	{
		var lsw = (a & 0xffff) + (b & 0xffff) + (c & 0xffff) + (d & 0xffff) + (e & 0xffff),
			msw = (a >>> 16) + (b >>> 16) + (c >>> 16) + (d >>> 16) + (e >>> 16) + (lsw >>> 16);
		return ((msw & 0xffff) << 16) | (lsw & 0xffff);
	},

	coreSHA1 = function (message, messageLen)
	{
		var W = [], a, b, c, d, e, T, i, t, appendedMessageLength,
			H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0],
			K = [0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999,
			0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999,
			0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999,
			0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999,
			0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999,
			0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1,
			0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1,
			0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1,
			0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1,
			0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1,
			0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc,
			0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc,
			0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc,
			0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc,
			0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc,
			0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6,
			0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6,
			0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6,
			0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6,
			0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6];

		message[messageLen >> 5] |= 0x80 << (24 - (messageLen % 32));
		message[(((messageLen + 65) >> 9) << 4) + 15] = messageLen;

		appendedMessageLength = message.length;

		for(i = 0; i < appendedMessageLength; i += 16) {
			a = H[0];
			b = H[1];
			c = H[2];
			d = H[3];
			e = H[4];

			for(t = 0; t < 80; t += 1) {
				if(t < 16)
					W[t] = message[t + i];
				else
					W[t] = rotl(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1);

				if(t < 20)
					T = safeAdd_5(rotl(a, 5), ch(b, c, d), e, K[t], W[t]);
				else if(t < 40)
					T = safeAdd_5(rotl(a, 5), parity(b, c, d), e, K[t], W[t]);
				else if(t < 60)
					T = safeAdd_5(rotl(a, 5), maj(b, c, d), e, K[t], W[t]);
				else
					T = safeAdd_5(rotl(a, 5), parity(b, c, d), e, K[t], W[t]);

				e = d;
				d = c;
				c = rotl(b, 30);
				b = a;
				a = T;
			}

			H[0] = safeAdd_2(a, H[0]);
			H[1] = safeAdd_2(b, H[1]);
			H[2] = safeAdd_2(c, H[2]);
			H[3] = safeAdd_2(d, H[3]);
			H[4] = safeAdd_2(e, H[4]);
		}

		return H;
	},

	jsSHA = function(srcString, inputFormat)
	{
		this.sha1 = null;

		this.strBinLen = null;
		this.strToHash = null;

		if("HEX" === inputFormat) {
			if(0 !== (srcString.length % 2))
				return "TEXT MUST BE IN BYTE INCREMENTS";
			this.strBinLen = srcString.length * 4;
			this.strToHash = hex2binb(srcString);
		} else if(("ASCII" === inputFormat) ||
		   ('undefined' === typeof(inputFormat))) {
			this.strBinLen = srcString.length * charSize;
			this.strToHash = str2binb(srcString);
		} else
			return "UNKNOWN TEXT INPUT TYPE";
	};

	jsSHA.prototype = {
		getHash : function(format)
		{
			var formatFunc = null, message = this.strToHash.slice();

			switch(format) {
			case "HEX":
				formatFunc = binb2hex;
				break;
			case "B64":
				formatFunc = binb2b64;
				break;
			default:
				return "FORMAT NOT RECOGNIZED";
			}

			if(null === this.sha1)
				this.sha1 = coreSHA1(message, this.strBinLen);
			return formatFunc(this.sha1);
		},

		getHMAC : function(key, inputFormat, outputFormat)
		{
			var formatFunc, keyToUse, i, retVal, keyBinLen,
				keyWithIPad = [], keyWithOPad = [];

			switch (outputFormat) {
			case "HEX":
				formatFunc = binb2hex;
				break;
			case "B64":
				formatFunc = binb2b64;
				break;
			default:
				return "FORMAT NOT RECOGNIZED";
			}

			if("HEX" === inputFormat) {
				if(0 !== (key.length % 2))
					return "KEY MUST BE IN BYTE INCREMENTS";
				keyToUse = hex2binb(key);
				keyBinLen = key.length * 4;
			} else if ("ASCII" === inputFormat) {
				keyToUse = str2binb(key);
				keyBinLen = key.length * charSize;
			} else
				return "UNKNOWN KEY INPUT TYPE";

			if(64 < (keyBinLen / 8)) {
				keyToUse = coreSHA1(keyToUse, keyBinLen);
				keyToUse[15] &= 0xffffff00;
			} else if (64 > (keyBinLen / 8))
				keyToUse[15] &= 0xffffff00;

			for(i = 0; i <= 15; i += 1) {
				keyWithIPad[i] = keyToUse[i] ^ 0x36363636;
				keyWithOPad[i] = keyToUse[i] ^ 0x5c5c5c5c;
			}

			retVal = coreSHA1(keyWithIPad.concat(this.strToHash), 512 + this.strBinLen);
			retVal = coreSHA1(keyWithOPad.concat(retVal), 672);

			return (formatFunc(retVal));
		}
	};

	window.jsSHA = jsSHA;
}());

/**
 *	@file bigintx.js
 *	@brief big integer implementation on hexadecimal strings
 *	@author -tHE SWINe-
 *	@date 2012
 *
 *	This file contains functions for multiple precision unsigned integer arithmetic.
 *	Javascript String is used as internal number representation, where each character
 *	stores a single byte (char codes 0 - 255). This proved to be working on all major
 *	browsers.
 *
 *	To make sure it is indeed working, one should call BigXInt_TestInvariants() and make sure
 *	it returns true.
 *
 */

function Number_to_ByteStr(n_x)
{
	var a = "";
	do {
		a = String.fromCharCode(n_x & 0xff) + a;
		n_x >>= 8;
	} while(n_x > 0);
	// convert hex to string (will expand to double digits, if
	// the number of hex chars is even, it will make it one longer on conversion back)

	return a;
}

function XStr_to_ByteStr(s_hex_string) // converts hexadecimal string to a byte-string
{
	s_hex_string = "" + s_hex_string;
	// makes sure it is string

	var a = "";
	for(var i = s_hex_string.length; i > 0;) {
		var digits = 2;
		if(digits > i)
			digits = i;
		i -= digits;
		a = String.fromCharCode(parseInt(s_hex_string.substr(i, digits), 16)) + a;
	}
	// convert hex to string (will expand to double digits, if
	// the number of hex chars is even, it will make it one longer on conversion back)

	return a;
}

function ByteStr_to_XStr(s_number) // converts string to hexadecimal
{
	var bx = ""
	for(var i = 0; i < s_number.length; ++ i) {
		var n = s_number.charCodeAt(i);
		var lsb = n % 16;
		var msb = n / 16;
		lsb = (lsb < 10)? String.fromCharCode(48 + lsb) : String.fromCharCode(65 + 32 + lsb - 10);
		msb = (msb < 10)? String.fromCharCode(48 + msb) : String.fromCharCode(65 + 32 + msb - 10);
		bx = bx + msb + lsb;
	}
	// convert string to hex

	return bx;
}

function BigXInt_TestInvariants()
{
	return false || ( // javascript fails if the value to be returned does not begin on this line (howgh)

		ByteStr_to_XStr(Number_to_ByteStr(1)) == "01" && // simple numeral conversion
		ByteStr_to_XStr(Number_to_ByteStr(10)) == "0a" && // hex numeral conversion
		ByteStr_to_XStr(Number_to_ByteStr(16)) == "10" && // make sure the base is hex
		ByteStr_to_XStr(Number_to_ByteStr(18)) == "12" && // both digit conversion
		ByteStr_to_XStr(Number_to_ByteStr(31)) == "1f" && // both digit conversion, with hex
		ByteStr_to_XStr(Number_to_ByteStr(136171)) == "0213eb" && // multiple characters
		ByteStr_to_XStr(Number_to_ByteStr(33559531)) == "020013eb" && // multiple characters, zero in between
		ByteStr_to_XStr(Number_to_ByteStr(50271211)) == "02ff13eb" && // multiple characters, ff in between
	
		ByteStr_to_XStr(XStr_to_ByteStr("12")) == "12" && // simple conversion
		ByteStr_to_XStr(XStr_to_ByteStr("a3")) == "a3" && // general hex character
		ByteStr_to_XStr(XStr_to_ByteStr("A3")) == "a3" && // general hex character, lowercase conversion
		ByteStr_to_XStr(XStr_to_ByteStr("00")) == "00" && // null character is representable
		ByteStr_to_XStr(XStr_to_ByteStr("ff")) == "ff" && // ff is representable
		ByteStr_to_XStr(XStr_to_ByteStr("00ffab8456ec2")) ==  "000ffab8456ec2" && // odd sequences made even
		ByteStr_to_XStr(XStr_to_ByteStr("000ffab8456ec2")) == "000ffab8456ec2"); // even sequences are not changed
}

function n_LSB_ByteStr(a) // returns least significant bit, complexity O(1)
{
	return a.charCodeAt(a.length - 1) & 1;
}

function Add_ByteStr(a, b, n_factor)
{
	var c = "";
	var n_carry = 0;
	var m = a.length - 1;
	var n = b.length - 1;
	for(; m >= 0 && n >= 0; -- m, -- n) {
		n_carry += a.charCodeAt(m) + b.charCodeAt(n) * n_factor;
		c = String.fromCharCode(n_carry & 0xff) + c;
		n_carry >>= 8;
	}
	// add with carry

	if(m >= 0) {
		for(; m >= 0 && n_carry != 0; -- m) {
			n_carry += a.charCodeAt(m);
			c = String.fromCharCode(n_carry & 0xff) + c;
			n_carry >>= 8;
		}
		// add with carry

		if(m >= 0)
			c = a.substr(0, m + 1) + c;
		// copy the rest of the string
	} else if(n >= 0) {
		for(; n >= 0 && n_carry != 0; -- n) {
			n_carry += b.charCodeAt(n) * n_factor;
			c = String.fromCharCode(n_carry & 0xff) + c;
			n_carry >>= 8;
		}
		// add with carry

		if(n >= 0)
			c = b.substr(0, n + 1) + c;
		// copy the rest of the string
	}

	if(n_carry < 0)
		return "NaN";
	while(n_carry != 0) {
		c = String.fromCharCode(n_carry & 0xff) + c;
		n_carry >>= 8;
	}
	// add the last carry

	if(n_factor < 0) {
		while(c.length > 1 && c.charCodeAt(0) == 0)
			c = c.substr(1, c.length - 1);
	}
	// remove leading zeros on subtraction

	return c;
}

function RightShift_ByteStr(a)
{
	var c = "";
	var n_carry = 0;
	for(var i = 0, n = a.length; i < n; ++ i) {
		n_carry |= a.charCodeAt(i);
		c += String.fromCharCode(n_carry >> 1);
		n_carry = (n_carry & 1) << 8;
	}
	return c;
}

function LeftShift_ByteStr(a)
{
	var c = "";
	var n_carry = 0;
	for(var i = a.length - 1; i >= 0; -- i) {
		n_carry |= a.charCodeAt(i) << 1;
		c = String.fromCharCode(n_carry & 0xff) + c;
		n_carry = (n_carry & 0x100) >> 8;
	}
	if(n_carry)
		c = String.fromCharCode(n_carry) + c;
	return c;
}

function ConditionalSubtract_ByteStr(a, b)
{
	var m = 0, n = 0;
	for(var l = a.length; m < l; ++ m) {
		if(a.charCodeAt(m) != 0)
			break;
	}
	for(var l = b.length; n < l; ++ n) {
		if(b.charCodeAt(n) != 0)
			break;
	}
	// skip leading zeros (note that there should be none)

	if(a.length - m < b.length - n)
		return a;
	// a is shorter than b, it is smaller

	if(a.length - m == b.length - n) {
		var k = a.length - m;
		for(; k > 1 && a.charCodeAt(m) == b.charCodeAt(n); -- k) {
			++ m;
			++ n;
		}
		if(a.charCodeAt(m) < b.charCodeAt(n)) // note that m and n are both valid indices
			return a; // a is smaller
	}

	return Add_ByteStr(a, b, -1);
	// subtract b from a
}

function Multiply_ByteStr(a, b) // this is faster if a < b
{
	var c = String.fromCharCode(0);
	for(var i = a.length * 8; i > 0; -- i) { // for all bits in a
		if(n_LSB_ByteStr(a)) // if(a & 1)
			c = Add_ByteStr(c, b, 1); // c += b
		a = RightShift_ByteStr(a); // a >>= 1
		b = LeftShift_ByteStr(b); // b <<= 1
	}
	return c;
}

/**
 *	@note Condition b < m must be true. It is not hard id D-H, though
 *		(b is either 5 (much smaller than m), or a public key, that is also modulo m)
 */
function ModuloMultiply_ByteStr(a, b, m)
{
	var c = String.fromCharCode(0);
	for(var i = a.length * 8; i > 0; -- i) { // for all bits in a
		if(n_LSB_ByteStr(a)) { // if(a & 1)
			c = Add_ByteStr(c, b, 1); // c += b
			c = ConditionalSubtract_ByteStr(c, m);
		}
		a = RightShift_ByteStr(a); // a >>= 1
		b = LeftShift_ByteStr(b); // b <<= 1
		b = ConditionalSubtract_ByteStr(b, m);
	}
	return c;
}

/**
 *	@note Condition b < m must be true. It is not hard id D-H, though
 *		(b is either 5 (much smaller than m), or a public key, that is also modulo m)
 */
function ModuloPower_ByteStr(b, e, m, progress_fun)
{
	if(m == String.fromCharCode(1))
		return String.fromCharCode(0);

	/*var _b = parseInt(ByteStr_to_XStr(b), 16);
	var _e = parseInt(ByteStr_to_XStr(e), 16);
	var _m = parseInt(ByteStr_to_XStr(m), 16); // debug
	var _c = 1;*/ // debug

	var c = String.fromCharCode(1);
	if(progress_fun == null) {
		for(var i = e.length * 8; i > 0; -- i) { // for all bits in e
			if(n_LSB_ByteStr(e)) {
				c = ModuloMultiply_ByteStr(b, c, m); // both b and c are guaranteed to be smaller than modulus, b is preconditioned

				/*_c = parseInt(ByteStr_to_XStr(c), 16);*/ // debug
			}
			b = ModuloMultiply_ByteStr(b, b, m);
			e = RightShift_ByteStr(e);

			/*_b = parseInt(ByteStr_to_XStr(b), 16);
			_e = parseInt(ByteStr_to_XStr(e), 16);*/ // debug
		}
	} else {
		for(var i = e.length * 8; i > 0;) { // for all bits in e
			var n_next_lim = i - 29;
			if(n_next_lim < 0)
				n_next_lim = 0;
			for(; i > n_next_lim; -- i) {
				if(n_LSB_ByteStr(e)) {
					c = ModuloMultiply_ByteStr(b, c, m); // both b and c are guaranteed to be smaller than modulus, b is preconditioned

					/*_c = parseInt(ByteStr_to_XStr(c), 16);*/ // debug
				}
				b = ModuloMultiply_ByteStr(b, b, m);
				e = RightShift_ByteStr(e);

				/*_b = parseInt(ByteStr_to_XStr(b), 16);
				_e = parseInt(ByteStr_to_XStr(e), 16);*/ // debug
			}
			progress_fun(1.0 - i / (e.length * 8.0));
		}
	}
	return c;
}

function BigXInt_UnitTests()
{
	/*var zero = Number_to_ByteStr(256);
	var one = Number_to_ByteStr(256);
	var bn = Add_ByteStr(zero, one, 1);
	var r = parseInt(ByteStr_to_XStr(bn), 16);*/
	//return;

	/*var one = Number_to_ByteStr(64);
	var bn = LeftShift_ByteStr(one);
	var r = parseInt(ByteStr_to_XStr(bn), 16);*/
	//return;

	/*var zero = Number_to_ByteStr(1);
	var one = Number_to_ByteStr(1);
	var bn = Multiply_ByteStr(zero, one);
	var r = parseInt(ByteStr_to_XStr(bn), 16);*/
	//return;

	/*var zero = Number_to_ByteStr(2);
	var one = Number_to_ByteStr(33);
	var two = Number_to_ByteStr(51);
	var bn = ModuloPower_ByteStr(zero, one, two);
	var r = parseInt(ByteStr_to_XStr(bn), 16);
	var pow = Math.pow(2, 33);
	var er = parseInt("" + Math.floor((pow / 51 - Math.floor(pow / 51)) * 51 + 0.5));
	var diff = Math.log(pow / 51) / Math.log(10); // how many decimals is needed
	var tol = 0;
	if(diff > 8)
		tol = Math.pow(10, diff - 8); // establish some tolerance*/
	//return;

	var nfails = 0;
	for(var k = 1; k < 1000; k += 50) {
		for(var i = 0; i < k && i < 1000; ++ i) {
			for(var j = 0; j < 50; ++ j) {
				var bn = ModuloPower_ByteStr(Number_to_ByteStr(i), Number_to_ByteStr(j), Number_to_ByteStr(k));
				var r = parseInt(ByteStr_to_XStr(bn), 16);
				var pow = Math.pow(i, j);
				var diff = Math.log(pow / k) / Math.log(10); // how many decimals is needed
				var tol = 0;
				if(diff > 8)
					tol = Math.pow(10, diff - 8); // establish some tolerance
				var er = parseInt("" + Math.floor((pow / k - Math.floor(pow / k)) * k + 0.5));
				if((tol == 0 && er != r) || (tol > 0 && Math.abs(er - r) > tol)) {
					window.alert("(" + i + " ^ " + j + ") % " + k + " != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
					++ nfails;
				}
			}
		}
		document.getElementById("newMailField").value = k + "-ModuloPower_ByteStr passed";
	}
	document.getElementById("newMailField").value = "ModuloPower_ByteStr passed";
	for(var k = 1; k < 1000; k += 50) {
		for(var i = 0; i < 1000; ++ i) {
			for(var j = 0; j < k && j < 1000; ++ j) {
				var bn = ModuloMultiply_ByteStr(Number_to_ByteStr(i), Number_to_ByteStr(j), Number_to_ByteStr(k));
				var r = parseInt(ByteStr_to_XStr(bn), 16);
				if((i * j) % k != r) {
					window.alert("(" + i + " * " + j + ") % " + k + " != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
					++ nfails;
				}
			}
		}
		document.getElementById("newMailField").value = k + "-ModuloMultiply_ByteStr passed";
	}
	document.getElementById("newMailField").value = "ModuloMultiply_ByteStr passed";
	for(var i = 0; i < 1000; ++ i) {
		for(var j = 0; j < 1000; ++ j) {
			var bn = ConditionalSubtract_ByteStr(Number_to_ByteStr(i), Number_to_ByteStr(j));
			var r = parseInt(ByteStr_to_XStr(bn), 16);
			if(((i >= j)? i - j : i) != r) {
				window.alert(i + " % " + j + " != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
				++ nfails;
			}
		}
	}
	document.getElementById("newMailField").value = "ConditionalSubtract_ByteStr passed";
	for(var i = 0; i < 1000; ++ i) {
		for(var j = 0; j < 1000; ++ j) {
			var bn = Multiply_ByteStr(Number_to_ByteStr(i), Number_to_ByteStr(j), 1);
			var r = parseInt(ByteStr_to_XStr(bn), 16);
			if(i * j != r) {
				window.alert(i + " * " + j + " != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
				++ nfails;
			}
		}
	}
	document.getElementById("newMailField").value = "Multiply_ByteStr passed";
	for(var i = 0; i < 1000; ++ i) {
		var bn = RightShift_ByteStr(Number_to_ByteStr(i));
		var r = parseInt(ByteStr_to_XStr(bn), 16);
		if((i >> 1) != r) {
			window.alert(i + " >> 1 != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
			++ nfails;
		}
	}
	document.getElementById("newMailField").value = "RightShift_ByteStr passed";
	for(var i = 0; i < 1000; ++ i) {
		var bn = LeftShift_ByteStr(Number_to_ByteStr(i));
		var r = parseInt(ByteStr_to_XStr(bn), 16);
		if((i << 1) != r) {
			window.alert(i + " << 1 != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
			++ nfails;
		}
	}
	document.getElementById("newMailField").value = "LeftShift_ByteStr passed";
	for(var i = 0; i < 1000; ++ i) {
		for(var j = 0; j < 1000; ++ j) {
			var bn = Add_ByteStr(Number_to_ByteStr(i), Number_to_ByteStr(j), 1);
			var r = parseInt(ByteStr_to_XStr(bn), 16);
			if(i + j != r) {
				window.alert(i + " + " + j + " != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
				++ nfails;
			}
		}
	}
	document.getElementById("newMailField").value = "Add_ByteStr passed";
	for(var i = 0; i < 1000; ++ i) {
		for(var j = 0; j <= i; ++ j) {
			var bn = Add_ByteStr(Number_to_ByteStr(i), Number_to_ByteStr(j), -1);
			var r = parseInt(ByteStr_to_XStr(bn), 16);
			if(i - j != r) {
				window.alert(i + " - " + j + " != " + r + " (0x" + ByteStr_to_XStr(bn) + ")");
				++ nfails;
			}
		}
	}
	document.getElementById("newMailField").value = "Sub_ByteStr passed";
	if(nfails == 0)
		window.alert("unit tests finished with no errors");
	else
		window.alert("error: unit tests finished with " + nfails + " errors");
}

/**
 *	@file bigintx.js
 *	@brief Merkle-Hellman-Diffie key exchange
 *	@author -tHE SWINe-
 *	@date 2012
 *
 *	To make sure it is indeed working, one should call MHD_TestInvariants() and make sure
 *	it returns true.
 *
 *	To perform key exchange between two partners, either party generates a modulus: large safe prime
 *	(at least 100 digits, p * 2 + 1 must also be prime), and chooses a generator (2, 3 or 5). These
 *	values are then sent to the other party in plain text.
 *
 *	Both parties also calculate large random number, used as their respective secret keys (at least 300 digits).
 *	These must be kept secret (do not send them to the other party).
 *
 *	Then both parties calculate their public key using MHD_PublicKey(). The public key is echanged between
 *	the parties, in plain text.
 *
 *	Finally, both parties use MHD_SharedSecretKey() with their private key and the other partys public key
 *	to generate a shared secret key. This key is the same for both parties. This key must be kept secret.
 *
 *	This key can be used to encrypt session material of any length (use SHA-1 of concatenation of the shared
 *	secret key with a binary counter and possibly some other information to generate enough keying material).
 */

/**
 *	@brief the first part of Merkle-Hellman-Diffie key exchange, generating a public key from secret private key
 *
 *	@param[in] s_common_generator is string containing hexadecimal value of common generator (in practice, value of 2, 3 or 5 is used)
 *	@param[in] s_common_modulus is string containing hexadecimal value of common modulus (large safe prime, at least 100 digits)
 *	@param[in] s_my_private_key is string containing hexadecimal value of private key (random generated, at least 300 digits)
 *
 *	@return Returns a string, containing hexadecimal value of public key.
 *
 *	@note Generator and modulus are typically generated on server and it is ok to transmit them
 *		in plain text (knowledge of these doesn't give away any secrets).
 *	@note The public key is exchanged with the other side (the server) and it is ok to transmit it
 *		in plain text (its knowledge doesn't give away any secrets).
 */
function MHD_PublicKey(s_common_generator, s_common_modulus, s_my_private_key, progress_fun)
{
	return ByteStr_to_XStr(ModuloPower_ByteStr(XStr_to_ByteStr(s_common_generator),
		XStr_to_ByteStr(s_my_private_key), XStr_to_ByteStr(s_common_modulus), progress_fun));
}

/**
 *	@brief the second part of Merkle-Hellman-Diffie key exchange, generating a public key from secret private key
 *
 *	@param[in] s_common_modulus is string containing hexadecimal value of common modulus (the same as in MHD_PublicKey())
 *	@param[in] s_my_private_key is string containing hexadecimal value of private key (the same as in MHD_PublicKey())
 *	@param[in] s_peer_public_key is string containing hexadecimal value of peer public key (which the peers exchanged)
 *
 *	@return Returns a string, containing hexadecimal value of shared secret key. The key is the same for both peers
 *		and it is unknown to the attacker. It can be used for generation of KEK.
 *
 *	@note Generator and modulus are typically generated on server and it is ok to transmit them
 *		in plain text (knowledge of these doesn't give away any secrets).
 *	@note The public key is exchanged with the other side (the server) and it is ok to transmit it
 *		in plain text (its knowledge doesn't give away any secrets).
 */
function MHD_SharedSecretKey(s_common_modulus, s_my_private_key, s_peer_public_key, progress_fun)
{
	return ByteStr_to_XStr(ModuloPower_ByteStr(XStr_to_ByteStr(s_peer_public_key),
		XStr_to_ByteStr(s_my_private_key), XStr_to_ByteStr(s_common_modulus), progress_fun));
}

function MHD_TestInvariants()
{
	if(!BigXInt_TestInvariants())
		return false;
	// big integer library is required

	var s_g = "3";
	var s_m = "10001";
	var s_priv_key_a = "9A2E";
	var s_priv_key_b = "4C20"; // private keys doesn't need to be smaller than m!

	var s_pub_key_a = MHD_PublicKey(s_g, s_m, s_priv_key_a); // c366
	var s_pub_key_b = MHD_PublicKey(s_g, s_m, s_priv_key_b); // 6246

	// exchange public keys

	var s_shared_key_a = MHD_SharedSecretKey(s_m, s_priv_key_a, s_pub_key_b); // ded4
	var s_shared_key_b = MHD_SharedSecretKey(s_m, s_priv_key_b, s_pub_key_a); // ded4

	return s_pub_key_a == "c366" && s_pub_key_b == "6246" &&
		s_shared_key_a == s_shared_key_b && s_shared_key_a == "ded4";
}

function MHD_RandomPrivateKey(n_size, s_random_phrase)
{
	var k = "";
	while(k.length < n_size) {
		var l = "";
		if(k.length == 0) {
			var thresh = (new Date()).getTime() + 10; // can't use much bigger values, the generated string is too long then, hashing takes a lot of time (the block cypher approach would actually limit this better just to chosen interval, no extra memory would be needed)
			{
				l += Math.random();
				l += thresh - 100;
				l += navigator.appCodeName +
					navigator.appName +
					navigator.appVersion +
					navigator.cookieEnabled +
					navigator.platform +
					navigator.userAgent +
					navigator.systemLanguage;
				l += screen.availWidth + screen.availHeight;
				if(s_random_phrase != null)
					l += s_random_phrase;
			}
			// make sure at least two iterations take place

			var tt;
			do {
				tt = new Date().getTime();
				l += Math.random();
				l += tt;
				/*l += navigator.appCodeName +
					navigator.appName +
					navigator.appVersion +
					navigator.cookieEnabled +
					navigator.platform +
					navigator.userAgent +
					navigator.systemLanguage;
				l += screen.availWidth + screen.availHeight;*/ // keep it short
			} while(tt < thresh);
			// make sure that time actually changes, generating additional entropy
		} else {
			l += k.length;
			l += Math.random();
			l += (new Date().getTime());
			l += navigator.appCodeName +
				navigator.appName +
				navigator.appVersion +
				navigator.cookieEnabled +
				navigator.platform +
				navigator.userAgent +
				navigator.systemLanguage;
			l += screen.availWidth + screen.availHeight;
			if(s_random_phrase != null)
				l += s_random_phrase;
			// generate string with a lot of party information that is pottentially unknown to the attacker
		}

		k += (new jsSHA(l, "ASCII")).getHash("HEX");
		// use SHA1 to digest all the random data
	}
	// note that this only generates as safe keys as safe is Math.random (not at all) and
	// as random is the current date (to some extent)
	// it would probably be better to use a block encryption cypher // todo

	return k.substr(0, n_size);
	// return string of desired size
}

function MHD_PerfTest(progress_fun)
{
	//return;

	var s_g = "2";
	var s_m = ("DE9B707D4C5A4633C0290C95FF30A605AEB7AE864FF48370F13CF01D49ADB9F23D19A439F753EE7703CF342D87F431105C843C78CA4DF639931F3458FAE8A94D1687E99A76ED99D0BA87189F42FD31AD8262C54A8CF5914AE6C28C540D714A5F6087A171FB74F4814C6F968D72386EF356A05180C3BEC7DDD5EF6FE76B0531C3").substr(0, 100);
	var s_priv_key_a = MHD_RandomPrivateKey(100);
	var s_priv_key_b = MHD_RandomPrivateKey(100); // private keys doesn't need to be smaller than m!

	var start = new Date();

	var s_pub_key_a = MHD_PublicKey(s_g, s_m, s_priv_key_a, progress_fun);
	var s_pub_key_b = MHD_PublicKey(s_g, s_m, s_priv_key_b, progress_fun);

	// exchange public keys

	var s_shared_key_a = MHD_SharedSecretKey(s_m, s_priv_key_a, s_pub_key_b, progress_fun);
	var s_shared_key_b = MHD_SharedSecretKey(s_m, s_priv_key_b, s_pub_key_a, progress_fun);

	var end = new Date();
	window.alert("it took " + (end.getTime() - start.getTime()) / 1000.0 / 2 + " sec (per one party)");
	window.alert("my private key is " + s_priv_key_a);
	window.alert("shared key is " + s_shared_key_a + " (" + ((s_shared_key_a == s_shared_key_b)? "equals" : "failed") + ")");
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Block TEA (xxtea) Tiny Encryption Algorithm implementation in JavaScript                      */
/*     (c) Chris Veness 2002-2012: www.movable-type.co.uk/tea-block.html                          */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab                   */
/*             http://www.cl.cam.ac.uk/ftp/papers/djw-rmn/djw-rmn-tea.html (1994)                 */
/*             http://www.cl.cam.ac.uk/ftp/users/djw3/xtea.ps (1997)                              */
/*             http://www.cl.cam.ac.uk/ftp/users/djw3/xxtea.ps (1998)                             */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Tea = {};  // Tea namespace

/*
 * encrypt text using Corrected Block TEA (xxtea) algorithm
 *
 * @param {string} plaintext String to be encrypted (multi-byte safe)
 * @param {string} password  Password to be used for encryption (1st 16 chars)
 * @returns {string} encrypted text
 */
Tea.encrypt = function(plaintext, password)
{
	if (plaintext.length == 0) return('');  // nothing to encrypt
	
	// convert string to array of longs after converting any multi-byte chars to UTF-8
	var v = Tea.strToLongs(Utf8.encode(plaintext));
	if (v.length <= 1) v[1] = 0;  // algorithm doesn't work for n<2 so fudge by adding a null
	// simply convert first 16 chars of password as key
	var k = Tea.strToLongs(Utf8.encode(password).slice(0,16));  
	var n = v.length;
	
	// ---- <TEA coding> ---- 
	
	var z = v[n-1], y = v[0], delta = 0x9E3779B9;
	var mx, e, q = Math.floor(6 + 52/n), sum = 0;
	
	while (q-- > 0) {  // 6 + 52/n operations gives between 6 & 32 mixes on each word
		sum += delta;
		e = sum>>>2 & 3;
		for (var p = 0; p < n; p++) {
			y = v[(p+1)%n];
			mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
			z = v[p] += mx;
		}
	}
	
	// ---- </TEA> ----
	
	var ciphertext = Tea.longsToStr(v);
	
	return Base64.encode(ciphertext);
}

/*
 * decrypt text using Corrected Block TEA (xxtea) algorithm
 *
 * @param {string} ciphertext String to be decrypted
 * @param {string} password   Password to be used for decryption (1st 16 chars)
 * @returns {string} decrypted text
 */
Tea.decrypt = function(ciphertext, password)
{
	if (ciphertext.length == 0) return('');
	var v = Tea.strToLongs(Base64.decode(ciphertext));
	var k = Tea.strToLongs(Utf8.encode(password).slice(0,16)); 
	var n = v.length;
	
	// ---- <TEA decoding> ---- 
	
	var z = v[n-1], y = v[0], delta = 0x9E3779B9;
	var mx, e, q = Math.floor(6 + 52/n), sum = q*delta;

	while (sum != 0) {
		e = sum>>>2 & 3;
		for (var p = n-1; p >= 0; p--) {
			z = v[p>0 ? p-1 : n-1];
			mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
			y = v[p] -= mx;
		}
		sum -= delta;
	}
	
	// ---- </TEA> ---- 
	
	var plaintext = Tea.longsToStr(v);

	// strip trailing null chars resulting from filling 4-char blocks:
	plaintext = plaintext.replace(/\0+$/,'');

	return Utf8.decode(plaintext);
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

// supporting functions

Tea.strToLongs = function(s) // convert string to array of longs, each containing 4 chars
{
	// note chars must be within ISO-8859-1 (with Unicode code-point < 256) to fit 4/long
	var l = new Array(Math.ceil(s.length/4));
	for (var i=0; i<l.length; i++) {
		// note little-endian encoding - endianness is irrelevant as long as 
		// it is the same in longsToStr() 
		l[i] = s.charCodeAt(i*4) + (s.charCodeAt(i*4+1)<<8) + 
			   (s.charCodeAt(i*4+2)<<16) + (s.charCodeAt(i*4+3)<<24);
	}
	return l;  // note running off the end of the string generates nulls since 
}              // bitwise operators treat NaN as 0

Tea.longsToStr = function(l) // convert array of longs back to string
{
	var a = new Array(l.length);
	for (var i=0; i<l.length; i++) {
		a[i] = String.fromCharCode(l[i] & 0xFF, l[i]>>>8 & 0xFF, 
								   l[i]>>>16 & 0xFF, l[i]>>>24 & 0xFF);
	}
	return a.join('');  // use Array.join() rather than repeated string appends for efficiency in IE
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Base64 class: Base 64 encoding / decoding (c) Chris Veness 2002-2012                          */
/*    note: depends on Utf8 class                                                                 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Base64 = {};  // Base64 namespace

//Base64.code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
Base64.code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"; // -tHE SWINe- only chars valid in URL (the same scheme as musicbrainz)

/**
 * Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
 * (instance method extending String object). As per RFC 4648, no newlines are added.
 *
 * @param {String} str The string to be encoded as base-64
 * @param {Boolean} [utf8encode=false] Flag to indicate whether str is Unicode string to be encoded 
 *   to UTF8 before conversion to base64; otherwise string is assumed to be 8-bit characters
 * @returns {String} Base64-encoded string
 */ 
Base64.encode = function(str, utf8encode) // http://tools.ietf.org/html/rfc4648
{
	utf8encode =  (typeof utf8encode == 'undefined') ? false : utf8encode;
	var o1, o2, o3, bits, h1, h2, h3, h4, e=[], pad = '', c, plain, coded;
	var b64 = Base64.code;

	plain = utf8encode ? Utf8.encode(str) : str;

	c = plain.length % 3;  // pad string to length of multiple of 3
	if (c > 0) { while (c++ < 3) { pad += '-'; plain += '\0'; } }	// -tHE SWINe- only chars valid in URL (the same scheme as musicbrainz)
	// note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars

	for (c=0; c<plain.length; c+=3) {  // pack three octets into four hexets
		o1 = plain.charCodeAt(c);
		o2 = plain.charCodeAt(c+1);
		o3 = plain.charCodeAt(c+2);
		  
		bits = o1<<16 | o2<<8 | o3;
		  
		h1 = bits>>18 & 0x3f;
		h2 = bits>>12 & 0x3f;
		h3 = bits>>6 & 0x3f;
		h4 = bits & 0x3f;

		// use hextets to index into code string
		e[c/3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
	}
	coded = e.join('');  // join() is far faster than repeated string concatenation in IE

	// replace 'A's from padded nulls with '='s
	coded = coded.slice(0, coded.length-pad.length) + pad;

	return coded;
}

/**
 * Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
 * (instance method extending String object). As per RFC 4648, newlines are not catered for.
 *
 * @param {String} str The string to be decoded from base-64
 * @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded 
 *   from UTF8 after conversion from base64
 * @returns {String} decoded string
 */ 
Base64.decode = function(str, utf8decode)
{
	utf8decode =  (typeof utf8decode == 'undefined') ? false : utf8decode;
	var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded;
	var b64 = Base64.code;

	coded = utf8decode ? Utf8.decode(str) : str;

	for (var c=0; c<coded.length; c+=4) {  // unpack four hexets into three octets
		h1 = b64.indexOf(coded.charAt(c));
		h2 = b64.indexOf(coded.charAt(c+1));
		h3 = b64.indexOf(coded.charAt(c+2));
		h4 = b64.indexOf(coded.charAt(c+3));
		  
		bits = h1<<18 | h2<<12 | h3<<6 | h4;
		  
		o1 = bits>>>16 & 0xff;
		o2 = bits>>>8 & 0xff;
		o3 = bits & 0xff;

		if(h4 == 0x40)
			d[c/4] = String.fromCharCode(o1, o2);	// -tHE SWINe- this is supposed to be 0x40, this is after index decoding
		else if(h3 == 0x40)
			d[c/4] = String.fromCharCode(o1);		// -tHE SWINe- this is supposed to be 0x40, this is after index decoding
		else
			d[c/4] = String.fromCharCode(o1, o2, o3);
		// check for padding
	}
	plain = d.join('');  // join() is far faster than repeated string concatenation in IE

	return utf8decode ? Utf8.decode(plain) : plain; 
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
/*              single-byte character encoding (c) Chris Veness 2002-2012                         */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Utf8 = {};  // Utf8 namespace

/**
 * Encode multi-byte Unicode string into utf-8 multiple single-byte characters 
 * (BMP / basic multilingual plane only)
 *
 * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
 *
 * @param {String} strUni Unicode string to be encoded as UTF-8
 * @returns {String} encoded string
 */
Utf8.encode = function(strUni)
{
	// use regular expressions & String.replace callback function for better efficiency 
	// than procedural approaches
	var strUtf = strUni.replace(
		/[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
		function(c) { 
			var cc = c.charCodeAt(0);
			return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f);
		});
	strUtf = strUtf.replace(
		/[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
		function(c) { 
			var cc = c.charCodeAt(0); 
			return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f);
		});
	return strUtf;
}

/**
 * Decode utf-8 encoded string back into multi-byte Unicode characters
 *
 * @param {String} strUtf UTF-8 string to be decoded back to Unicode
 * @returns {String} decoded string
 */
Utf8.decode = function(strUtf)
{
	// note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
	var strUni = strUtf.replace(
		/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
		function(c) {  // (note parentheses for precence)
			var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f); 
			return String.fromCharCode(cc);
		});
	strUni = strUni.replace(
		/[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
		function(c) {  // (note parentheses for precence)
			var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
			return String.fromCharCode(cc);
		});
	return strUni;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/**
 *  @file thirdparty.js
 *  @brief third party licenses
 *  @date 2015
 */

/**
 *  @brief gets jsSHA license text
 */
function s_jsSHA_License()
{
    return
        "Copyright (c) 2008-2012, Brian Turek\n" +
        "All rights reserved.\n" +
        "\n" +
        "Redistribution and use in source and binary forms, with or without\n" +
        "modification, are permitted provided that the following conditions are met:\n" +
        "\n" +
        " * Redistributions of source code must retain the above copyright notice, this\n" +
        "   list of conditions and the following disclaimer.\n" +
        " * Redistributions in binary form must reproduce the above copyright notice,\n" +
        "   this list of conditions and the following disclaimer in the documentation\n" +
        "   and/or other materials provided with the distribution.\n" +
        " * The names of the contributors may not be used to endorse or promote products\n" +
        "   derived from this software without specific prior written permission.\n" +
        "\n" +
        "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n" +
        "ANDANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n" +
        "IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n" +
        "DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n" +
        "ANY DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n" +
        "(INCLUDING,BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n" +
        " LOSS OF USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n" +
        "ANY THEORY OFLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" +
        "(INCLUDING NEGLIGENCEOR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n" +
        "SOFTWARE, EVEN IF ADVISEDOF THE POSSIBILITY OF SUCH DAMAGE.";
}

/**
 *  @brief gets license disclaimers HTML code
 */
function s_Licenses_HTML()
{
    return '<ul>' +
        '<li><a href="http://caligatio.github.com/jsSHA/" onclick="window.open(this.href); return false;">jsSHA</a> by Brian Turek (<a href="jsSHA_LICENSE.txt" onclick="window.alert(s_jsSHA_License()); return false;">license</a>)</li>' +
        '<li><a href="http://www.movable-type.co.uk/scripts/tea-block.html" onclick="window.open(this.href); return false;">XTEA</a> by Chris Veness (<a href="http://creativecommons.org/licenses/by/3.0/" onclick="window.open(this.href); return false;">license</a>).</li>' +
        '</ul>';
}

/**
 *	@file utils.js
 *	@brief miscelaneous utilities
 *	@date 2012
 *	@author -tHE SWINe-
 */

function f_Mod(f,m)
{
	return f - Math.floor(f / m) * m;
}

function s_StrTime(f_time)
{
	var seconds = f_Mod(f_time, 60);
	f_time -= seconds;
	f_time /= 60;
	var minutes = f_Mod(f_time, 60);
	f_time -= minutes;
	f_time /= 60;
	var hours = f_time;
	// divide to sec / 

	seconds = "" + seconds;
	if(seconds.indexOf(".") == -1)
		seconds += ".0";
	seconds = seconds.substr(0, seconds.indexOf(".") + 2);
	if(seconds.indexOf(".") == 1)
		seconds = "0" + seconds;
	minutes = "" + minutes;
	if(minutes.length == 1)
		minutes = "0" + minutes;
	hours = "" + hours;
	if(hours.length == 1)
		hours = "0" + hours;

	return hours + ":" + minutes + ":" + seconds;
}

function sleep(ms)
{
	var url = "http://127.0.0.1:9999/dyn/sleep?ms=" + ms;
	try {
		var http;
		if(window.XMLHttpRequest)
			http = new XMLHttpRequest();
		else
			http = new ActiveXObject("Microsoft.XMLHTTP");
		http.open("GET", url, false);
		http.send();
		return http.responseText;
	} catch(ex) {
	}
}

/**
 *	@file lauth.js
 *	@brief lame user authentication system
 *	@date 2012
 *	@author -tHE SWINe-
 */

// todo - convert this to an object, hide any state inside

var b_logged_in = false;
var s_username = "", s_password = "";
// user credentials

var s_nonce_value = "";
var f_nonce_thresh_msec = 0;
var t_nonce_last_update = new Date(0); // = 1970-01-01
// nonce for enhanced HMAC

function t_Load_XMLDoc(s_file_uri)
{
	var xhttp;
	if(window.XMLHttpRequest)
		xhttp = new XMLHttpRequest();
	else
		xhttp = new ActiveXObject("Microsoft.XMLHTTP");
	xhttp.open("GET", s_file_uri, false);
	if(window.XMLHttpRequest) { // disable caching
		xhttp.setRequestHeader("Cache-Control", "no-cache");
	 	xhttp.setRequestHeader("Pragma", "no-cache");
	 	xhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
	}
	xhttp.send();

	if(xhttp.responseXML == null)
		window.alert("Unable to connect to server, check your connection.");

	if(window.DOMParser)
		return xhttp.responseXML;
	// current browsers

	if(xhttp.responseText == null)
		return null;
	xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
	xmlDoc.async = false;
	xmlDoc.loadXML(xhttp.responseText);
	return xmlDoc;
	// IE 5
}

function t_Touch_XMLDoc(s_file_uri)
{
	var xhttp;
	if(window.XMLHttpRequest)
		xhttp = new XMLHttpRequest();
	else
		xhttp = new ActiveXObject("Microsoft.XMLHTTP");
	xhttp.open("HEAD", s_file_uri, false);
	if(window.XMLHttpRequest) { // disable caching
		xhttp.setRequestHeader("Cache-Control", "no-cache");
	 	xhttp.setRequestHeader("Pragma", "no-cache");
	 	xhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
	}
	xhttp.send();
	// just touch it, nothing more ...
}

function s_Nonce_Value()
{
	var t_now = new Date();
	if(t_now.getTime() - t_nonce_last_update.getTime() > f_nonce_thresh_msec) {
		var xmlDoc = t_Load_XMLDoc("_lauth/nonce.xml");
		var xmlTimeSliceId = xmlDoc.getElementsByTagName("nonce")[0];
		var s_time_slice_id = xmlTimeSliceId.childNodes[0].nodeValue;
		var s_valid_msec = xmlTimeSliceId.getAttribute("valid_msec");
		s_nonce_value = s_time_slice_id.trim(); // remember the nonce
		f_nonce_thresh_msec = parseFloat(s_valid_msec); // remember for how long is it valid
		t_nonce_last_update = t_now; // remember when was it last updated (time of request, on purpose)
	}
	return s_nonce_value;
}

/*function n_Rotate16(i, n) // rotates a 16-bit number
{
	return ((i << n) | (i >> n)) % 65536;
}*/

function s_SaltPassword(_s_password)
{
	/*var s_salted_password = (new jsSHA(_s_password + "&nonce=" + s_Nonce_Value(), "ASCII")).getHash("HEX");
	//window.alert("nonce = " + s_Nonce_Value() + ", pwd = " + _s_password + ", hash = " + s_salted_password);
	for(var i = 0; i < 997; ++ i) // note that 997 is a prime
		s_salted_password = (new jsSHA(s_salted_password + n_Rotate16(i, 10), "ASCII")).getHash("HEX"); // we can make it harder this way
	// use a salted password for higher security*/

	var s_salted_password = (new jsSHA(s_Nonce_Value(), "HEX")).getHMAC(_s_password, "ASCII", "HEX");
	var s_prev = s_salted_password;
	for(var i = 0; i < 347; ++ i) { // note that 347 is a prime
		var s_next = (new jsSHA(s_prev, "HEX")).getHMAC(_s_password, "ASCII", "HEX");
		var s_xor = "";
		for(var j = 0, m = s_next.length; j < m; ++ j) { // todo - use bigint to do this
			var a = parseInt(s_salted_password.charAt(j), 16), b = parseInt(s_next.charAt(j), 16);
			var c = a ^ b;
			s_xor += c.toString(16);
		}
		s_salted_password = s_xor;
		s_prev = s_next;
	}
	// use hmac iterations to salt the password

	//window.alert("nonce = " + s_Nonce_Value() + ", salted pwd = " + s_salted_password);

	return s_salted_password;
}

function t_Load_Authenticated_XMLDoc(s_file_uri, _s_username, _s_password)
{
	if(_s_username == null || _s_password == null) {
		if(!b_logged_in) {
			window.alert("t_Load_Authenticated_XMLDoc() while not logged in");
			return null;
		}
		_s_username = s_username;
		_s_password = s_password;
	}

	var s_auth_request;
	if(s_file_uri.indexOf('?') >= 0)
		s_auth_request = s_file_uri + "&username=" + _s_username;
	else
		s_auth_request = s_file_uri + "?username=" + _s_username;
	// create authenticated request

	var s_salted_password = s_SaltPassword(_s_password);
	// use a salted password for higher security

	var hmac = (new jsSHA(s_auth_request, "ASCII")).getHMAC(s_salted_password, "ASCII", "B64");
	// calculate HMAC for the request

	// note that if you are eavesdropping, you can mount a brute-force attack on salted passwords
	// (you know what login is, just calculate hmac on every possible hex hash and there you are)
	//
	// the nonce is making it harder as if you know the hash of the password and nonce,
	// it is not very useful in the long run (low probability that the nonce will be used again)
	//
	// the cost of calculating plain password from salted password is the cost of another 350
	// SHA-1 HMAC brute-force attacks (well, ...)
	//
	// it is still vulnerable against dictionary attacks, as that way it is possible to guess
	// the password directly (not just salted one). this depends on the users to choose good
	// passwords.

	s_auth_request += "&hmac=" + hmac;
	// add it to the request

	return t_Load_XMLDoc(s_auth_request);
}

function t_Touch_Authenticated_XMLDoc(s_file_uri, _s_username, _s_password)
{
	if(_s_username == null || _s_password == null) {
		if(!b_logged_in) {
			window.alert("t_Load_Authenticated_XMLDoc() while not logged in");
			return null;
		}
		_s_username = s_username;
		_s_password = s_password;
	}

	var s_auth_request;
	if(s_file_uri.indexOf('?') >= 0)
		s_auth_request = s_file_uri + "&username=" + _s_username;
	else
		s_auth_request = s_file_uri + "?username=" + _s_username;
	// create authenticated request

	var s_salted_password = s_SaltPassword(_s_password);
	// use a salted password for higher security

	var hmac = (new jsSHA(s_auth_request, "ASCII")).getHMAC(s_salted_password, "ASCII", "B64");
	// calculate HMAC for the request

	// note that if you are eavesdropping, you can mount a brute-force attack on salted passwords
	// (you know what login is, just calculate hmac on every possible hex hash and there you are)
	//
	// the nonce is making it harder as if you know the hash of the password and nonce,
	// it is not very useful in the long run (low probability that the nonce will be used again)
	//
	// the cost of calculating plain password from salted password is the cost of another 350
	// SHA-1 HMAC brute-force attacks (well, ...)
	//
	// it is still vulnerable against dictionary attacks, as that way it is possible to guess
	// the password directly (not just salted one). this depends on the users to choose good
	// passwords.

	s_auth_request += "&hmac=" + hmac;
	// add it to the request

	t_Touch_XMLDoc(s_auth_request);
}

function b_IsValidUsername(_s_username)
{
	if(_s_username.length < 4)
		return false;
	for(var i = 0, n = _s_username.length; i < n; ++ i) {
		var c = _s_username.charCodeAt(i);
		if(c == '_'.charCodeAt(0))
			continue;
		if(c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0))
			continue;
		if(c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0))
			continue;
		if(c >= 'A'.charCodeAt(0) && c <= 'Z'.charCodeAt(0))
			continue;
		return false; // a character that is not permitted
	}
	return true;
}

function b_IsUsernameAvailable(_s_username)
{
	if(!b_IsValidLogin(_s_username))
		return false; // don't even bother
	var login_avail = t_Load_XMLDoc("_lauth/login-availability.xml?login=" + _s_username);
	return login_avail.getElementsByTagName("available")[0].childNodes[0].nodeValue == "true";
}

function s_FourCC(four_cc)
{
	var x = "";
	x += ByteStr_to_XStr(Number_to_ByteStr(four_cc.charCodeAt(0)));
	x += ByteStr_to_XStr(Number_to_ByteStr(four_cc.charCodeAt(1)));
	x += ByteStr_to_XStr(Number_to_ByteStr(four_cc.charCodeAt(2)));
	x += ByteStr_to_XStr(Number_to_ByteStr(four_cc.charCodeAt(3)));
	return x;
}

function s_Encrypt(plaintext, password)
{
	return Tea.encrypt(plaintext, password);
}

function secondsToDate(n_seconds) // converts time in seconds since 1970-01-01 to a Date object
{
	return new Date(n_seconds * 1000); // wow, this actually *works*
}

function dateId(year, month, monthDay) // converts year, month, monthDay from numbers to a "yyyy-mm-dd" string
{
	dayStr = "" + monthDay;
	if(dayStr.length < 2)
		dayStr = "0" + dayStr;
	monthStr = "" + (month + 1);
	if(monthStr.length < 2)
		monthStr = "0" + monthStr;
	return year + "-" + monthStr + "-" + dayStr;
}

function dateId2(date) // converts a Date object to a "yyyy-mm-dd" string
{
	return dateId(date.getFullYear(), date.getMonth(), date.getDate());
}

function dateToUnixString(date) // converts a Date object to unix time ("yyyy-mm-ddThh:mm:ss.ss")
{
	var hourStr = "" + date.getHours();
	if(hourStr.length < 2)
		hourStr = "0" + hourStr;
	var minuteStr = "" + date.getMinutes();
	if(minuteStr.length < 2)
		minuteStr = "0" + minuteStr;
	var secondStr = "" + date.getSeconds();
	if(secondStr.length < 2)
		secondStr = "0" + secondStr;
	var millsStr = "00";
	return dateId2(date) + "T" + hourStr + ":" + minuteStr + ":" + secondStr + "." + millsStr;
}

function shortUnixDate(dateStr) // converts unix time string to just a date (time part is ommited)
{
	if(dateStr.indexOf("T") != -1)
		return dateStr.substr(0, dateStr.indexOf("T"));
	return dateStr;
}

function parseTimeValue(s_timeval) // parses hh:mm:ss // todo - support just "mm:ss" or "nn unit" (e.g. "50 sec", "10 min")
{
	var b = 0, e = s_timeval.length;

	var n_hour_start = b;
	while(b < e && s_timeval.charCodeAt(b) >= "0".charCodeAt(0) && s_timeval.charCodeAt(b) <= "9".charCodeAt(0))
		++ b;
	var n_hour_end = b;
	// read hour

	if(b == e || s_timeval.charAt(b) != ':')
		return null;
	++ b;
	// skip ':'

	var n_minute_start = b;
	while(b < e && s_timeval.charCodeAt(b) >= "0".charCodeAt(0) && s_timeval.charCodeAt(b) <= "9".charCodeAt(0))
		++ b;
	var n_minute_end = b;
	// read minute

	if(b == e || s_timeval.charAt(b) != ':')
		return null;
	++ b;
	// skip ':'

	var n_second_start = b;
	while(b < e && s_timeval.charCodeAt(b) >= "0".charCodeAt(0) && s_timeval.charCodeAt(b) <= "9".charCodeAt(0))
		++ b;
	var n_second_end = b;
	// read second

	if(b != e)
		return null;
	// make sure that's it

	var h = parseInt(s_timeval.substr(n_hour_start, n_hour_end - n_hour_start), 10);
	var m = parseInt(s_timeval.substr(n_minute_start, n_minute_end - n_minute_start), 10);
	var s = parseInt(s_timeval.substr(n_second_start, n_second_end - n_second_start), 10);
	if(h < 0 || m < 0 || m >= 60 || s < 0 || s >= 60)
		return null;
	// parse and sanity check

	return ((h * 60) + m) * 60 + s;
	// convert to seconds
}

// TODOs
// convert stuff to js classes
// test with chrome
// think about adding error messages, mostly the xml loading functions return empty string (server closes connection), or contain the user-readable html response (http 400: don't come near there) - detect that and do window.alert
// make registrations work
// 
