Path: blob/main/extensions/copilot/src/util/vs/base/common/hash.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { encodeHex, VSBuffer } from './buffer';8import * as strings from './strings';910type NotSyncHashable = ArrayBufferLike | ArrayBufferView;1112/**13* Return a hash value for an object.14*15* Note that this should not be used for binary data types. Instead,16* prefer {@link hashAsync}.17*/18export function hash<T>(obj: T extends NotSyncHashable ? never : T): number {19return doHash(obj, 0);20}2122export function doHash(obj: unknown, hashVal: number): number {23switch (typeof obj) {24case 'object':25if (obj === null) {26return numberHash(349, hashVal);27} else if (Array.isArray(obj)) {28return arrayHash(obj, hashVal);29}30return objectHash(obj, hashVal);31case 'string':32return stringHash(obj, hashVal);33case 'boolean':34return booleanHash(obj, hashVal);35case 'number':36return numberHash(obj, hashVal);37case 'undefined':38return numberHash(937, hashVal);39default:40return numberHash(617, hashVal);41}42}4344export function numberHash(val: number, initialHashVal: number): number {45return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int3246}4748function booleanHash(b: boolean, initialHashVal: number): number {49return numberHash(b ? 433 : 863, initialHashVal);50}5152export function stringHash(s: string, hashVal: number) {53hashVal = numberHash(149417, hashVal);54for (let i = 0, length = s.length; i < length; i++) {55hashVal = numberHash(s.charCodeAt(i), hashVal);56}57return hashVal;58}5960function arrayHash(arr: unknown[], initialHashVal: number): number {61initialHashVal = numberHash(104579, initialHashVal);62return arr.reduce<number>((hashVal, item) => doHash(item, hashVal), initialHashVal);63}6465function objectHash(obj: object, initialHashVal: number): number {66initialHashVal = numberHash(181387, initialHashVal);67return Object.keys(obj).sort().reduce((hashVal, key) => {68hashVal = stringHash(key, hashVal);69return doHash((obj as Record<string, unknown>)[key], hashVal);70}, initialHashVal);71}72737475/** Hashes the input as SHA-1, returning a hex-encoded string. */76export const hashAsync = (input: string | ArrayBufferView | VSBuffer) => {77// Note: I would very much like to expose a streaming interface for hashing78// generally, but this is not available in web crypto yet, see79// https://github.com/w3c/webcrypto/issues/738081// StringSHA1 is faster for small string input, use it since we have it:82if (typeof input === 'string' && input.length < 250) {83const sha = new StringSHA1();84sha.update(input);85return Promise.resolve(sha.digest());86}8788let buff: ArrayBufferView;89if (typeof input === 'string') {90buff = new TextEncoder().encode(input);91} else if (input instanceof VSBuffer) {92buff = input.buffer;93} else {94buff = input;95}9697return crypto.subtle.digest('sha-1', buff as ArrayBufferView<ArrayBuffer>).then(toHexString); // CodeQL [SM04514] we use sha1 here for validating old stored client state, not for security98};99100const enum SHA1Constant {101BLOCK_SIZE = 64, // 512 / 8102UNICODE_REPLACEMENT = 0xFFFD,103}104105function leftRotate(value: number, bits: number, totalBits: number = 32): number {106// delta + bits = totalBits107const delta = totalBits - bits;108109// All ones, expect `delta` zeros aligned to the right110const mask = ~((1 << delta) - 1);111112// Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits)113return ((value << bits) | ((mask & value) >>> delta)) >>> 0;114}115116function toHexString(buffer: ArrayBuffer): string;117function toHexString(value: number, bitsize?: number): string;118function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string {119if (bufferOrValue instanceof ArrayBuffer) {120return encodeHex(VSBuffer.wrap(new Uint8Array(bufferOrValue)));121}122123return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0');124}125126/**127* A SHA1 implementation that works with strings and does not allocate.128*129* Prefer to use {@link hashAsync} in async contexts130*/131export class StringSHA1 {132private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320133134private _h0 = 0x67452301;135private _h1 = 0xEFCDAB89;136private _h2 = 0x98BADCFE;137private _h3 = 0x10325476;138private _h4 = 0xC3D2E1F0;139140private readonly _buff: Uint8Array;141private readonly _buffDV: DataView;142private _buffLen: number;143private _totalLen: number;144private _leftoverHighSurrogate: number;145private _finished: boolean;146147constructor() {148this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */);149this._buffDV = new DataView(this._buff.buffer);150this._buffLen = 0;151this._totalLen = 0;152this._leftoverHighSurrogate = 0;153this._finished = false;154}155156public update(str: string): void {157const strLen = str.length;158if (strLen === 0) {159return;160}161162const buff = this._buff;163let buffLen = this._buffLen;164let leftoverHighSurrogate = this._leftoverHighSurrogate;165let charCode: number;166let offset: number;167168if (leftoverHighSurrogate !== 0) {169charCode = leftoverHighSurrogate;170offset = -1;171leftoverHighSurrogate = 0;172} else {173charCode = str.charCodeAt(0);174offset = 0;175}176177while (true) {178let codePoint = charCode;179if (strings.isHighSurrogate(charCode)) {180if (offset + 1 < strLen) {181const nextCharCode = str.charCodeAt(offset + 1);182if (strings.isLowSurrogate(nextCharCode)) {183offset++;184codePoint = strings.computeCodePoint(charCode, nextCharCode);185} else {186// illegal => unicode replacement character187codePoint = SHA1Constant.UNICODE_REPLACEMENT;188}189} else {190// last character is a surrogate pair191leftoverHighSurrogate = charCode;192break;193}194} else if (strings.isLowSurrogate(charCode)) {195// illegal => unicode replacement character196codePoint = SHA1Constant.UNICODE_REPLACEMENT;197}198199buffLen = this._push(buff, buffLen, codePoint);200offset++;201if (offset < strLen) {202charCode = str.charCodeAt(offset);203} else {204break;205}206}207208this._buffLen = buffLen;209this._leftoverHighSurrogate = leftoverHighSurrogate;210}211212private _push(buff: Uint8Array, buffLen: number, codePoint: number): number {213if (codePoint < 0x0080) {214buff[buffLen++] = codePoint;215} else if (codePoint < 0x0800) {216buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);217buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);218} else if (codePoint < 0x10000) {219buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);220buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);221buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);222} else {223buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);224buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);225buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);226buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);227}228229if (buffLen >= SHA1Constant.BLOCK_SIZE) {230this._step();231buffLen -= SHA1Constant.BLOCK_SIZE;232this._totalLen += SHA1Constant.BLOCK_SIZE;233// take last 3 in case of UTF8 overflow234buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0];235buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1];236buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2];237}238239return buffLen;240}241242public digest(): string {243if (!this._finished) {244this._finished = true;245if (this._leftoverHighSurrogate) {246// illegal => unicode replacement character247this._leftoverHighSurrogate = 0;248this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT);249}250this._totalLen += this._buffLen;251this._wrapUp();252}253254return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4);255}256257private _wrapUp(): void {258this._buff[this._buffLen++] = 0x80;259this._buff.subarray(this._buffLen).fill(0);260261if (this._buffLen > 56) {262this._step();263this._buff.fill(0);264}265266// this will fit because the mantissa can cover up to 52 bits267const ml = 8 * this._totalLen;268269this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false);270this._buffDV.setUint32(60, ml % 4294967296, false);271272this._step();273}274275private _step(): void {276const bigBlock32 = StringSHA1._bigBlock32;277const data = this._buffDV;278279for (let j = 0; j < 64 /* 16*4 */; j += 4) {280bigBlock32.setUint32(j, data.getUint32(j, false), false);281}282283for (let j = 64; j < 320 /* 80*4 */; j += 4) {284bigBlock32.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);285}286287let a = this._h0;288let b = this._h1;289let c = this._h2;290let d = this._h3;291let e = this._h4;292293let f: number, k: number;294let temp: number;295296for (let j = 0; j < 80; j++) {297if (j < 20) {298f = (b & c) | ((~b) & d);299k = 0x5A827999;300} else if (j < 40) {301f = b ^ c ^ d;302k = 0x6ED9EBA1;303} else if (j < 60) {304f = (b & c) | (b & d) | (c & d);305k = 0x8F1BBCDC;306} else {307f = b ^ c ^ d;308k = 0xCA62C1D6;309}310311temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff;312e = d;313d = c;314c = leftRotate(b, 30);315b = a;316a = temp;317}318319this._h0 = (this._h0 + a) & 0xffffffff;320this._h1 = (this._h1 + b) & 0xffffffff;321this._h2 = (this._h2 + c) & 0xffffffff;322this._h3 = (this._h3 + d) & 0xffffffff;323this._h4 = (this._h4 + e) & 0xffffffff;324}325}326327328