@hugoalh/djb2a
Version:
A module to get the non-cryptographic hash of the data with algorithm DJB2a.
110 lines (109 loc) • 3.33 kB
JavaScript
/**
* 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;