| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** @fileoverview Various string utility functions */ |
| 'use strict'; |
| |
| /** |
| * Converts a string to an array of bytes. |
| * @param {string} s The string to convert. |
| * @param {(Array|Uint8Array)=} bytes The Array-like object into which to store |
| * the bytes. A new Array will be created if not provided. |
| * @return {(Array|Uint8Array)} An array of bytes representing the string. |
| */ |
| function UTIL_StringToBytes(s, bytes) { |
| bytes = bytes || new Array(s.length); |
| for (var i = 0; i < s.length; ++i) |
| bytes[i] = s.charCodeAt(i); |
| return bytes; |
| } |
| |
| /** |
| * Converts a byte array to a string. |
| * @param {(Uint8Array|Array<number>)} b input byte array. |
| * @return {string} result. |
| */ |
| function UTIL_BytesToString(b) { |
| return String.fromCharCode.apply(null, b); |
| } |
| |
| /** |
| * Converts a byte array to a hex string. |
| * @param {(Uint8Array|Array<number>)} b input byte array. |
| * @return {string} result. |
| */ |
| function UTIL_BytesToHex(b) { |
| if (!b) |
| return '(null)'; |
| var hexchars = '0123456789ABCDEF'; |
| var hexrep = new Array(b.length * 2); |
| |
| for (var i = 0; i < b.length; ++i) { |
| hexrep[i * 2 + 0] = hexchars.charAt((b[i] >> 4) & 15); |
| hexrep[i * 2 + 1] = hexchars.charAt(b[i] & 15); |
| } |
| return hexrep.join(''); |
| } |
| |
| function UTIL_BytesToHexWithSeparator(b, sep) { |
| var hexchars = '0123456789ABCDEF'; |
| var stride = 2 + (sep ? 1 : 0); |
| var hexrep = new Array(b.length * stride); |
| |
| for (var i = 0; i < b.length; ++i) { |
| if (sep) |
| hexrep[i * stride + 0] = sep; |
| hexrep[i * stride + stride - 2] = hexchars.charAt((b[i] >> 4) & 15); |
| hexrep[i * stride + stride - 1] = hexchars.charAt(b[i] & 15); |
| } |
| return (sep ? hexrep.slice(1) : hexrep).join(''); |
| } |
| |
| function UTIL_HexToBytes(h) { |
| var hexchars = '0123456789ABCDEFabcdef'; |
| var res = new Uint8Array(h.length / 2); |
| for (var i = 0; i < h.length; i += 2) { |
| if (hexchars.indexOf(h.substring(i, i + 1)) == -1) |
| break; |
| res[i / 2] = parseInt(h.substring(i, i + 2), 16); |
| } |
| return res; |
| } |
| |
| function UTIL_HexToArray(h) { |
| var hexchars = '0123456789ABCDEFabcdef'; |
| var res = new Array(h.length / 2); |
| for (var i = 0; i < h.length; i += 2) { |
| if (hexchars.indexOf(h.substring(i, i + 1)) == -1) |
| break; |
| res[i / 2] = parseInt(h.substring(i, i + 2), 16); |
| } |
| return res; |
| } |
| |
| function UTIL_equalArrays(a, b) { |
| if (!a || !b) |
| return false; |
| if (a.length != b.length) |
| return false; |
| var accu = 0; |
| for (var i = 0; i < a.length; ++i) |
| accu |= a[i] ^ b[i]; |
| return accu === 0; |
| } |
| |
| function UTIL_ltArrays(a, b) { |
| if (a.length < b.length) |
| return true; |
| if (a.length > b.length) |
| return false; |
| for (var i = 0; i < a.length; ++i) { |
| if (a[i] < b[i]) |
| return true; |
| if (a[i] > b[i]) |
| return false; |
| } |
| return false; |
| } |
| |
| function UTIL_gtArrays(a, b) { |
| return UTIL_ltArrays(b, a); |
| } |
| |
| function UTIL_geArrays(a, b) { |
| return !UTIL_ltArrays(a, b); |
| } |
| |
| function UTIL_unionArrays(a, b) { |
| var obj = {}; |
| for (var i = 0; i < a.length; i++) { |
| obj[a[i]] = a[i]; |
| } |
| for (var i = 0; i < b.length; i++) { |
| obj[b[i]] = b[i]; |
| } |
| var union = []; |
| for (var k in obj) { |
| union.push(obj[k]); |
| } |
| return union; |
| } |
| |
| function UTIL_getRandom(a) { |
| var tmp = new Array(a); |
| var rnd = new Uint8Array(a); |
| window.crypto.getRandomValues(rnd); // Yay! |
| for (var i = 0; i < a; ++i) |
| tmp[i] = rnd[i] & 255; |
| return tmp; |
| } |
| |
| function UTIL_setFavicon(icon) { |
| // Construct a new favion link tag |
| var faviconLink = document.createElement('link'); |
| faviconLink.rel = 'Shortcut Icon'; |
| faviconLink.type = 'image/x-icon'; |
| faviconLink.href = icon; |
| |
| // Remove the old favion, if it exists |
| var head = document.getElementsByTagName('head')[0]; |
| var links = head.getElementsByTagName('link'); |
| for (var i = 0; i < links.length; i++) { |
| var link = links[i]; |
| if (link.type == faviconLink.type && link.rel == faviconLink.rel) { |
| head.removeChild(link); |
| } |
| } |
| |
| // Add in the new one |
| head.appendChild(faviconLink); |
| } |
| |
| // Erase all entries in array |
| function UTIL_clear(a) { |
| if (a instanceof Array) { |
| for (var i = 0; i < a.length; ++i) |
| a[i] = 0; |
| } |
| } |
| |
| // Type tags used for ASN.1 encoding of ECDSA signatures |
| /** @const */ |
| var UTIL_ASN_INT = 0x02; |
| /** @const */ |
| var UTIL_ASN_SEQUENCE = 0x30; |
| |
| /** |
| * Parse SEQ(INT, INT) from ASN1 byte array. |
| * @param {(Uint8Array|Array<number>)} a input to parse from. |
| * @return {{'r': !Array<number>, 's': !Array<number>}|null} |
| */ |
| function UTIL_Asn1SignatureToJson(a) { |
| if (a.length < 6) |
| return null; // Too small to be valid |
| if (a[0] != UTIL_ASN_SEQUENCE) |
| return null; |
| var l = a[1] & 255; |
| if (l & 0x80) |
| return null; // SEQ.size too large |
| if (a.length != 2 + l) |
| return null; // SEQ size does not match input |
| |
| function parseInt(off) { |
| if (a[off] != UTIL_ASN_INT) |
| return null; |
| var l = a[off + 1] & 255; |
| if (l & 0x80) |
| return null; // INT.size too large |
| if (off + 2 + l > a.length) |
| return null; // Out of bounds |
| return a.slice(off + 2, off + 2 + l); |
| } |
| |
| var r = parseInt(2); |
| if (!r) |
| return null; |
| |
| var s = parseInt(2 + 2 + r.length); |
| if (!s) |
| return null; |
| |
| return {'r': r, 's': s}; |
| } |
| |
| /** |
| * Encode a JSON signature {r,s} as an ASN1 SEQ(INT, INT). May modify sig |
| * @param {{'r': (!Array<number>|undefined), 's': !Array<number>}} sig |
| * @return {!Uint8Array} |
| */ |
| function UTIL_JsonSignatureToAsn1(sig) { |
| var rbytes = sig.r; |
| var sbytes = sig.s; |
| |
| // ASN.1 integers are arbitrary length msb first and signed. |
| // sig.r and sig.s are 256 bits msb first but _unsigned_, so we must |
| // prepend a zero byte in case their high bit is set. |
| if (rbytes[0] & 0x80) |
| rbytes.unshift(0); |
| if (sbytes[0] & 0x80) |
| sbytes.unshift(0); |
| |
| var len = 4 + rbytes.length + sbytes.length; |
| var buf = new Uint8Array(2 + len); |
| var i = 0; |
| buf[i++] = UTIL_ASN_SEQUENCE; |
| buf[i++] = len; |
| |
| buf[i++] = UTIL_ASN_INT; |
| buf[i++] = rbytes.length; |
| buf.set(rbytes, i); |
| i += rbytes.length; |
| |
| buf[i++] = UTIL_ASN_INT; |
| buf[i++] = sbytes.length; |
| buf.set(sbytes, i); |
| |
| return buf; |
| } |
| |
| function UTIL_prepend_zero(s, n) { |
| if (s.length == n) |
| return s; |
| var l = s.length; |
| for (var i = 0; i < n - l; ++i) { |
| s = '0' + s; |
| } |
| return s; |
| } |
| |
| // hr:min:sec.milli string |
| function UTIL_time() { |
| var d = new Date(); |
| var m = UTIL_prepend_zero((d.getMonth() + 1).toString(), 2); |
| var t = UTIL_prepend_zero(d.getDate().toString(), 2); |
| var H = UTIL_prepend_zero(d.getHours().toString(), 2); |
| var M = UTIL_prepend_zero(d.getMinutes().toString(), 2); |
| var S = UTIL_prepend_zero(d.getSeconds().toString(), 2); |
| var L = UTIL_prepend_zero((d.getMilliseconds() * 1000).toString(), 6); |
| return m + t + ' ' + H + ':' + M + ':' + S + '.' + L; |
| } |
| |
| var UTIL_events = []; |
| var UTIL_max_events = 500; |
| |
| function UTIL_fmt(s) { |
| var line = UTIL_time() + ': ' + s; |
| if (UTIL_events.push(line) > UTIL_max_events) { |
| // Drop from head. |
| UTIL_events.splice(0, UTIL_events.length - UTIL_max_events); |
| } |
| return line; |
| } |