| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- import { packedValue, EncodingType, FormatType } from "./custom_types";
- /**
- * Return type for all the *2packed functions
- */
- const b64Tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- const arraybuffer_error = "ARRAYBUFFER not supported by this environment";
- const uint8array_error = "UINT8ARRAY not supported by this environment";
- /**
- * Convert a string to an array of words.
- *
- * There is a known bug with an odd number of existing bytes and using a UTF-16 encoding. However, this function is
- * used such that the existing bytes are always a result of a previous UTF-16 str2packed call and therefore there
- * should never be an odd number of existing bytes.
- * @param str Unicode string to be converted to binary representation.
- * @param utfType The Unicode type to use to encode the source string.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked`.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Hashmap of the packed values.
- */
- function str2packed(
- str: string,
- utfType: EncodingType,
- existingPacked: number[] | undefined,
- existingPackedLen: number | undefined,
- bigEndianMod: -1 | 1
- ): packedValue {
- let codePnt,
- codePntArr,
- byteCnt = 0,
- i,
- j,
- intOffset,
- byteOffset,
- shiftModifier,
- transposeBytes;
- existingPackedLen = existingPackedLen || 0;
- const packed = existingPacked || [0],
- existingByteLen = existingPackedLen >>> 3;
- if ("UTF8" === utfType) {
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < str.length; i += 1) {
- codePnt = str.charCodeAt(i);
- codePntArr = [];
- if (0x80 > codePnt) {
- codePntArr.push(codePnt);
- } else if (0x800 > codePnt) {
- codePntArr.push(0xc0 | (codePnt >>> 6));
- codePntArr.push(0x80 | (codePnt & 0x3f));
- } else if (0xd800 > codePnt || 0xe000 <= codePnt) {
- codePntArr.push(0xe0 | (codePnt >>> 12), 0x80 | ((codePnt >>> 6) & 0x3f), 0x80 | (codePnt & 0x3f));
- } else {
- i += 1;
- codePnt = 0x10000 + (((codePnt & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
- codePntArr.push(
- 0xf0 | (codePnt >>> 18),
- 0x80 | ((codePnt >>> 12) & 0x3f),
- 0x80 | ((codePnt >>> 6) & 0x3f),
- 0x80 | (codePnt & 0x3f)
- );
- }
- for (j = 0; j < codePntArr.length; j += 1) {
- byteOffset = byteCnt + existingByteLen;
- intOffset = byteOffset >>> 2;
- while (packed.length <= intOffset) {
- packed.push(0);
- }
- /* Known bug kicks in here */
- packed[intOffset] |= codePntArr[j] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
- byteCnt += 1;
- }
- }
- } else {
- /* UTF16BE or UTF16LE */
- shiftModifier = bigEndianMod === -1 ? 2 : 0;
- /* Internally strings are UTF-16BE so transpose bytes under two conditions:
- * need LE and not switching endianness due to SHA-3
- * need BE and switching endianness due to SHA-3 */
- transposeBytes = ("UTF16LE" === utfType && bigEndianMod !== 1) || ("UTF16LE" !== utfType && bigEndianMod === 1);
- for (i = 0; i < str.length; i += 1) {
- codePnt = str.charCodeAt(i);
- if (transposeBytes === true) {
- j = codePnt & 0xff;
- codePnt = (j << 8) | (codePnt >>> 8);
- }
- byteOffset = byteCnt + existingByteLen;
- intOffset = byteOffset >>> 2;
- while (packed.length <= intOffset) {
- packed.push(0);
- }
- packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
- byteCnt += 2;
- }
- }
- return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
- }
- /**
- * Convert a hex string to an array of words.
- *
- * @param str Hexadecimal string to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Hashmap of the packed values.
- */
- function hex2packed(
- str: string,
- existingPacked: number[] | undefined,
- existingPackedLen: number | undefined,
- bigEndianMod: -1 | 1
- ): packedValue {
- let i, num, intOffset, byteOffset;
- if (0 !== str.length % 2) {
- throw new Error("String of HEX type must be in byte increments");
- }
- existingPackedLen = existingPackedLen || 0;
- const packed = existingPacked || [0],
- existingByteLen = existingPackedLen >>> 3,
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < str.length; i += 2) {
- num = parseInt(str.substr(i, 2), 16);
- if (!isNaN(num)) {
- byteOffset = (i >>> 1) + existingByteLen;
- intOffset = byteOffset >>> 2;
- while (packed.length <= intOffset) {
- packed.push(0);
- }
- packed[intOffset] |= num << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
- } else {
- throw new Error("String of HEX type contains invalid characters");
- }
- }
- return { value: packed, binLen: str.length * 4 + existingPackedLen };
- }
- /**
- * Convert a string of raw bytes to an array of words.
- *
- * @param str String of raw bytes to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Hashmap of the packed values.
- */
- function bytes2packed(
- str: string,
- existingPacked: number[] | undefined,
- existingPackedLen: number | undefined,
- bigEndianMod: -1 | 1
- ): packedValue {
- let codePnt, i, intOffset, byteOffset;
- existingPackedLen = existingPackedLen || 0;
- const packed = existingPacked || [0],
- existingByteLen = existingPackedLen >>> 3,
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < str.length; i += 1) {
- codePnt = str.charCodeAt(i);
- byteOffset = i + existingByteLen;
- intOffset = byteOffset >>> 2;
- if (packed.length <= intOffset) {
- packed.push(0);
- }
- packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
- }
- return { value: packed, binLen: str.length * 8 + existingPackedLen };
- }
- /**
- * Convert a base-64 string to an array of words.
- *
- * @param str Base64-encoded string to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Hashmap of the packed values.
- */
- function b642packed(
- str: string,
- existingPacked: number[] | undefined,
- existingPackedLen: number | undefined,
- bigEndianMod: -1 | 1
- ): packedValue {
- let byteCnt = 0,
- index,
- i,
- j,
- tmpInt,
- strPart,
- intOffset,
- byteOffset;
- existingPackedLen = existingPackedLen || 0;
- const packed = existingPacked || [0],
- existingByteLen = existingPackedLen >>> 3,
- shiftModifier = bigEndianMod === -1 ? 3 : 0,
- firstEqual = str.indexOf("=");
- if (-1 === str.search(/^[a-zA-Z0-9=+/]+$/)) {
- throw new Error("Invalid character in base-64 string");
- }
- str = str.replace(/=/g, "");
- if (-1 !== firstEqual && firstEqual < str.length) {
- throw new Error("Invalid '=' found in base-64 string");
- }
- for (i = 0; i < str.length; i += 4) {
- strPart = str.substr(i, 4);
- tmpInt = 0;
- for (j = 0; j < strPart.length; j += 1) {
- index = b64Tab.indexOf(strPart.charAt(j));
- tmpInt |= index << (18 - 6 * j);
- }
- for (j = 0; j < strPart.length - 1; j += 1) {
- byteOffset = byteCnt + existingByteLen;
- intOffset = byteOffset >>> 2;
- while (packed.length <= intOffset) {
- packed.push(0);
- }
- packed[intOffset] |=
- ((tmpInt >>> (16 - j * 8)) & 0xff) << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
- byteCnt += 1;
- }
- }
- return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
- }
- /**
- * Convert an Uint8Array to an array of words.
- *
- * @param arr Uint8Array to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Hashmap of the packed values.
- */
- function uint8array2packed(
- arr: Uint8Array,
- existingPacked: number[] | undefined,
- existingPackedLen: number | undefined,
- bigEndianMod: -1 | 1
- ): packedValue {
- let i, intOffset, byteOffset;
- existingPackedLen = existingPackedLen || 0;
- const packed = existingPacked || [0],
- existingByteLen = existingPackedLen >>> 3,
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < arr.length; i += 1) {
- byteOffset = i + existingByteLen;
- intOffset = byteOffset >>> 2;
- if (packed.length <= intOffset) {
- packed.push(0);
- }
- packed[intOffset] |= arr[i] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
- }
- return { value: packed, binLen: arr.length * 8 + existingPackedLen };
- }
- /**
- * Convert an ArrayBuffer to an array of words
- *
- * @param arr ArrayBuffer to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Hashmap of the packed values.
- */
- function arraybuffer2packed(
- arr: ArrayBuffer,
- existingPacked: number[] | undefined,
- existingPackedLen: number | undefined,
- bigEndianMod: -1 | 1
- ): packedValue {
- return uint8array2packed(new Uint8Array(arr), existingPacked, existingPackedLen, bigEndianMod);
- }
- /**
- * Function that takes an input format and UTF encoding and returns the appropriate function used to convert the input.
- *
- * @param format The format of the input to be converted
- * @param utfType The string encoding to use for TEXT inputs.
- * @param bigEndianMod Modifier for whether hash function is big or small endian
- * @returns Function that will convert an input to a packed int array.
- */
- export function getStrConverter(
- format: FormatType,
- utfType: EncodingType,
- bigEndianMod: -1 | 1
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- ): (input: any, existingBin?: number[], existingBinLen?: number) => packedValue {
- /* Validate encoding */
- switch (utfType) {
- case "UTF8":
- /* Fallthrough */
- case "UTF16BE":
- /* Fallthrough */
- case "UTF16LE":
- /* Fallthrough */
- break;
- default:
- throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE");
- }
- /* Map inputFormat to the appropriate converter */
- switch (format) {
- case "HEX":
- /**
- * @param str String of hexadecimal bytes to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @returns Hashmap of the packed values.
- */
- return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
- return hex2packed(str, existingBin, existingBinLen, bigEndianMod);
- };
- case "TEXT":
- /**
- * @param str Unicode string to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @returns Hashmap of the packed values.
- */
- return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
- return str2packed(str, utfType, existingBin, existingBinLen, bigEndianMod);
- };
- case "B64":
- /**
- * @param str Base64-encoded string to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @returns Hashmap of the packed values.
- */
- return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
- return b642packed(str, existingBin, existingBinLen, bigEndianMod);
- };
- case "BYTES":
- /**
- * @param str String of raw bytes to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @returns Hashmap of the packed values.
- */
- return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
- return bytes2packed(str, existingBin, existingBinLen, bigEndianMod);
- };
- case "ARRAYBUFFER":
- try {
- new ArrayBuffer(0);
- } catch (ignore) {
- throw new Error(arraybuffer_error);
- }
- /**
- * @param arr ArrayBuffer to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @returns Hashmap of the packed values.
- */
- return function (arr: ArrayBuffer, existingBin?: number[], existingBinLen?: number): packedValue {
- return arraybuffer2packed(arr, existingBin, existingBinLen, bigEndianMod);
- };
- case "UINT8ARRAY":
- try {
- new Uint8Array(0);
- } catch (ignore) {
- throw new Error(uint8array_error);
- }
- /**
- * @param arr Uint8Array to be converted to binary representation.
- * @param existingPacked A packed int array of bytes to append the results to.
- * @param existingPackedLen The number of bits in `existingPacked` array.
- * @returns Hashmap of the packed values.
- */
- return function (arr: Uint8Array, existingBin?: number[], existingBinLen?: number): packedValue {
- return uint8array2packed(arr, existingBin, existingBinLen, bigEndianMod);
- };
- default:
- throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
- }
- }
- /**
- * Convert an array of words to a hexadecimal string.
- *
- * toString() won't work here because it removes preceding zeros (e.g. 0x00000001.toString === "1" rather than
- * "00000001" and 0.toString(16) === "0" rather than "00").
- *
- * @param packed Array of integers to be converted.
- * @param outputLength Length of output in bits.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @param formatOpts Hashmap containing validated output formatting options.
- * @returns Hexadecimal representation of `packed`.
- */
- export function packed2hex(
- packed: number[],
- outputLength: number,
- bigEndianMod: -1 | 1,
- formatOpts: { outputUpper: boolean; b64Pad: string }
- ): string {
- const hex_tab = "0123456789abcdef";
- let str = "",
- i,
- srcByte;
- const length = outputLength / 8,
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < length; i += 1) {
- /* The below is more than a byte but it gets taken care of later */
- srcByte = packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)));
- str += hex_tab.charAt((srcByte >>> 4) & 0xf) + hex_tab.charAt(srcByte & 0xf);
- }
- return formatOpts["outputUpper"] ? str.toUpperCase() : str;
- }
- /**
- * Convert an array of words to a base-64 string.
- *
- * @param packed Array of integers to be converted.
- * @param outputLength Length of output in bits.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @param formatOpts Hashmap containing validated output formatting options.
- * @returns Base64-encoded representation of `packed`.
- */
- export function packed2b64(
- packed: number[],
- outputLength: number,
- bigEndianMod: -1 | 1,
- formatOpts: { outputUpper: boolean; b64Pad: string }
- ): string {
- let str = "",
- i,
- j,
- triplet,
- int1,
- int2;
- const length = outputLength / 8,
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < length; i += 3) {
- int1 = i + 1 < length ? packed[(i + 1) >>> 2] : 0;
- int2 = i + 2 < length ? packed[(i + 2) >>> 2] : 0;
- triplet =
- (((packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff) << 16) |
- (((int1 >>> (8 * (shiftModifier + bigEndianMod * ((i + 1) % 4)))) & 0xff) << 8) |
- ((int2 >>> (8 * (shiftModifier + bigEndianMod * ((i + 2) % 4)))) & 0xff);
- for (j = 0; j < 4; j += 1) {
- if (i * 8 + j * 6 <= outputLength) {
- str += b64Tab.charAt((triplet >>> (6 * (3 - j))) & 0x3f);
- } else {
- str += formatOpts["b64Pad"];
- }
- }
- }
- return str;
- }
- /**
- * Convert an array of words to raw bytes string.
- *
- * @param packed Array of integers to be converted.
- * @param outputLength Length of output in bits.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns Raw bytes representation of `packed`.
- */
- export function packed2bytes(packed: number[], outputLength: number, bigEndianMod: -1 | 1): string {
- let str = "",
- i,
- srcByte;
- const length = outputLength / 8,
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < length; i += 1) {
- srcByte = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
- str += String.fromCharCode(srcByte);
- }
- return str;
- }
- /**
- * Convert an array of words to an ArrayBuffer.
- *
- * @param packed Array of integers to be converted.
- * @param outputLength Length of output in bits.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns An ArrayBuffer containing bytes from `packed.
- */
- export function packed2arraybuffer(packed: number[], outputLength: number, bigEndianMod: -1 | 1): ArrayBuffer {
- let i;
- const length = outputLength / 8,
- retVal = new ArrayBuffer(length),
- arrView = new Uint8Array(retVal),
- shiftModifier = bigEndianMod === -1 ? 3 : 0;
- for (i = 0; i < length; i += 1) {
- arrView[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
- }
- return retVal;
- }
- /**
- * Convert an array of words to an Uint8Array.
- *
- * @param packed Array of integers to be converted.
- * @param outputLength Length of output in bits.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @returns An Uint8Array containing bytes from `packed.
- */
- export function packed2uint8array(packed: number[], outputLength: number, bigEndianMod: -1 | 1): Uint8Array {
- let i;
- const length = outputLength / 8,
- shiftModifier = bigEndianMod === -1 ? 3 : 0,
- retVal = new Uint8Array(length);
- for (i = 0; i < length; i += 1) {
- retVal[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
- }
- return retVal;
- }
- /**
- * Function that takes an output format and associated parameters and returns a function that converts packed integers
- * to that format.
- *
- * @param format The desired output formatting.
- * @param outputBinLen Output length in bits.
- * @param bigEndianMod Modifier for whether hash function is big or small endian.
- * @param outputOptions Hashmap of output formatting options
- * @returns Function that will convert a packed integer array to desired format.
- */
- export function getOutputConverter(
- format: "HEX" | "B64" | "BYTES",
- outputBinLen: number,
- bigEndianMod: -1 | 1,
- outputOptions: { outputUpper: boolean; b64Pad: string }
- ): (binarray: number[]) => string;
- export function getOutputConverter(
- format: "ARRAYBUFFER",
- outputBinLen: number,
- bigEndianMod: -1 | 1,
- outputOptions: { outputUpper: boolean; b64Pad: string }
- ): (binarray: number[]) => ArrayBuffer;
- export function getOutputConverter(
- format: "UINT8ARRAY",
- outputBinLen: number,
- bigEndianMod: -1 | 1,
- outputOptions: { outputUpper: boolean; b64Pad: string }
- ): (binarray: number[]) => Uint8Array;
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- export function getOutputConverter(format: any, outputBinLen: any, bigEndianMod: any, outputOptions: any): any {
- switch (format) {
- case "HEX":
- return function (binarray: number[]): string {
- return packed2hex(binarray, outputBinLen, bigEndianMod, outputOptions);
- };
- case "B64":
- return function (binarray: number[]): string {
- return packed2b64(binarray, outputBinLen, bigEndianMod, outputOptions);
- };
- case "BYTES":
- return function (binarray: number[]): string {
- return packed2bytes(binarray, outputBinLen, bigEndianMod);
- };
- case "ARRAYBUFFER":
- try {
- /* Need to test ArrayBuffer support */
- new ArrayBuffer(0);
- } catch (ignore) {
- throw new Error(arraybuffer_error);
- }
- return function (binarray: number[]): ArrayBuffer {
- return packed2arraybuffer(binarray, outputBinLen, bigEndianMod);
- };
- case "UINT8ARRAY":
- try {
- /* Need to test Uint8Array support */
- new Uint8Array(0);
- } catch (ignore) {
- throw new Error(uint8array_error);
- }
- return function (binarray: number[]): Uint8Array {
- return packed2uint8array(binarray, outputBinLen, bigEndianMod);
- };
- default:
- throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
- }
- }
|