1 // ♓🌟 Piscēs ∷ binary.js
2 // ====================================================================
4 // Copyright © 2020–2023 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
10 import { fill
, map
, reduce
} from "./collection.js";
11 import { bind
, call
} from "./function.js";
12 import { ceil
, floor
} from "./numeric.js";
13 import { hasOwnProperty
, objectCreate
} from "./object.js";
20 import { ITERATOR
} from "./value.js";
22 const Buffer
= ArrayBuffer
;
23 const View
= DataView
;
24 const TypedArray
= Object
.getPrototypeOf(Uint8Array
);
25 const { prototype: arrayPrototype
} = Array
;
26 const { prototype: bufferPrototype
} = Buffer
;
27 const { prototype: rePrototype
} = RegExp
;
28 const { prototype: typedArrayPrototype
} = TypedArray
;
29 const { prototype: viewPrototype
} = View
;
31 const { [ITERATOR
]: arrayIterator
} = arrayPrototype
;
33 next
: arrayIteratorNext
,
34 } = Object
.getPrototypeOf([][ITERATOR
]());
35 const argumentIterablePrototype
= {
40 call(arrayIterator
, this.args
, []),
46 const binaryCodeUnitIterablePrototype
= {
51 call(arrayIterator
, this, []),
58 const { exec
: reExec
} = rePrototype
;
59 const getBufferByteLength
=
60 Object
.getOwnPropertyDescriptor(bufferPrototype
, "byteLength").get;
61 const getTypedArrayBuffer
=
62 Object
.getOwnPropertyDescriptor(typedArrayPrototype
, "buffer").get;
64 Object
.getOwnPropertyDescriptor(viewPrototype
, "buffer").get;
66 getUint8
: viewGetUint8
,
67 setUint8
: viewSetUint8
,
68 setUint16
: viewSetUint16
,
72 * Returns an ArrayBuffer for encoding generated from the provided
75 const bufferFromArgs
= ($, $s
) => {
77 // Try just getting the array buffer associated with the first
78 // argument and returning it if possible.
79 return toArrayBuffer($);
81 // There is no array buffer associated with the first argument.
83 // Construct a string and convert it to an array buffer instead.
89 (result
, ucsCharacter
, index
) => (
90 call(viewSetUint16
, result
, [
92 getCodeUnit(ucsCharacter
, 0),
95 new View(new Buffer(string
.length
* 2)),
101 : hasOwnProperty($, "raw")
104 ...objectCreate(argumentIterablePrototype
, {
114 * Returns the result of decoding the provided base16 string into an
117 * ※ This function is not exposed.
119 const decodeBase16
= (source
) => {
123 const code
= getCodeUnit(ucsCharacter
, 0);
124 const result
= code
>= 0x30 && code
<= 0x39
126 : code
>= 0x41 && code
<= 0x46
128 : code
>= 0x61 && code
<= 0x66
132 // The source contains an invalid character.
133 throw new RangeError(
134 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
137 // The source contains a valid character with a recognized
143 const { length
} = u4s
;
144 if (length
% 2 === 1) {
145 // The length is such that an entire letter would be dropped during
146 // a forgiving decode.
147 throw new RangeError(
148 `Piscēs: Base16 string has invalid length: ${source}.`,
151 // Every letter contributes at least some bits to the result.
152 const dataView
= new View(new Buffer(floor(length
/ 2)));
153 for (let index
= 0; index
< length
- 1;) {
154 // Iterate over the characters and assign their bits to the
156 call(viewSetUint8
, dataView
, [
158 (u4s
[index
] << 4) | u4s
[++index
, index
++],
161 return call(getViewBuffer
, dataView
, []);
166 * Returns the result of decoding the provided base32 string into an
169 * If the second argument is truthy, uses Crockford’s encoding rather
170 * than the RFC’s (see <https://www.crockford.com/base32.html>). This
171 * is more human‐friendly and tolerant. Check digits are not supported.
173 * ※ This function is not exposed.
175 const decodeBase32
= (source
, wrmg
) => {
178 ? stringReplace(source
, /-/gu
, "")
179 : source
.length
% 8 === 0
180 ? stringReplace(source
, /(?:=|={3,4}|={6})$/u, "")
183 const code
= getCodeUnit(ucsCharacter
, 0);
185 ? code
>= 0x30 && code
<= 0x39
187 : code
>= 0x41 && code
<= 0x48
191 : code
>= 0x4A && code
<= 0x4B
195 : code
>= 0x4D && code
<= 0x4E
199 : code
>= 0x50 && code
<= 0x54
202 : code
>= 0x56 && code
<= 0x5A
204 : code
>= 0x61 && code
<= 0x68
208 : code
>= 0x6A && code
<= 0x6B
212 : code
>= 0x6D && code
<= 0x6E
216 : code
>= 0x70 && code
<= 0x74
219 : code
>= 0x76 && code
<= 0x7A
222 : code
>= 0x41 && code
<= 0x5A
224 : code
>= 0x61 && code
<= 0x7A
225 ? code
- 97 // same result as above; case insensitive
226 : code
>= 0x32 && code
<= 0x37
227 ? code
- 24 // digits 2–7 map to 26–31
230 // The source contains an invalid character.
231 throw new RangeError(
232 `Piscēs: Invalid character in Base32: ${ucsCharacter}.`,
235 // The source contains a valid character with a recognized
241 const { length
} = u5s
;
242 const lengthMod8
= length
% 8;
243 if (lengthMod8
=== 1 || lengthMod8
=== 3 || lengthMod8
=== 6) {
244 // The length is such that an entire letter would be dropped during
245 // a forgiving decode.
246 throw new RangeError(
247 `Piscēs: Base32 string has invalid length: ${source}.`,
250 // Every letter contributes at least some bits to the result.
251 const dataView
= new View(new Buffer(floor(length
* 5 / 8)));
252 for (let index
= 0; index
< length
- 1;) {
253 // Iterate over the characters and assign their bits to the
256 // The final index is not handled; if the string is not divisible
257 // by 8, some bits might be dropped. This matches the “forgiving
258 // decode” behaviour specified by WhatW·G for base64.
259 const dataIndex
= ceil(index
* 5 / 8);
260 const remainder
= index
% 8;
261 call(viewSetUint8
, dataView
, [
264 ? u5s
[index
] << 3 | u5s
[++index
] >> 2
266 ? u5s
[index
] << 6 | u5s
[++index
] << 1 | u5s
[++index
] >> 4
268 ? u5s
[index
] << 4 | u5s
[++index
] >> 1
270 ? u5s
[index
] << 7 | u5s
[++index
] << 2 | u5s
[++index
] >> 3
271 : u5s
[index
] << 5 | u5s
[++index
, index
++], // remainder === 6
274 return call(getViewBuffer
, dataView
, []);
279 * Returns the result of decoding the provided base64 string into an
282 * ※ This function is not exposed.
284 const decodeBase64
= (source
, safe
= false) => {
286 source
.length
% 4 === 0
287 ? stringReplace(source
, /={1,2}$/u, "")
290 const code
= getCodeUnit(ucsCharacter
, 0);
291 const result
= code
>= 0x41 && code
<= 0x5A
293 : code
>= 0x61 && code
<= 0x7A
295 : code
>= 0x30 && code
<= 0x39
297 : code
=== (safe
? 0x2D : 0x2B)
299 : code
=== (safe
? 0x5F : 0x2F)
303 // The source contains an invalid character.
304 throw new RangeError(
305 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
308 // The source contains a valid character with a recognized
314 const { length
} = u6s
;
315 if (length
% 4 === 1) {
316 // The length is such that an entire letter would be dropped during
317 // a forgiving decode.
318 throw new RangeError(
319 `Piscēs: Base64 string has invalid length: ${source}.`,
322 // Every letter contributes at least some bits to the result.
323 const dataView
= new View(new Buffer(floor(length
* 3 / 4)));
324 for (let index
= 0; index
< length
- 1;) {
325 // Iterate over the characters and assign their bits to the
328 // The final index is not handled; if the string is not divisible
329 // by 4, some bits might be dropped. This matches the “forgiving
330 // decode” behaviour specified by WhatW·G for base64.
331 const dataIndex
= ceil(index
* 3 / 4);
332 const remainder
= index
% 4;
333 call(viewSetUint8
, dataView
, [
336 ? u6s
[index
] << 2 | u6s
[++index
] >> 4
338 ? u6s
[index
] << 4 | u6s
[++index
] >> 2
339 : u6s
[index
] << 6 | u6s
[++index
, index
++], // remainder === 2
342 return call(getViewBuffer
, dataView
, []);
347 * Returns the result of encoding the provided ArrayBuffer into a
350 * ※ This function is not exposed.
352 const encodeBase16
= (buffer
) => {
353 const dataView
= new View(buffer
);
354 const byteLength
= call(getBufferByteLength
, buffer
, []);
355 const minimumLengthOfResults
= byteLength
* 2;
356 const resultingCodeUnits
= fill(
358 binaryCodeUnitIterablePrototype
,
359 { length
: { value
: minimumLengthOfResults
} },
363 for (let index
= 0; index
< byteLength
;) {
364 // Iterate over the bytes and generate code units for them.
365 const codeUnitIndex
= index
* 2;
366 const datum
= call(viewGetUint8
, dataView
, [index
++]);
367 const u4s
= [datum
>> 4, datum
& 0xF];
368 for (let u4i
= 0; u4i
< 2; ++u4i
) {
369 // Handle the high four bits, then the low four bits.
371 const result
= u4
< 10 ? u4
+ 48 : u4
< 16 ? u4
+ 55 : -1;
373 // No mapping exists for these four bits.
375 // ※ This shouldn’t be possible!
376 throw new RangeError(
377 `Piscēs: Unexpected Base16 value: ${u4}.`,
380 // A mapping exists for the bits.
381 resultingCodeUnits
[codeUnitIndex
+ u4i
] = result
;
385 return stringFromCodeUnits(...resultingCodeUnits
);
389 * Returns the result of encoding the provided ArrayBuffer into a
392 * ※ This function is not exposed.
394 const encodeBase32
= (buffer
, wrmg
= false) => {
395 const dataView
= new View(buffer
);
396 const byteLength
= call(getBufferByteLength
, buffer
, []);
397 const minimumLengthOfResults
= ceil(byteLength
* 8 / 5);
398 const fillByte
= wrmg
? 0x2D : 0x3D;
399 const resultingCodeUnits
= fill(
401 binaryCodeUnitIterablePrototype
,
404 value
: minimumLengthOfResults
+
405 (8 - (minimumLengthOfResults
% 8)) % 8,
411 for (let index
= 0; index
< byteLength
;) {
412 // Iterate over the bytes and generate code units for them.
413 const codeUnitIndex
= ceil(index
* 8 / 5);
414 const currentIndex
= codeUnitIndex
+ +(
415 0b01011 & 1 << index
% 5 &&
416 resultingCodeUnits
[codeUnitIndex
] != fillByte
417 ); // bytes 0, 1 & 3 handle two letters; this is for the second
418 const remainder
= currentIndex
% 8;
419 const currentByte
= call(viewGetUint8
, dataView
, [index
]);
421 0b01011010 & 1 << remainder
&& ++index
< byteLength
422 // digits 1, 3, 4 & 6 span multiple bytes
423 ? call(viewGetUint8
, dataView
, [index
])
425 const u5
= remainder
=== 0
428 ? (currentByte
& 0b00000111) << 2 | nextByte
>> 6
430 ? (currentByte
& 0b00111111) >> 1
432 ? (currentByte
& 0b00000001) << 4 | nextByte
>> 4
434 ? (currentByte
& 0b00001111) << 1 | nextByte
>> 7
436 ? (currentByte
& 0b01111111) >> 2
438 ? (currentByte
& 0b00000011) << 3 | nextByte
>> 5
439 : (++index
, currentByte
& 0b00011111); // remainder === 7
441 ? u5
< 10 ? u5
+ 48 : u5
< 18
462 // No mapping exists for these five bits.
464 // ※ This shouldn’t be possible!
465 throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`);
467 // A mapping exists for the bits.
468 resultingCodeUnits
[currentIndex
] = result
;
471 const answer
= stringFromCodeUnits(...resultingCodeUnits
);
472 return wrmg
? answer
.replace(/-+$/u, "") : answer
;
476 * Returns the result of encoding the provided ArrayBuffer into a
479 * ※ This function is not exposed.
481 const encodeBase64
= (buffer
, safe
= false) => {
482 const dataView
= new View(buffer
);
483 const byteLength
= call(getBufferByteLength
, buffer
, []);
484 const minimumLengthOfResults
= ceil(byteLength
* 4 / 3);
485 const resultingCodeUnits
= fill(
487 binaryCodeUnitIterablePrototype
,
490 value
: minimumLengthOfResults
+
491 (4 - (minimumLengthOfResults
% 4)) % 4,
497 for (let index
= 0; index
< byteLength
;) {
498 // Iterate over the bytes and generate code units for them.
499 const codeUnitIndex
= ceil(index
* 4 / 3);
500 const currentIndex
= codeUnitIndex
+ +(
501 index
% 3 === 0 && resultingCodeUnits
[codeUnitIndex
] != 0x3D
502 ); // every third byte handles two letters; this is for the second
503 const remainder
= currentIndex
% 4;
504 const currentByte
= call(viewGetUint8
, dataView
, [index
]);
505 const nextByte
= remainder
% 3 && ++index
< byteLength
506 // digits 1 & 2 span multiple bytes
507 ? call(viewGetUint8
, dataView
, [index
])
509 const u6
= remainder
=== 0
512 ? (currentByte
& 0b00000011) << 4 | nextByte
>> 4
514 ? (currentByte
& 0b00001111) << 2 | nextByte
>> 6
515 : (++index
, currentByte
& 0b00111111); // remainder === 3
516 const result
= u6
< 26
523 ? (safe
? 0x2D : 0x2B)
525 ? (safe
? 0x5F : 0x2F)
528 // No mapping exists for these six bits.
530 // ※ This shouldn’t be possible!
531 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
533 // A mapping exists for the bits.
534 resultingCodeUnits
[currentIndex
] = result
;
537 return stringFromCodeUnits(...resultingCodeUnits
);
541 * Returns a source string generated from the arguments passed to a
544 * ※ This function is not exposed.
546 const sourceFromArgs
= ($, $s
) =>
548 typeof $ === "string" ? $ : hasOwnProperty($, "raw")
551 ...objectCreate(argumentIterablePrototype
, {
561 * Returns an ArrayBuffer generated from the provided base16 string.
563 * This function can also be used as a tag for a template literal. The
564 * literal will be interpreted akin to `String.raw`.
566 * ☡ This function throws if the provided string is not a valid base16
569 export const base16Binary
= ($, ...$s
) =>
570 decodeBase16(sourceFromArgs($, $s
));
573 * Returns a (big‐endian) base16 string created from the provided typed
574 * array, buffer, or (16‐bit) string.
576 * This function can also be used as a tag for a template literal. The
577 * literal will be interpreted akin to `String.raw`.
579 export const base16String
= ($, ...$s
) =>
580 encodeBase16(bufferFromArgs($, $s
));
583 * Returns an ArrayBuffer generated from the provided base32 string.
585 * This function can also be used as a tag for a template literal. The
586 * literal will be interpreted akin to `String.raw`.
588 * ☡ This function throws if the provided string is not a valid base32
591 export const base32Binary
= ($, ...$s
) =>
592 decodeBase32(sourceFromArgs($, $s
));
595 * Returns a (big‐endian) base32 string created from the provided typed
596 * array, buffer, or (16‐bit) string.
598 * This function can also be used as a tag for a template literal. The
599 * literal will be interpreted akin to `String.raw`.
601 export const base32String
= ($, ...$s
) =>
602 encodeBase32(bufferFromArgs($, $s
));
605 * Returns an ArrayBuffer generated from the provided base64 string.
607 * This function can also be used as a tag for a template literal. The
608 * literal will be interpreted akin to `String.raw`.
610 * ☡ This function throws if the provided string is not a valid base64
613 export const base64Binary
= ($, ...$s
) =>
614 decodeBase64(sourceFromArgs($, $s
));
617 * Returns a (big‐endian) base64 string created from the provided typed
618 * array, buffer, or (16‐bit) string.
620 * This function can also be used as a tag for a template literal. The
621 * literal will be interpreted akin to `String.raw`.
623 export const base64String
= ($, ...$s
) =>
624 encodeBase64(bufferFromArgs($, $s
));
627 * Returns an ArrayBuffer generated from the provided filename‐safe
630 * This function can also be used as a tag for a template literal. The
631 * literal will be interpreted akin to `String.raw`.
633 * ☡ This function throws if the provided string is not a valid
634 * filename‐safe base64 string.
636 export const filenameSafeBase64Binary
= ($, ...$s
) =>
637 decodeBase64(sourceFromArgs($, $s
), true);
640 * Returns a (big‐endian) filename‐safe base64 string created from the
641 * provided typed array, buffer, or (16‐bit) string.
643 * This function can also be used as a tag for a template literal. The
644 * literal will be interpreted akin to `String.raw`.
646 export const filenameSafeBase64String
= ($, ...$s
) =>
647 encodeBase64(bufferFromArgs($, $s
), true);
650 * Returns whether the provided value is a view on an underlying array
653 * ※ This function returns true for typed arrays and data views.
655 export const { isView
: isArrayBufferView
} = Buffer
;
657 /** Returns whether the provided value is an array buffer. */
658 export const isArrayBuffer
= ($) => {
660 // Try to see if the provided argument has array buffer internal
661 // slots and return true if so.
662 return call(getBufferByteLength
, $, []), true;
664 // The provided argument does not have array buffer internal slots.
671 * Returns whether the provided value is a base16 string.
673 * ※ This function returns false if the provided value is not a string
676 export const isBase16
= ($) => {
677 if (typeof $ !== "string") {
678 // The provided value is not a string.
681 // The provided value is a string.
682 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
683 return source
.length
% 2 !== 1 &&
684 call(reExec
, /[^0-9A-F]/iu, [source
]) === null;
689 * Returns whether the provided value is a base32 string.
691 * ※ This function returns false if the provided value is not a string
694 export const isBase32
= ($) => {
695 if (typeof $ !== "string") {
696 // The provided value is not a string.
699 // The provided value is a string.
700 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
701 const trimmed
= source
.length
% 8 === 0
702 ? stringReplace(source
, /(?:=|={3,4}|={6})$/u, "")
704 return trimmed
.length
% 8 !== 1 &&
705 call(reExec
, /[^2-7A-Z/]/iu
, [trimmed
]) === null;
710 * Returns whether the provided value is a Base64 string.
712 * ※ This function returns false if the provided value is not a string
715 export const isBase64
= ($) => {
716 if (typeof $ !== "string") {
717 // The provided value is not a string.
720 // The provided value is a string.
721 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
722 const trimmed
= source
.length
% 4 === 0
723 ? stringReplace(source
, /={1,2}$/u, "")
725 return trimmed
.length
% 4 !== 1 &&
726 call(reExec
, /[^0-9A-Za-z+\/]/u, [trimmed
]) === null;
730 /** Returns whether the provided value is a data view. */
731 export const isDataView
= ($) => {
733 // Try to see if the provided argument has data view internal slots
734 // and return true if so.
735 return call(getViewBuffer
, $, []), true;
737 // The provided argument does not have data view internal slots.
743 * Returns whether the provided value is a filename‐safe base64 string.
745 * ※ This function returns false if the provided value is not a string
748 export const isFilenameSafeBase64
= ($) => {
749 if (typeof $ !== "string") {
750 // The provided value is not a string.
753 // The provided value is a string.
754 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
755 const trimmed
= source
.length
% 4 === 0
756 ? stringReplace(source
, /={1,2}$/u, "")
758 return trimmed
.length
% 4 !== 1 &&
759 call(reExec
, /[^0-9A-Za-z_-]/u, [trimmed
]) === null;
763 /** Returns whether the provided value is a typed array. */
764 export const isTypedArray
= ($) => {
766 // Try to see if the provided argument has typed array internal
767 // slots and return true if so.
768 return call(getTypedArrayBuffer
, $, []), true;
770 // The provided argument does not have typed array internal slots.
776 * Returns whether the provided value is a W·R·M·G (Crockford) base32
777 * string. Check digits are not supported.
779 * ※ This function returns false if the provided value is not a string
782 export const isWRMGBase32
= ($) => {
783 if (typeof $ !== "string") {
784 // The provided value is not a string.
787 // The provided value is a string.
788 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
789 const trimmed
= stringReplace(source
, /-/gu
, "");
790 return trimmed
.length
% 8 !== 1 &&
791 call(reExec
, /[^0-9A-TV-Z]/iu, [trimmed
]) === null;
796 * Returns the array buffer associated with the provided object.
798 * ☡ This function throws if the provided object is not a data view or
801 export const toArrayBuffer
= ($) => {
802 if (isArrayBuffer($)) {
803 // The provided argument has array buffer internal slots.
806 // The provided argument does not have array buffer internal slots.
808 // The provided argument has typed array internal slots.
809 return call(getTypedArrayBuffer
, $, []);
814 // The provided argument has data view internal slots.
815 return call(getViewBuffer
, $, []);
819 throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
824 * Returns an ArrayBuffer generated from the provided W·R·M·G
825 * (Crockford) base32 string.
827 * This function can also be used as a tag for a template literal. The
828 * literal will be interpreted akin to `String.raw`.
830 * ☡ This function throws if the provided string is not a valid W·R·M·G
833 export const wrmgBase32Binary
= ($, ...$s
) =>
834 decodeBase32(sourceFromArgs($, $s
), true);
837 * Returns a (big‐endian) W·R·M·G (Crockford) base32 string created
838 * from the provided typed array, buffer, or (16‐bit) string.
840 * This function can also be used as a tag for a template literal. The
841 * literal will be interpreted akin to `String.raw`.
843 export const wrmgBase32String
= ($, ...$s
) =>
844 encodeBase32(bufferFromArgs($, $s
), true);