• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { CustomTextDecoder } from "@koalaui/compat"
17import { int32 } from "@koalaui/compat"
18
19const K = [
20    (0x5a827999 | 0) as int32,
21    (0x6ed9eba1 | 0) as int32,
22    (0x8f1bbcdc | 0) as int32,
23    (0xca62c1d6 | 0) as int32,
24]
25
26const inputBytes = 64
27const inputWords = inputBytes / 4
28const highIndex = inputWords - 2
29const lowIndex = inputWords - 1
30const workWords = 80
31const allocBytes = 80
32const allocWords = allocBytes / 4
33const allocTotal = allocBytes * 100
34
35export function createSha1(): SHA1Hash {
36   return new SHA1Hash()
37}
38
39export class SHA1Hash {
40    private A = (0x67452301 | 0) as int32
41    private B = (0xefcdab89 | 0) as int32
42    private C = (0x98badcfe | 0) as int32
43    private D = (0x10325476 | 0) as int32
44    private E = (0xc3d2e1f0 | 0) as int32
45    private readonly _byte: Uint8Array
46    private readonly _word: Int32Array
47    private _size = 0
48    private _sp = 0 // surrogate pair
49
50    constructor() {
51        if (!sharedBuffer || sharedOffset >= allocTotal) {
52            sharedBuffer = new ArrayBuffer(allocTotal)
53            sharedOffset = 0
54        }
55
56        this._byte = new Uint8Array(sharedBuffer, sharedOffset, allocBytes)
57        this._word = new Int32Array(sharedBuffer, sharedOffset, allocWords)
58        sharedOffset += allocBytes
59    }
60
61    updateString(data: string, encoding?: string): SHA1Hash {
62        return this._utf8(data)
63    }
64    updateInt32(data: int32): SHA1Hash {
65        const buffer = new Int32Array(1)
66        buffer[0] = data
67        return this.update(buffer)
68    }
69
70    update(data: Int32Array | Float32Array | Uint32Array | Uint8Array): SHA1Hash {
71        if (data == null) {
72            throw new TypeError("SHA1Hash expected non-null data: ")
73        }
74
75        let byteOffset: int32 = 0
76        let length: int32 = 0
77        let buffer: ArrayBufferLike | undefined = undefined
78
79        // TODO: an attempt to wrie this in a generic form causes
80        // es2panda to segfault.
81        if (data instanceof Int32Array) {
82            byteOffset = data.byteOffset as int32
83            length = data.byteLength as int32
84            buffer = data.buffer
85        } else if (data instanceof Uint32Array) {
86            byteOffset = data.byteOffset as int32
87            length = data.byteLength as int32
88            buffer = data.buffer
89        } else if (data instanceof Float32Array) {
90            byteOffset = data.byteOffset as int32
91            length = data.byteLength as int32
92            buffer = data.buffer
93        } else if (data instanceof Uint8Array) {
94            byteOffset = data.byteOffset as int32
95            length = data.byteLength as int32
96            buffer = data.buffer
97        }
98
99        let blocks: int32 = ((length / inputBytes) | 0) as int32
100        let offset: int32 = 0
101
102        // longer than 1 block
103        if ((blocks != 0) && !(byteOffset & 3) && !(this._size % inputBytes)) {
104            const block = new Int32Array(buffer!, byteOffset, blocks * inputWords)
105            while (blocks--) {
106                this._int32(block, offset >> 2)
107                offset += inputBytes
108            }
109            this._size += offset
110        }
111
112        // data: TypedArray | DataView
113        const BYTES_PER_ELEMENT = (data as Uint8Array).BYTES_PER_ELEMENT as int32
114        if ((BYTES_PER_ELEMENT != 1) && buffer != undefined) {
115            const rest = new Uint8Array(buffer, byteOffset + offset, length - offset)
116            return this._uint8(rest)
117        }
118
119        // no more bytes
120        if (offset == length) return this
121
122        return this._uint8(new Uint8Array(buffer!), offset)
123    }
124
125    private _uint8(data: Uint8Array, offset?: int32): SHA1Hash {
126        const _byte = this._byte
127        const _word = this._word
128        const length = data.length
129        offset = ((offset ?? 0) | 0) as int32
130
131        while (offset < length) {
132            const start = this._size % inputBytes
133            let index = start
134
135            while (offset < length && index < inputBytes) {
136                _byte[index++] = data[offset++]
137            }
138
139            if (index >= inputBytes) {
140                this._int32(_word)
141            }
142
143            this._size += index - start
144        }
145
146        return this
147    }
148
149    private _utf8(text: string): SHA1Hash {
150        const _byte = this._byte
151        const _word = this._word
152        const length = text.length
153        let surrogate = this._sp
154
155        for (let offset = 0; offset < length; ) {
156            const start = this._size % inputBytes
157            let index = start
158
159            while (offset < length && index < inputBytes) {
160                let code = text.charCodeAt(offset++) | 0
161                if (code < 0x80) {
162                    // ASCII characters
163                    _byte[index++] = code
164                } else if (code < 0x800) {
165                    // 2 bytes
166                    _byte[index++] = 0xC0 | (code >>> 6)
167                    _byte[index++] = 0x80 | (code & 0x3F)
168                } else if (code < 0xD800 || code > 0xDFFF) {
169                    // 3 bytes
170                    _byte[index++] = 0xE0 | (code >>> 12)
171                    _byte[index++] = 0x80 | ((code >>> 6) & 0x3F)
172                    _byte[index++] = 0x80 | (code & 0x3F)
173                } else if (surrogate) {
174                    // 4 bytes - surrogate pair
175                    code = ((surrogate & 0x3FF) << 10) + (code & 0x3FF) + 0x10000
176                    _byte[index++] = 0xF0 | (code >>> 18)
177                    _byte[index++] = 0x80 | ((code >>> 12) & 0x3F)
178                    _byte[index++] = 0x80 | ((code >>> 6) & 0x3F)
179                    _byte[index++] = 0x80 | (code & 0x3F)
180                    surrogate = 0
181                } else {
182                    surrogate = code
183                }
184            }
185
186            if (index >= inputBytes) {
187                this._int32(_word)
188                _word[0] = _word[inputWords]
189            }
190
191            this._size += index - start
192        }
193
194        this._sp = surrogate
195        return this
196    }
197
198    private _int32(data: Int32Array, offset?: int32): void {
199        let A = this.A
200        let B = this.B
201        let C = this.C
202        let D = this.D
203        let E = this.E
204        let i = 0
205        offset = ((offset ??  0) | 0) as int32
206
207        while (i < inputWords) {
208            W[i++] = swap32(data[offset++] as int32)
209        }
210
211        for (i = inputWords; i < workWords; i++) {
212            W[i] = rotate1((W[i - 3] as int32) ^ (W[i - 8] as int32) ^ (W[i - 14] as int32) ^ (W[i - 16] as int32))
213        }
214
215        for (i = 0; i < workWords; i++) {
216            const S = (i / 20) | 0
217            const T = ((rotate5(A) + ft(S, B, C, D) + E + W[i] + K[S]) as int32) | 0
218            E = D
219            D = C
220            C = rotate30(B)
221            B = A
222            A = T
223        }
224
225        this.A = (A + this.A) | 0
226        this.B = (B + this.B) | 0
227        this.C = (C + this.C) | 0
228        this.D = (D + this.D) | 0
229        this.E = (E + this.E) | 0
230    }
231
232    // digest(): Uint8Array
233    // digest(encoding: string): string
234    digest(encoding?: string): Uint8Array | string {
235        const _byte = this._byte
236        const _word = this._word
237        let i = (this._size % inputBytes) | 0
238        _byte[i++] = 0x80
239
240        // pad 0 for current word
241        while (i & 3) {
242            _byte[i++] = 0
243        }
244        i >>= 2
245
246        if (i > highIndex) {
247            while (i < inputWords) {
248                _word[i++] = 0
249            }
250            i = 0
251            this._int32(_word)
252        }
253
254        // pad 0 for rest words
255        while (i < inputWords) {
256            _word[i++] = 0
257        }
258
259        // input size
260        const bits64: int32 = this._size * 8
261        const low32: int32 = ((bits64 & 0xffffffff) as int32 >>> 0) as int32
262        const high32: int32 = ((bits64 - low32) as int32 / 0x100000000) as int32
263        if (high32) _word[highIndex] = swap32(high32) as int32
264        if (low32) _word[lowIndex] = swap32(low32) as int32
265
266        this._int32(_word)
267
268        return (encoding === "hex") ? this._hex() : this._bin()
269    }
270
271    private _hex(): string {
272        let A = this.A
273        let B = this.B
274        let C = this.C
275        let D = this.D
276        let E = this.E
277
278        return hex32Str(A, B, C, D, E)
279    }
280
281    private _bin(): Uint8Array {
282        let A = this.A
283        let B = this.B
284        let C = this.C
285        let D = this.D
286        let E = this.E
287        const _byte = this._byte
288        const _word = this._word
289
290        _word[0] = swap32(A)
291        _word[1] = swap32(B)
292        _word[2] = swap32(C)
293        _word[3] = swap32(D)
294        _word[4] = swap32(E)
295
296        return _byte.slice(0, 20)
297    }
298}
299
300type NS = (num: int32) => string
301type NN = (num: int32) => int32
302
303const W = new Int32Array(workWords)
304
305let sharedBuffer: ArrayBuffer
306let sharedOffset: int32 = 0
307
308const swapLE: NN = ((c:int32):int32 => (((c << 24) & 0xff000000) | ((c << 8) & 0xff0000) | ((c >> 8) & 0xff00) | ((c >> 24) & 0xff)))
309const swapBE: NN = ((c:int32):int32 => c)
310const swap32: NN = isBE() ? swapBE : swapLE
311const rotate1: NN = (num: int32): int32 => (num << 1) | (num >>> 31)
312const rotate5: NN = (num: int32): int32 => (num << 5) | (num >>> 27)
313const rotate30: NN = (num: int32): int32 => (num << 30) | (num >>> 2)
314
315function isBE(): boolean {
316    let a16 = new Uint16Array(1)
317    a16[0] = 0xFEFF
318    let a8 = new Uint8Array(a16.buffer)
319    return a8[0] == 0xFE // BOM
320}
321
322
323function ft(s: int32, b: int32, c: int32, d: int32) {
324    if (s == 0) return (b & c) | ((~b) & d)
325    if (s == 2) return (b & c) | (b & d) | (c & d)
326    return b ^ c ^ d
327}
328
329const hex32Decoder = new CustomTextDecoder()
330const hex32DecodeBuffer = new Uint8Array(40)
331function hex32Str(A: int32, B: int32, C: int32, D: int32, E: int32): string {
332    writeIntAsHexUTF8(A, hex32DecodeBuffer, 0)
333    writeIntAsHexUTF8(B, hex32DecodeBuffer, 8)
334    writeIntAsHexUTF8(C, hex32DecodeBuffer, 16)
335    writeIntAsHexUTF8(D, hex32DecodeBuffer, 24)
336    writeIntAsHexUTF8(E, hex32DecodeBuffer, 32)
337    return hex32Decoder.decode(hex32DecodeBuffer)
338}
339
340function writeIntAsHexUTF8(value: int32, buffer: Uint8Array, byteOffset: int32) {
341    buffer[byteOffset++] = nibbleToHexCode((value >> 28) & 0xF)
342    buffer[byteOffset++] = nibbleToHexCode((value >> 24) & 0xF)
343    buffer[byteOffset++] = nibbleToHexCode((value >> 20) & 0xF)
344    buffer[byteOffset++] = nibbleToHexCode((value >> 16) & 0xF)
345    buffer[byteOffset++] = nibbleToHexCode((value >> 12) & 0xF)
346    buffer[byteOffset++] = nibbleToHexCode((value >> 8 ) & 0xF)
347    buffer[byteOffset++] = nibbleToHexCode((value >> 4 ) & 0xF)
348    buffer[byteOffset++] = nibbleToHexCode((value >> 0 ) & 0xF)
349}
350
351function nibbleToHexCode(nibble: int32) {
352    return nibble > 9 ? nibble + 87 : nibble + 48
353}
354