/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; // 65536 is the size of a wasm memory page // 64 is the maximum chunk size for every possible wasm hash implementation // 4 is the maximum number of bytes per char for string encoding (max is utf-8) // ~3 makes sure that it's always a block of 4 chars, so avoid partially encoded bytes for base64 const MAX_SHORT_STRING = Math.floor((65536 - 64) / 4) & ~3; class WasmHash { /** * @param {WebAssembly.Instance} instance wasm instance * @param {WebAssembly.Instance[]} instancesPool pool of instances * @param {number} chunkSize size of data chunks passed to wasm * @param {number} digestSize size of digest returned by wasm */ constructor(instance, instancesPool, chunkSize, digestSize) { const exports = /** @type {any} */ (instance.exports); exports.init(); this.exports = exports; this.mem = Buffer.from(exports.memory.buffer, 0, 65536); this.buffered = 0; this.instancesPool = instancesPool; this.chunkSize = chunkSize; this.digestSize = digestSize; } reset() { this.buffered = 0; this.exports.init(); } /** * @param {Buffer | string} data data * @param {BufferEncoding=} encoding encoding * @returns {this} itself */ update(data, encoding) { if (typeof data === "string") { while (data.length > MAX_SHORT_STRING) { this._updateWithShortString(data.slice(0, MAX_SHORT_STRING), encoding); data = data.slice(MAX_SHORT_STRING); } this._updateWithShortString(data, encoding); return this; } this._updateWithBuffer(data); return this; } /** * @param {string} data data * @param {BufferEncoding=} encoding encoding * @returns {void} */ _updateWithShortString(data, encoding) { const { exports, buffered, mem, chunkSize } = this; let endPos; if (data.length < 70) { if (!encoding || encoding === "utf-8" || encoding === "utf8") { endPos = buffered; for (let i = 0; i < data.length; i++) { const cc = data.charCodeAt(i); if (cc < 0x80) mem[endPos++] = cc; else if (cc < 0x800) { mem[endPos] = (cc >> 6) | 0xc0; mem[endPos + 1] = (cc & 0x3f) | 0x80; endPos += 2; } else { // bail-out for weird chars endPos += mem.write(data.slice(i), endPos, encoding); break; } } } else if (encoding === "latin1") { endPos = buffered; for (let i = 0; i < data.length; i++) { const cc = data.charCodeAt(i); mem[endPos++] = cc; } } else { endPos = buffered + mem.write(data, buffered, encoding); } } else { endPos = buffered + mem.write(data, buffered, encoding); } if (endPos < chunkSize) { this.buffered = endPos; } else { const l = endPos & ~(this.chunkSize - 1); exports.update(l); const newBuffered = endPos - l; this.buffered = newBuffered; if (newBuffered > 0) mem.copyWithin(0, l, endPos); } } /** * @param {Buffer} data data * @returns {void} */ _updateWithBuffer(data) { const { exports, buffered, mem } = this; const length = data.length; if (buffered + length < this.chunkSize) { data.copy(mem, buffered, 0, length); this.buffered += length; } else { const l = (buffered + length) & ~(this.chunkSize - 1); if (l > 65536) { let i = 65536 - buffered; data.copy(mem, buffered, 0, i); exports.update(65536); const stop = l - buffered - 65536; while (i < stop) { data.copy(mem, 0, i, i + 65536); exports.update(65536); i += 65536; } data.copy(mem, 0, i, l - buffered); exports.update(l - buffered - i); } else { data.copy(mem, buffered, 0, l - buffered); exports.update(l); } const newBuffered = length + buffered - l; this.buffered = newBuffered; if (newBuffered > 0) data.copy(mem, 0, length - newBuffered, length); } } /** * @param {BufferEncoding} type type * @returns {Buffer | string} digest */ digest(type) { const { exports, buffered, mem, digestSize } = this; exports.final(buffered); this.instancesPool.push(this); const hex = mem.toString("latin1", 0, digestSize); if (type === "hex") return hex; if (type === "binary" || !type) return Buffer.from(hex, "hex"); return Buffer.from(hex, "hex").toString(type); } } /** * @param {TODO} wasmModule wasm module * @param {WasmHash[]} instancesPool pool of instances * @param {number} chunkSize size of data chunks passed to wasm * @param {number} digestSize size of digest returned by wasm * @returns {WasmHash} wasm hash */ const create = (wasmModule, instancesPool, chunkSize, digestSize) => { if (instancesPool.length > 0) { const old = /** @type {WasmHash} */ (instancesPool.pop()); old.reset(); return old; } return new WasmHash( new WebAssembly.Instance(wasmModule), instancesPool, chunkSize, digestSize ); }; module.exports = create; module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING;