/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as strings from './strings.js'; /** * Return a hash value for an object. */ export function hash(obj) { return doHash(obj, 0); } export function doHash(obj, hashVal) { switch (typeof obj) { case 'object': if (obj === null) { return numberHash(349, hashVal); } else if (Array.isArray(obj)) { return arrayHash(obj, hashVal); } return objectHash(obj, hashVal); case 'string': return stringHash(obj, hashVal); case 'boolean': return booleanHash(obj, hashVal); case 'number': return numberHash(obj, hashVal); case 'undefined': return numberHash(937, hashVal); default: return numberHash(617, hashVal); } } export function numberHash(val, initialHashVal) { return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32 } function booleanHash(b, initialHashVal) { return numberHash(b ? 433 : 863, initialHashVal); } export function stringHash(s, hashVal) { hashVal = numberHash(149417, hashVal); for (let i = 0, length = s.length; i < length; i++) { hashVal = numberHash(s.charCodeAt(i), hashVal); } return hashVal; } function arrayHash(arr, initialHashVal) { initialHashVal = numberHash(104579, initialHashVal); return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal); } function objectHash(obj, initialHashVal) { initialHashVal = numberHash(181387, initialHashVal); return Object.keys(obj).sort().reduce((hashVal, key) => { hashVal = stringHash(key, hashVal); return doHash(obj[key], hashVal); }, initialHashVal); } function leftRotate(value, bits, totalBits = 32) { // delta + bits = totalBits const delta = totalBits - bits; // All ones, expect `delta` zeros aligned to the right const mask = ~((1 << delta) - 1); // Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits) return ((value << bits) | ((mask & value) >>> delta)) >>> 0; } function fill(dest, index = 0, count = dest.byteLength, value = 0) { for (let i = 0; i < count; i++) { dest[index + i] = value; } } function leftPad(value, length, char = '0') { while (value.length < length) { value = char + value; } return value; } export function toHexString(bufferOrValue, bitsize = 32) { if (bufferOrValue instanceof ArrayBuffer) { return Array.from(new Uint8Array(bufferOrValue)).map(b => b.toString(16).padStart(2, '0')).join(''); } return leftPad((bufferOrValue >>> 0).toString(16), bitsize / 4); } /** * A SHA1 implementation that works with strings and does not allocate. */ export class StringSHA1 { constructor() { this._h0 = 0x67452301; this._h1 = 0xEFCDAB89; this._h2 = 0x98BADCFE; this._h3 = 0x10325476; this._h4 = 0xC3D2E1F0; this._buff = new Uint8Array(64 /* SHA1Constant.BLOCK_SIZE */ + 3 /* to fit any utf-8 */); this._buffDV = new DataView(this._buff.buffer); this._buffLen = 0; this._totalLen = 0; this._leftoverHighSurrogate = 0; this._finished = false; } update(str) { const strLen = str.length; if (strLen === 0) { return; } const buff = this._buff; let buffLen = this._buffLen; let leftoverHighSurrogate = this._leftoverHighSurrogate; let charCode; let offset; if (leftoverHighSurrogate !== 0) { charCode = leftoverHighSurrogate; offset = -1; leftoverHighSurrogate = 0; } else { charCode = str.charCodeAt(0); offset = 0; } while (true) { let codePoint = charCode; if (strings.isHighSurrogate(charCode)) { if (offset + 1 < strLen) { const nextCharCode = str.charCodeAt(offset + 1); if (strings.isLowSurrogate(nextCharCode)) { offset++; codePoint = strings.computeCodePoint(charCode, nextCharCode); } else { // illegal => unicode replacement character codePoint = 65533 /* SHA1Constant.UNICODE_REPLACEMENT */; } } else { // last character is a surrogate pair leftoverHighSurrogate = charCode; break; } } else if (strings.isLowSurrogate(charCode)) { // illegal => unicode replacement character codePoint = 65533 /* SHA1Constant.UNICODE_REPLACEMENT */; } buffLen = this._push(buff, buffLen, codePoint); offset++; if (offset < strLen) { charCode = str.charCodeAt(offset); } else { break; } } this._buffLen = buffLen; this._leftoverHighSurrogate = leftoverHighSurrogate; } _push(buff, buffLen, codePoint) { if (codePoint < 0x0080) { buff[buffLen++] = codePoint; } else if (codePoint < 0x0800) { buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6); buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); } else if (codePoint < 0x10000) { buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12); buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); } else { buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18); buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12); buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); } if (buffLen >= 64 /* SHA1Constant.BLOCK_SIZE */) { this._step(); buffLen -= 64 /* SHA1Constant.BLOCK_SIZE */; this._totalLen += 64 /* SHA1Constant.BLOCK_SIZE */; // take last 3 in case of UTF8 overflow buff[0] = buff[64 /* SHA1Constant.BLOCK_SIZE */ + 0]; buff[1] = buff[64 /* SHA1Constant.BLOCK_SIZE */ + 1]; buff[2] = buff[64 /* SHA1Constant.BLOCK_SIZE */ + 2]; } return buffLen; } digest() { if (!this._finished) { this._finished = true; if (this._leftoverHighSurrogate) { // illegal => unicode replacement character this._leftoverHighSurrogate = 0; this._buffLen = this._push(this._buff, this._buffLen, 65533 /* SHA1Constant.UNICODE_REPLACEMENT */); } this._totalLen += this._buffLen; this._wrapUp(); } return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4); } _wrapUp() { this._buff[this._buffLen++] = 0x80; fill(this._buff, this._buffLen); if (this._buffLen > 56) { this._step(); fill(this._buff); } // this will fit because the mantissa can cover up to 52 bits const ml = 8 * this._totalLen; this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false); this._buffDV.setUint32(60, ml % 4294967296, false); this._step(); } _step() { const bigBlock32 = StringSHA1._bigBlock32; const data = this._buffDV; for (let j = 0; j < 64 /* 16*4 */; j += 4) { bigBlock32.setUint32(j, data.getUint32(j, false), false); } for (let j = 64; j < 320 /* 80*4 */; j += 4) { bigBlock32.setUint32(j, leftRotate((bigBlock32.getUint32(j - 12, false) ^ bigBlock32.getUint32(j - 32, false) ^ bigBlock32.getUint32(j - 56, false) ^ bigBlock32.getUint32(j - 64, false)), 1), false); } let a = this._h0; let b = this._h1; let c = this._h2; let d = this._h3; let e = this._h4; let f, k; let temp; for (let j = 0; j < 80; j++) { if (j < 20) { f = (b & c) | ((~b) & d); k = 0x5A827999; } else if (j < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; } else if (j < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; } else { f = b ^ c ^ d; k = 0xCA62C1D6; } temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff; e = d; d = c; c = leftRotate(b, 30); b = a; a = temp; } this._h0 = (this._h0 + a) & 0xffffffff; this._h1 = (this._h1 + b) & 0xffffffff; this._h2 = (this._h2 + c) & 0xffffffff; this._h3 = (this._h3 + d) & 0xffffffff; this._h4 = (this._h4 + e) & 0xffffffff; } } StringSHA1._bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320