UNPKG

@hugoalh/fnv

Version:

A module to get the non-cryptographic hash of the data with algorithm Fowler-Noll-Vo (FNV).

162 lines (161 loc) 5.58 kB
const variants = [ "0", "1", "1a" ]; const bitsParameters = new Map([ [32, { offset: 2166136261n, prime: 16777619n }], [64, { offset: 14695981039346656037n, prime: 1099511628211n }], [128, { offset: 144066263297769815596495629667062367629n, prime: 309485009821345068724781371n }], [256, { offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557n, prime: 374144419156711147060143317175368453031918731002211n }], [512, { offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785n, prime: 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759n }], [1024, { offset: 14197795064947621068722070641403218320880622795441933960878474914617582723252296732303717722150864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915n, prime: 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573n }] ]); /** * Get the non-cryptographic hash of the data with algorithm Fowler-Noll-Vo (FNV). */ export class FNV { get [Symbol.toStringTag]() { return `FNV-${this.#variant}-${this.#size}`; } #freezed = false; #hashHex = null; #hashUint8Array = null; #prime; #size; #variant; #bin = 0n; /** * Initialize. * @param {FNVVariant} variant Variant of the FNV. * @param {FNVBitsSize} size Bits size of the FNV. * @param {FNVAcceptDataType} [data] Data. Can append later via the method {@linkcode FNV.update} and {@linkcode FNV.updateFromStream}. */ constructor(variant, size, data) { if (!variants.includes(variant)) { throw new RangeError(`\`${variant}\` is not a valid FNV variant! Only accept these values: ${variants.join(", ")}`); } this.#variant = variant; const parameter = bitsParameters.get(size); if (typeof parameter === "undefined") { throw new RangeError(`\`${size}\` is not a valid FNV hash bits size! Only accept these values: ${Array.from(bitsParameters.keys()).join(", ")}`); } const { offset, prime } = parameter; if (this.#variant !== "0") { this.#bin = offset; } this.#prime = prime; this.#size = size; if (typeof data !== "undefined") { this.update(data); } } /** * Whether the instance is freezed. * @returns {boolean} */ get freezed() { return this.#freezed; } /** * Bits size of the FNV. * @returns {FNVBitsSize} */ get size() { return this.#size; } /** * Variant of the FNV. * @returns {FNVVariant} */ get variant() { return this.#variant; } /** * 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 = this.#bin.toString(16).toUpperCase().padStart(this.#size / 4, "0"); if (this.#hashHex.length !== this.#size / 4) { throw new Error(`Unexpected hash hex result \`${this.#hashHex}\`! Please submit a bug report.`); } } return this.#hashHex; } /** * Append data. * @param {FNVAcceptDataType} 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") ? new TextEncoder().encode(data) : Uint8Array.from(data); for (const byte of dataFmt) { this.#bin = (this.#variant === "1a") ? BigInt.asUintN(this.#size, (this.#bin ^ BigInt(byte)) * this.#prime) : BigInt.asUintN(this.#size, (this.#bin * this.#prime) ^ BigInt(byte)); } return this; } /** * Append data from the readable stream. * @param {ReadableStream<FNVAcceptDataType>} stream Data from the readable stream. * @returns {Promise<this>} */ async updateFromStream(stream) { for await (const chunk of stream) { this.update(chunk); } return this; } } export default FNV;