UNPKG

21.1 kBPlain TextView Raw
1import { packedValue, EncodingType, FormatType } from "./custom_types";
2/**
3 * Return type for all the *2packed functions
4 */
5const b64Tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6
7const arraybuffer_error = "ARRAYBUFFER not supported by this environment";
8const uint8array_error = "UINT8ARRAY not supported by this environment";
9
10/**
11 * Convert a string to an array of words.
12 *
13 * There is a known bug with an odd number of existing bytes and using a UTF-16 encoding. However, this function is
14 * used such that the existing bytes are always a result of a previous UTF-16 str2packed call and therefore there
15 * should never be an odd number of existing bytes.
16
17 * @param str Unicode string to be converted to binary representation.
18 * @param utfType The Unicode type to use to encode the source string.
19 * @param existingPacked A packed int array of bytes to append the results to.
20 * @param existingPackedLen The number of bits in `existingPacked`.
21 * @param bigEndianMod Modifier for whether hash function is big or small endian.
22 * @returns Hashmap of the packed values.
23 */
24function str2packed(
25 str: string,
26 utfType: EncodingType,
27 existingPacked: number[] | undefined,
28 existingPackedLen: number | undefined,
29 bigEndianMod: -1 | 1
30): packedValue {
31 let codePnt,
32 codePntArr,
33 byteCnt = 0,
34 i,
35 j,
36 intOffset,
37 byteOffset,
38 shiftModifier,
39 transposeBytes;
40
41 existingPackedLen = existingPackedLen || 0;
42 const packed = existingPacked || [0],
43 existingByteLen = existingPackedLen >>> 3;
44
45 if ("UTF8" === utfType) {
46 shiftModifier = bigEndianMod === -1 ? 3 : 0;
47 for (i = 0; i < str.length; i += 1) {
48 codePnt = str.charCodeAt(i);
49 codePntArr = [];
50
51 if (0x80 > codePnt) {
52 codePntArr.push(codePnt);
53 } else if (0x800 > codePnt) {
54 codePntArr.push(0xc0 | (codePnt >>> 6));
55 codePntArr.push(0x80 | (codePnt & 0x3f));
56 } else if (0xd800 > codePnt || 0xe000 <= codePnt) {
57 codePntArr.push(0xe0 | (codePnt >>> 12), 0x80 | ((codePnt >>> 6) & 0x3f), 0x80 | (codePnt & 0x3f));
58 } else {
59 i += 1;
60 codePnt = 0x10000 + (((codePnt & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
61 codePntArr.push(
62 0xf0 | (codePnt >>> 18),
63 0x80 | ((codePnt >>> 12) & 0x3f),
64 0x80 | ((codePnt >>> 6) & 0x3f),
65 0x80 | (codePnt & 0x3f)
66 );
67 }
68
69 for (j = 0; j < codePntArr.length; j += 1) {
70 byteOffset = byteCnt + existingByteLen;
71 intOffset = byteOffset >>> 2;
72 while (packed.length <= intOffset) {
73 packed.push(0);
74 }
75 /* Known bug kicks in here */
76 packed[intOffset] |= codePntArr[j] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
77 byteCnt += 1;
78 }
79 }
80 } else {
81 /* UTF16BE or UTF16LE */
82 shiftModifier = bigEndianMod === -1 ? 2 : 0;
83 /* Internally strings are UTF-16BE so transpose bytes under two conditions:
84 * need LE and not switching endianness due to SHA-3
85 * need BE and switching endianness due to SHA-3 */
86 transposeBytes = ("UTF16LE" === utfType && bigEndianMod !== 1) || ("UTF16LE" !== utfType && bigEndianMod === 1);
87 for (i = 0; i < str.length; i += 1) {
88 codePnt = str.charCodeAt(i);
89 if (transposeBytes === true) {
90 j = codePnt & 0xff;
91 codePnt = (j << 8) | (codePnt >>> 8);
92 }
93
94 byteOffset = byteCnt + existingByteLen;
95 intOffset = byteOffset >>> 2;
96 while (packed.length <= intOffset) {
97 packed.push(0);
98 }
99 packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
100 byteCnt += 2;
101 }
102 }
103 return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
104}
105
106/**
107 * Convert a hex string to an array of words.
108 *
109 * @param str Hexadecimal string to be converted to binary representation.
110 * @param existingPacked A packed int array of bytes to append the results to.
111 * @param existingPackedLen The number of bits in `existingPacked` array.
112 * @param bigEndianMod Modifier for whether hash function is big or small endian.
113 * @returns Hashmap of the packed values.
114 */
115function hex2packed(
116 str: string,
117 existingPacked: number[] | undefined,
118 existingPackedLen: number | undefined,
119 bigEndianMod: -1 | 1
120): packedValue {
121 let i, num, intOffset, byteOffset;
122
123 if (0 !== str.length % 2) {
124 throw new Error("String of HEX type must be in byte increments");
125 }
126
127 existingPackedLen = existingPackedLen || 0;
128 const packed = existingPacked || [0],
129 existingByteLen = existingPackedLen >>> 3,
130 shiftModifier = bigEndianMod === -1 ? 3 : 0;
131
132 for (i = 0; i < str.length; i += 2) {
133 num = parseInt(str.substr(i, 2), 16);
134 if (!isNaN(num)) {
135 byteOffset = (i >>> 1) + existingByteLen;
136 intOffset = byteOffset >>> 2;
137 while (packed.length <= intOffset) {
138 packed.push(0);
139 }
140 packed[intOffset] |= num << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
141 } else {
142 throw new Error("String of HEX type contains invalid characters");
143 }
144 }
145
146 return { value: packed, binLen: str.length * 4 + existingPackedLen };
147}
148
149/**
150 * Convert a string of raw bytes to an array of words.
151 *
152 * @param str String of raw bytes to be converted to binary representation.
153 * @param existingPacked A packed int array of bytes to append the results to.
154 * @param existingPackedLen The number of bits in `existingPacked` array.
155 * @param bigEndianMod Modifier for whether hash function is big or small endian.
156 * @returns Hashmap of the packed values.
157 */
158function bytes2packed(
159 str: string,
160 existingPacked: number[] | undefined,
161 existingPackedLen: number | undefined,
162 bigEndianMod: -1 | 1
163): packedValue {
164 let codePnt, i, intOffset, byteOffset;
165
166 existingPackedLen = existingPackedLen || 0;
167 const packed = existingPacked || [0],
168 existingByteLen = existingPackedLen >>> 3,
169 shiftModifier = bigEndianMod === -1 ? 3 : 0;
170
171 for (i = 0; i < str.length; i += 1) {
172 codePnt = str.charCodeAt(i);
173
174 byteOffset = i + existingByteLen;
175 intOffset = byteOffset >>> 2;
176 if (packed.length <= intOffset) {
177 packed.push(0);
178 }
179 packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
180 }
181
182 return { value: packed, binLen: str.length * 8 + existingPackedLen };
183}
184
185/**
186 * Convert a base-64 string to an array of words.
187 *
188 * @param str Base64-encoded string to be converted to binary representation.
189 * @param existingPacked A packed int array of bytes to append the results to.
190 * @param existingPackedLen The number of bits in `existingPacked` array.
191 * @param bigEndianMod Modifier for whether hash function is big or small endian.
192 * @returns Hashmap of the packed values.
193 */
194function b642packed(
195 str: string,
196 existingPacked: number[] | undefined,
197 existingPackedLen: number | undefined,
198 bigEndianMod: -1 | 1
199): packedValue {
200 let byteCnt = 0,
201 index,
202 i,
203 j,
204 tmpInt,
205 strPart,
206 intOffset,
207 byteOffset;
208
209 existingPackedLen = existingPackedLen || 0;
210 const packed = existingPacked || [0],
211 existingByteLen = existingPackedLen >>> 3,
212 shiftModifier = bigEndianMod === -1 ? 3 : 0,
213 firstEqual = str.indexOf("=");
214
215 if (-1 === str.search(/^[a-zA-Z0-9=+/]+$/)) {
216 throw new Error("Invalid character in base-64 string");
217 }
218
219 str = str.replace(/=/g, "");
220 if (-1 !== firstEqual && firstEqual < str.length) {
221 throw new Error("Invalid '=' found in base-64 string");
222 }
223
224 for (i = 0; i < str.length; i += 4) {
225 strPart = str.substr(i, 4);
226 tmpInt = 0;
227
228 for (j = 0; j < strPart.length; j += 1) {
229 index = b64Tab.indexOf(strPart.charAt(j));
230 tmpInt |= index << (18 - 6 * j);
231 }
232
233 for (j = 0; j < strPart.length - 1; j += 1) {
234 byteOffset = byteCnt + existingByteLen;
235 intOffset = byteOffset >>> 2;
236 while (packed.length <= intOffset) {
237 packed.push(0);
238 }
239 packed[intOffset] |=
240 ((tmpInt >>> (16 - j * 8)) & 0xff) << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
241 byteCnt += 1;
242 }
243 }
244
245 return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
246}
247
248/**
249 * Convert an Uint8Array to an array of words.
250 *
251 * @param arr Uint8Array to be converted to binary representation.
252 * @param existingPacked A packed int array of bytes to append the results to.
253 * @param existingPackedLen The number of bits in `existingPacked` array.
254 * @param bigEndianMod Modifier for whether hash function is big or small endian.
255 * @returns Hashmap of the packed values.
256 */
257function uint8array2packed(
258 arr: Uint8Array,
259 existingPacked: number[] | undefined,
260 existingPackedLen: number | undefined,
261 bigEndianMod: -1 | 1
262): packedValue {
263 let i, intOffset, byteOffset;
264
265 existingPackedLen = existingPackedLen || 0;
266 const packed = existingPacked || [0],
267 existingByteLen = existingPackedLen >>> 3,
268 shiftModifier = bigEndianMod === -1 ? 3 : 0;
269
270 for (i = 0; i < arr.length; i += 1) {
271 byteOffset = i + existingByteLen;
272 intOffset = byteOffset >>> 2;
273 if (packed.length <= intOffset) {
274 packed.push(0);
275 }
276 packed[intOffset] |= arr[i] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
277 }
278
279 return { value: packed, binLen: arr.length * 8 + existingPackedLen };
280}
281
282/**
283 * Convert an ArrayBuffer to an array of words
284 *
285 * @param arr ArrayBuffer to be converted to binary representation.
286 * @param existingPacked A packed int array of bytes to append the results to.
287 * @param existingPackedLen The number of bits in `existingPacked` array.
288 * @param bigEndianMod Modifier for whether hash function is big or small endian.
289 * @returns Hashmap of the packed values.
290 */
291function arraybuffer2packed(
292 arr: ArrayBuffer,
293 existingPacked: number[] | undefined,
294 existingPackedLen: number | undefined,
295 bigEndianMod: -1 | 1
296): packedValue {
297 return uint8array2packed(new Uint8Array(arr), existingPacked, existingPackedLen, bigEndianMod);
298}
299
300/**
301 * Function that takes an input format and UTF encoding and returns the appropriate function used to convert the input.
302 *
303 * @param format The format of the input to be converted
304 * @param utfType The string encoding to use for TEXT inputs.
305 * @param bigEndianMod Modifier for whether hash function is big or small endian
306 * @returns Function that will convert an input to a packed int array.
307 */
308export function getStrConverter(
309 format: FormatType,
310 utfType: EncodingType,
311 bigEndianMod: -1 | 1
312 /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
313): (input: any, existingBin?: number[], existingBinLen?: number) => packedValue {
314 /* Validate encoding */
315 switch (utfType) {
316 case "UTF8":
317 /* Fallthrough */
318 case "UTF16BE":
319 /* Fallthrough */
320 case "UTF16LE":
321 /* Fallthrough */
322 break;
323 default:
324 throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE");
325 }
326
327 /* Map inputFormat to the appropriate converter */
328 switch (format) {
329 case "HEX":
330 /**
331 * @param str String of hexadecimal bytes to be converted to binary representation.
332 * @param existingPacked A packed int array of bytes to append the results to.
333 * @param existingPackedLen The number of bits in `existingPacked` array.
334 * @returns Hashmap of the packed values.
335 */
336 return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
337 return hex2packed(str, existingBin, existingBinLen, bigEndianMod);
338 };
339 case "TEXT":
340 /**
341 * @param str Unicode string to be converted to binary representation.
342 * @param existingPacked A packed int array of bytes to append the results to.
343 * @param existingPackedLen The number of bits in `existingPacked` array.
344 * @returns Hashmap of the packed values.
345 */
346 return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
347 return str2packed(str, utfType, existingBin, existingBinLen, bigEndianMod);
348 };
349 case "B64":
350 /**
351 * @param str Base64-encoded string to be converted to binary representation.
352 * @param existingPacked A packed int array of bytes to append the results to.
353 * @param existingPackedLen The number of bits in `existingPacked` array.
354 * @returns Hashmap of the packed values.
355 */
356 return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
357 return b642packed(str, existingBin, existingBinLen, bigEndianMod);
358 };
359 case "BYTES":
360 /**
361 * @param str String of raw bytes to be converted to binary representation.
362 * @param existingPacked A packed int array of bytes to append the results to.
363 * @param existingPackedLen The number of bits in `existingPacked` array.
364 * @returns Hashmap of the packed values.
365 */
366 return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
367 return bytes2packed(str, existingBin, existingBinLen, bigEndianMod);
368 };
369 case "ARRAYBUFFER":
370 try {
371 new ArrayBuffer(0);
372 } catch (ignore) {
373 throw new Error(arraybuffer_error);
374 }
375 /**
376 * @param arr ArrayBuffer to be converted to binary representation.
377 * @param existingPacked A packed int array of bytes to append the results to.
378 * @param existingPackedLen The number of bits in `existingPacked` array.
379 * @returns Hashmap of the packed values.
380 */
381 return function (arr: ArrayBuffer, existingBin?: number[], existingBinLen?: number): packedValue {
382 return arraybuffer2packed(arr, existingBin, existingBinLen, bigEndianMod);
383 };
384 case "UINT8ARRAY":
385 try {
386 new Uint8Array(0);
387 } catch (ignore) {
388 throw new Error(uint8array_error);
389 }
390 /**
391 * @param arr Uint8Array to be converted to binary representation.
392 * @param existingPacked A packed int array of bytes to append the results to.
393 * @param existingPackedLen The number of bits in `existingPacked` array.
394 * @returns Hashmap of the packed values.
395 */
396 return function (arr: Uint8Array, existingBin?: number[], existingBinLen?: number): packedValue {
397 return uint8array2packed(arr, existingBin, existingBinLen, bigEndianMod);
398 };
399 default:
400 throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
401 }
402}
403
404/**
405 * Convert an array of words to a hexadecimal string.
406 *
407 * toString() won't work here because it removes preceding zeros (e.g. 0x00000001.toString === "1" rather than
408 * "00000001" and 0.toString(16) === "0" rather than "00").
409 *
410 * @param packed Array of integers to be converted.
411 * @param outputLength Length of output in bits.
412 * @param bigEndianMod Modifier for whether hash function is big or small endian.
413 * @param formatOpts Hashmap containing validated output formatting options.
414 * @returns Hexadecimal representation of `packed`.
415 */
416export function packed2hex(
417 packed: number[],
418 outputLength: number,
419 bigEndianMod: -1 | 1,
420 formatOpts: { outputUpper: boolean; b64Pad: string }
421): string {
422 const hex_tab = "0123456789abcdef";
423 let str = "",
424 i,
425 srcByte;
426
427 const length = outputLength / 8,
428 shiftModifier = bigEndianMod === -1 ? 3 : 0;
429
430 for (i = 0; i < length; i += 1) {
431 /* The below is more than a byte but it gets taken care of later */
432 srcByte = packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)));
433 str += hex_tab.charAt((srcByte >>> 4) & 0xf) + hex_tab.charAt(srcByte & 0xf);
434 }
435
436 return formatOpts["outputUpper"] ? str.toUpperCase() : str;
437}
438
439/**
440 * Convert an array of words to a base-64 string.
441 *
442 * @param packed Array of integers to be converted.
443 * @param outputLength Length of output in bits.
444 * @param bigEndianMod Modifier for whether hash function is big or small endian.
445 * @param formatOpts Hashmap containing validated output formatting options.
446 * @returns Base64-encoded representation of `packed`.
447 */
448export function packed2b64(
449 packed: number[],
450 outputLength: number,
451 bigEndianMod: -1 | 1,
452 formatOpts: { outputUpper: boolean; b64Pad: string }
453): string {
454 let str = "",
455 i,
456 j,
457 triplet,
458 int1,
459 int2;
460
461 const length = outputLength / 8,
462 shiftModifier = bigEndianMod === -1 ? 3 : 0;
463
464 for (i = 0; i < length; i += 3) {
465 int1 = i + 1 < length ? packed[(i + 1) >>> 2] : 0;
466 int2 = i + 2 < length ? packed[(i + 2) >>> 2] : 0;
467 triplet =
468 (((packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff) << 16) |
469 (((int1 >>> (8 * (shiftModifier + bigEndianMod * ((i + 1) % 4)))) & 0xff) << 8) |
470 ((int2 >>> (8 * (shiftModifier + bigEndianMod * ((i + 2) % 4)))) & 0xff);
471 for (j = 0; j < 4; j += 1) {
472 if (i * 8 + j * 6 <= outputLength) {
473 str += b64Tab.charAt((triplet >>> (6 * (3 - j))) & 0x3f);
474 } else {
475 str += formatOpts["b64Pad"];
476 }
477 }
478 }
479 return str;
480}
481
482/**
483 * Convert an array of words to raw bytes string.
484 *
485 * @param packed Array of integers to be converted.
486 * @param outputLength Length of output in bits.
487 * @param bigEndianMod Modifier for whether hash function is big or small endian.
488 * @returns Raw bytes representation of `packed`.
489 */
490export function packed2bytes(packed: number[], outputLength: number, bigEndianMod: -1 | 1): string {
491 let str = "",
492 i,
493 srcByte;
494
495 const length = outputLength / 8,
496 shiftModifier = bigEndianMod === -1 ? 3 : 0;
497
498 for (i = 0; i < length; i += 1) {
499 srcByte = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
500 str += String.fromCharCode(srcByte);
501 }
502
503 return str;
504}
505
506/**
507 * Convert an array of words to an ArrayBuffer.
508 *
509 * @param packed Array of integers to be converted.
510 * @param outputLength Length of output in bits.
511 * @param bigEndianMod Modifier for whether hash function is big or small endian.
512 * @returns An ArrayBuffer containing bytes from `packed.
513 */
514export function packed2arraybuffer(packed: number[], outputLength: number, bigEndianMod: -1 | 1): ArrayBuffer {
515 let i;
516 const length = outputLength / 8,
517 retVal = new ArrayBuffer(length),
518 arrView = new Uint8Array(retVal),
519 shiftModifier = bigEndianMod === -1 ? 3 : 0;
520
521 for (i = 0; i < length; i += 1) {
522 arrView[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
523 }
524
525 return retVal;
526}
527
528/**
529 * Convert an array of words to an Uint8Array.
530 *
531 * @param packed Array of integers to be converted.
532 * @param outputLength Length of output in bits.
533 * @param bigEndianMod Modifier for whether hash function is big or small endian.
534 * @returns An Uint8Array containing bytes from `packed.
535 */
536export function packed2uint8array(packed: number[], outputLength: number, bigEndianMod: -1 | 1): Uint8Array {
537 let i;
538 const length = outputLength / 8,
539 shiftModifier = bigEndianMod === -1 ? 3 : 0,
540 retVal = new Uint8Array(length);
541
542 for (i = 0; i < length; i += 1) {
543 retVal[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
544 }
545
546 return retVal;
547}
548
549/**
550 * Function that takes an output format and associated parameters and returns a function that converts packed integers
551 * to that format.
552 *
553 * @param format The desired output formatting.
554 * @param outputBinLen Output length in bits.
555 * @param bigEndianMod Modifier for whether hash function is big or small endian.
556 * @param outputOptions Hashmap of output formatting options
557 * @returns Function that will convert a packed integer array to desired format.
558 */
559export function getOutputConverter(
560 format: "HEX" | "B64" | "BYTES" | "ARRAYBUFFER" | "UINT8ARRAY",
561 outputBinLen: number,
562 bigEndianMod: -1 | 1,
563 outputOptions: { outputUpper: boolean; b64Pad: string }
564 /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
565): (binarray: number[]) => any {
566 switch (format) {
567 case "HEX":
568 return function (binarray): string {
569 return packed2hex(binarray, outputBinLen, bigEndianMod, outputOptions);
570 };
571 case "B64":
572 return function (binarray): string {
573 return packed2b64(binarray, outputBinLen, bigEndianMod, outputOptions);
574 };
575 case "BYTES":
576 return function (binarray): string {
577 return packed2bytes(binarray, outputBinLen, bigEndianMod);
578 };
579 case "ARRAYBUFFER":
580 try {
581 /* Need to test ArrayBuffer support */
582 new ArrayBuffer(0);
583 } catch (ignore) {
584 throw new Error(arraybuffer_error);
585 }
586 return function (binarray): ArrayBuffer {
587 return packed2arraybuffer(binarray, outputBinLen, bigEndianMod);
588 };
589 case "UINT8ARRAY":
590 try {
591 /* Need to test Uint8Array support */
592 new Uint8Array(0);
593 } catch (ignore) {
594 throw new Error(uint8array_error);
595 }
596 return function (binarray): Uint8Array {
597 return packed2uint8array(binarray, outputBinLen, bigEndianMod);
598 };
599 default:
600 throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
601 }
602}