UNPKG

armpit

Version:

Another resource manager programming interface toolkit.

174 lines 6.1 kB
var _a; import { createHash, createHmac } from "node:crypto"; import { CallableClassBase } from "./tsUtils.js"; // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class NameHash extends CallableClassBase { static getDefaultLength(type) { switch (type) { case "alphanumeric": case "alpha": return 3; default: return 4; } } static calculateSha256Hash(values) { const valuesHasher = createHash("sha256"); for (const value of values) { if (typeof value === "string") { valuesHasher.update(value, "utf8"); } else { valuesHasher.update(value); } } return valuesHasher.digest(); } static calculateSha256Hmac(prk, input) { const hmac = createHmac("sha256", prk); hmac.update(input); const buffer = hmac.digest(); if (!(buffer.length > 0)) { throw new Error("Hash failure"); } return buffer; } static packBufferIntoInt(buffer) { let result = 0n; for (let i = 0; i < buffer.length; i++) { result |= BigInt(buffer[i]) << BigInt(i * 8); } return result; } static toTextHex(data) { let result = ""; for (let sourceIndex = 0, targetIndex = 0; sourceIndex < data.length; sourceIndex++, targetIndex += 2) { const byte = data[sourceIndex]; result += (byte & 0xf).toString(16) + (byte >> 4).toString(16); } return result; } static radix26ToAlpha(value) { let code = value.charCodeAt(0); if (code >= 48 && code <= 57) { code += 49; } else if ((code >= 97 && code <= 112) || (code >= 65 && code <= 80)) { code += 10; } else { return value; } return String.fromCharCode(code); } static toTextBaseN(data, type) { let padCharacter = "0"; let resultLength; let radix; if (data.length !== 32) { throw new Error("Unexpected data size"); } switch (type) { case "numeric": resultLength = 77; radix = 10; break; case "alpha": resultLength = 54; radix = 26; padCharacter = "a"; break; case "alphanumeric": default: resultLength = 49; radix = 36; break; } const radixEncoded = _a.packBufferIntoInt(data).toString(radix); let result = ""; // Sometimes we may get an extra high order character out of the number that // isn't fully covered by all the bits of data so it gets skipped. const startIndex = Math.max(radixEncoded.length - resultLength, 0); let i = radixEncoded.length - 1; if (type === "alpha") { for (; i >= startIndex; i--) { result += _a.radix26ToAlpha(radixEncoded[i]); } } else { // Hashes of different lengths but sourced from the same inputs should sort together. // Because the resulting values are effectively text which is sorted left to right, // the lower order values should be on the left so that additional generated hash bytes // are eventually appended to the right of the string. A simple reverse after building // the value should work. // Whole blocks are generated by this code so this may not matter technically, doing // the reverse now gives more flexibility for future implementations, I hope. for (; i >= startIndex; i--) { result += radixEncoded[i]; } } // Sometimes the high order character maps to all zeros so padding is required while (result.length < resultLength) { result += padCharacter; } return result; } #values; #options; #cached; constructor(...args) { super(); let options = null; if (args.length > 0 && typeof args[args.length - 1] !== "string") { this.#values = args.slice(0, args.length - 1); options = args[args.length - 1]; } else { this.#values = [...args]; options = null; } const type = options?.type ?? "alphanumeric"; this.#options = { type, defaultLength: Math.max(options?.defaultLength ?? _a.getDefaultLength(type), 1), }; this.#cached = null; } concat(value) { return new _a(...this.#values, value, this.#options); } toString(length) { length = length != null && length > 0 ? length : this.#options.defaultLength; let result = this.#cached; if (result == null || result.length < length) { result = this.#buildHashText(length); this.#cached = result; } if (result.length > length) { result = result.slice(0, length); } return result; } fnImpl(length) { return this.toString(length); } #buildHashText(minTextLength) { let hashValue = ""; let iteration = 1; let tBuffer = null; const pseudoRandomKey = _a.calculateSha256Hash(this.#values); while (hashValue.length < minTextLength) { let hmacInputBuffer = Buffer.from([iteration % 256]); if (tBuffer) { hmacInputBuffer = Buffer.concat([tBuffer, hmacInputBuffer]); } tBuffer = _a.calculateSha256Hmac(pseudoRandomKey, hmacInputBuffer); hashValue += this.#options.type === "hex" ? _a.toTextHex(tBuffer) : _a.toTextBaseN(tBuffer, this.#options.type); iteration++; } return hashValue; } } _a = NameHash; //# sourceMappingURL=nameHash.js.map