UNPKG

@hugoalh/djb2a

Version:

A module to get the non-cryptographic hash of the data with algorithm DJB2a.

110 lines (109 loc) 3.33 kB
/** * Get the non-cryptographic hash of the data with algorithm DJB2a (32 bits). */ export class DJB2a { get [Symbol.toStringTag]() { return "DJB2a"; } #freezed = false; #hashHex = null; #hashUint8Array = null; #bin = 5381n; /** * Initialize. * @param {DJB2aAcceptDataType} [data] Data. Can append later via the method {@linkcode DJB2a.update} and {@linkcode DJB2a.updateFromStream}. */ constructor(data) { if (typeof data !== "undefined") { this.update(data); } } /** * Whether the instance is freezed. * @returns {boolean} */ get freezed() { return this.#freezed; } /** * Freeze the instance to prevent any further update. * @returns {this} */ freeze() { this.#freezed = true; return this; } /** * Get the non-cryptographic hash of the data, in Uint8Array. * @returns {Uint8Array} */ hash() { if (this.#hashUint8Array === null) { const hex = this.hashHex(); const bytes = []; for (let index = 0; index < hex.length; index += 2) { bytes.push(hex.slice(index, index + 2)); } this.#hashUint8Array = Uint8Array.from(bytes.map((byte) => { return Number.parseInt(byte, 16); })); } return Uint8Array.from(this.#hashUint8Array); } /** * Get the non-cryptographic hash of the data, in hexadecimal with padding. * @returns {string} */ hashHex() { if (this.#hashHex === null) { this.#hashHex = BigInt.asUintN(32, this.#bin).toString(16).toUpperCase().padStart(8, "0"); if (this.#hashHex.length !== 8) { throw new Error(`Unexpected hash hex result \`${this.#hashHex}\`! Please submit a bug report.`); } } return this.#hashHex; } /** * Append data. * @param {DJB2aAcceptDataType} data Data. * @returns {this} */ update(data) { if (this.#freezed) { throw new Error(`Instance is freezed!`); } this.#hashHex = null; this.#hashUint8Array = null; const dataFmt = (typeof data === "string") ? data : new TextDecoder().decode(data); for (let index = 0; index < dataFmt.length; index += 1) { this.#bin = this.#bin * 33n ^ BigInt(dataFmt.charCodeAt(index)); } return this; } /** * Append data from the readable stream. * @param {ReadableStream<DJB2aAcceptDataType>} stream Data from the readable stream. * @returns {Promise<this>} */ async updateFromStream(stream) { const reader = stream.getReader(); let done = false; let textDecoder; while (!done) { const { done: end, value } = await reader.read(); done = end; if (typeof value === "undefined") { continue; } if (typeof value === "string") { this.update(value); } else { textDecoder ??= new TextDecoder(); this.update(textDecoder.decode(value, { stream: !done })); } } return this; } } export default DJB2a;