Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/vs/base/common/hash.ts
13405 views
1
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'
2
3
/*---------------------------------------------------------------------------------------------
4
* Copyright (c) Microsoft Corporation. All rights reserved.
5
* Licensed under the MIT License. See License.txt in the project root for license information.
6
*--------------------------------------------------------------------------------------------*/
7
8
import { encodeHex, VSBuffer } from './buffer';
9
import * as strings from './strings';
10
11
type NotSyncHashable = ArrayBufferLike | ArrayBufferView;
12
13
/**
14
* Return a hash value for an object.
15
*
16
* Note that this should not be used for binary data types. Instead,
17
* prefer {@link hashAsync}.
18
*/
19
export function hash<T>(obj: T extends NotSyncHashable ? never : T): number {
20
return doHash(obj, 0);
21
}
22
23
export function doHash(obj: unknown, hashVal: number): number {
24
switch (typeof obj) {
25
case 'object':
26
if (obj === null) {
27
return numberHash(349, hashVal);
28
} else if (Array.isArray(obj)) {
29
return arrayHash(obj, hashVal);
30
}
31
return objectHash(obj, hashVal);
32
case 'string':
33
return stringHash(obj, hashVal);
34
case 'boolean':
35
return booleanHash(obj, hashVal);
36
case 'number':
37
return numberHash(obj, hashVal);
38
case 'undefined':
39
return numberHash(937, hashVal);
40
default:
41
return numberHash(617, hashVal);
42
}
43
}
44
45
export function numberHash(val: number, initialHashVal: number): number {
46
return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
47
}
48
49
function booleanHash(b: boolean, initialHashVal: number): number {
50
return numberHash(b ? 433 : 863, initialHashVal);
51
}
52
53
export function stringHash(s: string, hashVal: number) {
54
hashVal = numberHash(149417, hashVal);
55
for (let i = 0, length = s.length; i < length; i++) {
56
hashVal = numberHash(s.charCodeAt(i), hashVal);
57
}
58
return hashVal;
59
}
60
61
function arrayHash(arr: unknown[], initialHashVal: number): number {
62
initialHashVal = numberHash(104579, initialHashVal);
63
return arr.reduce<number>((hashVal, item) => doHash(item, hashVal), initialHashVal);
64
}
65
66
function objectHash(obj: object, initialHashVal: number): number {
67
initialHashVal = numberHash(181387, initialHashVal);
68
return Object.keys(obj).sort().reduce((hashVal, key) => {
69
hashVal = stringHash(key, hashVal);
70
return doHash((obj as Record<string, unknown>)[key], hashVal);
71
}, initialHashVal);
72
}
73
74
75
76
/** Hashes the input as SHA-1, returning a hex-encoded string. */
77
export const hashAsync = (input: string | ArrayBufferView | VSBuffer) => {
78
// Note: I would very much like to expose a streaming interface for hashing
79
// generally, but this is not available in web crypto yet, see
80
// https://github.com/w3c/webcrypto/issues/73
81
82
// StringSHA1 is faster for small string input, use it since we have it:
83
if (typeof input === 'string' && input.length < 250) {
84
const sha = new StringSHA1();
85
sha.update(input);
86
return Promise.resolve(sha.digest());
87
}
88
89
let buff: ArrayBufferView;
90
if (typeof input === 'string') {
91
buff = new TextEncoder().encode(input);
92
} else if (input instanceof VSBuffer) {
93
buff = input.buffer;
94
} else {
95
buff = input;
96
}
97
98
return 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 security
99
};
100
101
const enum SHA1Constant {
102
BLOCK_SIZE = 64, // 512 / 8
103
UNICODE_REPLACEMENT = 0xFFFD,
104
}
105
106
function leftRotate(value: number, bits: number, totalBits: number = 32): number {
107
// delta + bits = totalBits
108
const delta = totalBits - bits;
109
110
// All ones, expect `delta` zeros aligned to the right
111
const mask = ~((1 << delta) - 1);
112
113
// Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits)
114
return ((value << bits) | ((mask & value) >>> delta)) >>> 0;
115
}
116
117
function toHexString(buffer: ArrayBuffer): string;
118
function toHexString(value: number, bitsize?: number): string;
119
function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string {
120
if (bufferOrValue instanceof ArrayBuffer) {
121
return encodeHex(VSBuffer.wrap(new Uint8Array(bufferOrValue)));
122
}
123
124
return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0');
125
}
126
127
/**
128
* A SHA1 implementation that works with strings and does not allocate.
129
*
130
* Prefer to use {@link hashAsync} in async contexts
131
*/
132
export class StringSHA1 {
133
private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320
134
135
private _h0 = 0x67452301;
136
private _h1 = 0xEFCDAB89;
137
private _h2 = 0x98BADCFE;
138
private _h3 = 0x10325476;
139
private _h4 = 0xC3D2E1F0;
140
141
private readonly _buff: Uint8Array;
142
private readonly _buffDV: DataView;
143
private _buffLen: number;
144
private _totalLen: number;
145
private _leftoverHighSurrogate: number;
146
private _finished: boolean;
147
148
constructor() {
149
this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */);
150
this._buffDV = new DataView(this._buff.buffer);
151
this._buffLen = 0;
152
this._totalLen = 0;
153
this._leftoverHighSurrogate = 0;
154
this._finished = false;
155
}
156
157
public update(str: string): void {
158
const strLen = str.length;
159
if (strLen === 0) {
160
return;
161
}
162
163
const buff = this._buff;
164
let buffLen = this._buffLen;
165
let leftoverHighSurrogate = this._leftoverHighSurrogate;
166
let charCode: number;
167
let offset: number;
168
169
if (leftoverHighSurrogate !== 0) {
170
charCode = leftoverHighSurrogate;
171
offset = -1;
172
leftoverHighSurrogate = 0;
173
} else {
174
charCode = str.charCodeAt(0);
175
offset = 0;
176
}
177
178
while (true) {
179
let codePoint = charCode;
180
if (strings.isHighSurrogate(charCode)) {
181
if (offset + 1 < strLen) {
182
const nextCharCode = str.charCodeAt(offset + 1);
183
if (strings.isLowSurrogate(nextCharCode)) {
184
offset++;
185
codePoint = strings.computeCodePoint(charCode, nextCharCode);
186
} else {
187
// illegal => unicode replacement character
188
codePoint = SHA1Constant.UNICODE_REPLACEMENT;
189
}
190
} else {
191
// last character is a surrogate pair
192
leftoverHighSurrogate = charCode;
193
break;
194
}
195
} else if (strings.isLowSurrogate(charCode)) {
196
// illegal => unicode replacement character
197
codePoint = SHA1Constant.UNICODE_REPLACEMENT;
198
}
199
200
buffLen = this._push(buff, buffLen, codePoint);
201
offset++;
202
if (offset < strLen) {
203
charCode = str.charCodeAt(offset);
204
} else {
205
break;
206
}
207
}
208
209
this._buffLen = buffLen;
210
this._leftoverHighSurrogate = leftoverHighSurrogate;
211
}
212
213
private _push(buff: Uint8Array, buffLen: number, codePoint: number): number {
214
if (codePoint < 0x0080) {
215
buff[buffLen++] = codePoint;
216
} else if (codePoint < 0x0800) {
217
buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
218
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
219
} else if (codePoint < 0x10000) {
220
buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
221
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
222
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
223
} else {
224
buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
225
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
226
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
227
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
228
}
229
230
if (buffLen >= SHA1Constant.BLOCK_SIZE) {
231
this._step();
232
buffLen -= SHA1Constant.BLOCK_SIZE;
233
this._totalLen += SHA1Constant.BLOCK_SIZE;
234
// take last 3 in case of UTF8 overflow
235
buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0];
236
buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1];
237
buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2];
238
}
239
240
return buffLen;
241
}
242
243
public digest(): string {
244
if (!this._finished) {
245
this._finished = true;
246
if (this._leftoverHighSurrogate) {
247
// illegal => unicode replacement character
248
this._leftoverHighSurrogate = 0;
249
this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT);
250
}
251
this._totalLen += this._buffLen;
252
this._wrapUp();
253
}
254
255
return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4);
256
}
257
258
private _wrapUp(): void {
259
this._buff[this._buffLen++] = 0x80;
260
this._buff.subarray(this._buffLen).fill(0);
261
262
if (this._buffLen > 56) {
263
this._step();
264
this._buff.fill(0);
265
}
266
267
// this will fit because the mantissa can cover up to 52 bits
268
const ml = 8 * this._totalLen;
269
270
this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false);
271
this._buffDV.setUint32(60, ml % 4294967296, false);
272
273
this._step();
274
}
275
276
private _step(): void {
277
const bigBlock32 = StringSHA1._bigBlock32;
278
const data = this._buffDV;
279
280
for (let j = 0; j < 64 /* 16*4 */; j += 4) {
281
bigBlock32.setUint32(j, data.getUint32(j, false), false);
282
}
283
284
for (let j = 64; j < 320 /* 80*4 */; j += 4) {
285
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);
286
}
287
288
let a = this._h0;
289
let b = this._h1;
290
let c = this._h2;
291
let d = this._h3;
292
let e = this._h4;
293
294
let f: number, k: number;
295
let temp: number;
296
297
for (let j = 0; j < 80; j++) {
298
if (j < 20) {
299
f = (b & c) | ((~b) & d);
300
k = 0x5A827999;
301
} else if (j < 40) {
302
f = b ^ c ^ d;
303
k = 0x6ED9EBA1;
304
} else if (j < 60) {
305
f = (b & c) | (b & d) | (c & d);
306
k = 0x8F1BBCDC;
307
} else {
308
f = b ^ c ^ d;
309
k = 0xCA62C1D6;
310
}
311
312
temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff;
313
e = d;
314
d = c;
315
c = leftRotate(b, 30);
316
b = a;
317
a = temp;
318
}
319
320
this._h0 = (this._h0 + a) & 0xffffffff;
321
this._h1 = (this._h1 + b) & 0xffffffff;
322
this._h2 = (this._h2 + c) & 0xffffffff;
323
this._h3 = (this._h3 + d) & 0xffffffff;
324
this._h4 = (this._h4 + e) & 0xffffffff;
325
}
326
}
327
328