You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			164 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			164 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	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); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	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); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | const create = (wasmModule, instancesPool, chunkSize, digestSize) => { | ||
|  | 	if (instancesPool.length > 0) { | ||
|  | 		const old = instancesPool.pop(); | ||
|  | 		old.reset(); | ||
|  | 		return old; | ||
|  | 	} else { | ||
|  | 		return new WasmHash( | ||
|  | 			new WebAssembly.Instance(wasmModule), | ||
|  | 			instancesPool, | ||
|  | 			chunkSize, | ||
|  | 			digestSize | ||
|  | 		); | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports = create; | ||
|  | module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING; |