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";
21 const Buffer
= ArrayBuffer
;
22 const View
= DataView
;
23 const TypedArray
= Object
.getPrototypeOf(Uint8Array
);
24 const { prototype: arrayPrototype
} = Array
;
25 const { prototype: bufferPrototype
} = Buffer
;
26 const { iterator
: iteratorSymbol
} = Symbol
;
27 const { prototype: rePrototype
} = RegExp
;
28 const { prototype: typedArrayPrototype
} = TypedArray
;
29 const { prototype: viewPrototype
} = View
;
31 const { [iteratorSymbol
]: arrayIterator
} = arrayPrototype
;
33 next
: arrayIteratorNext
,
34 } = Object
.getPrototypeOf([][iteratorSymbol
]());
35 const argumentIterablePrototype
= {
40 call(arrayIterator
, this.args
, []),
46 const binaryCodeUnitIterablePrototype
= {
51 call(arrayIterator
, this, []),
58 const getBufferByteLength
=
59 Object
.getOwnPropertyDescriptor(bufferPrototype
, "byteLength").get;
60 const getTypedArrayBuffer
=
61 Object
.getOwnPropertyDescriptor(typedArrayPrototype
, "buffer").get;
63 Object
.getOwnPropertyDescriptor(viewPrototype
, "buffer").get;
64 const { exec
: reExec
} = rePrototype
;
66 getUint8
: viewGetUint8
,
67 setUint8
: viewSetUint8
,
68 setUint16
: viewSetUint16
,
72 * Returns an ArrayBuffer for encoding generated from the provided
75 const bufferFromArgs
= ($, $s
) =>
79 ? call(getViewBuffer
, $, [])
80 : $ instanceof TypedArray
81 ? call(getTypedArrayBuffer
, $, [])
87 (result
, ucsCharacter
, index
) => (
88 call(viewSetUint16
, result
, [
90 getCodeUnit(ucsCharacter
, 0),
93 new View(new Buffer(string
.length
* 2)),
99 : hasOwnProperty($, "raw")
102 ...objectCreate(argumentIterablePrototype
, {
110 * Returns the result of decoding the provided base16 string into an
113 * ※ This function is not exposed.
115 const decodeBase16
= (source
) => {
119 const code
= getCodeUnit(ucsCharacter
, 0);
120 const result
= code
>= 0x30 && code
<= 0x39
122 : code
>= 0x41 && code
<= 0x46
124 : code
>= 0x61 && code
<= 0x66
128 throw new RangeError(
129 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
136 const { length
} = u4s
;
137 if (length
% 2 == 1) {
138 // The length is such that an entire letter would be dropped during
139 // a forgiving decode.
140 throw new RangeError(
141 `Piscēs: Base16 string has invalid length: ${source}.`,
144 // Every letter contributes at least some bits to the result.
145 const dataView
= new View(new Buffer(floor(length
/ 2)));
146 for (let index
= 0; index
< length
- 1;) {
147 call(viewSetUint8
, dataView
, [
149 (u4s
[index
] << 4) | u4s
[++index
, index
++],
152 return call(getViewBuffer
, dataView
, []);
157 * Returns the result of decoding the provided base32 string into an
160 * If the second argument is truthy, uses Crockford’s encoding rather
161 * than the RFC’s (see <https://www.crockford.com/base32.html>). This
162 * is more human‐friendly and tolerant. Check digits are not supported.
164 * ※ This function is not exposed.
166 const decodeBase32
= (source
, wrmg
) => {
169 ? stringReplace(source
, /-/gu
, "")
170 : source
.length
% 8 == 0
171 ? stringReplace(source
, /(?:=|={3,4}|={6})$/u, "")
174 const code
= getCodeUnit(ucsCharacter
, 0);
176 ? code
>= 0x30 && code
<= 0x39
178 : code
>= 0x41 && code
<= 0x48
182 : code
>= 0x4A && code
<= 0x4B
186 : code
>= 0x4D && code
<= 0x4E
190 : code
>= 0x50 && code
<= 0x54
193 : code
>= 0x56 && code
<= 0x5A
195 : code
>= 0x61 && code
<= 0x68
199 : code
>= 0x6A && code
<= 0x6B
203 : code
>= 0x6D && code
<= 0x6E
207 : code
>= 0x70 && code
<= 0x74
210 : code
>= 0x76 && code
<= 0x7A
213 : code
>= 0x41 && code
<= 0x5A
215 : code
>= 0x61 && code
<= 0x7A
216 ? code
- 97 // same result as above; case insensitive
217 : code
>= 0x32 && code
<= 0x37
218 ? code
- 24 // digits 2–7 map to 26–31
221 throw new RangeError(
222 `Piscēs: Invalid character in Base32: ${ucsCharacter}.`,
229 const { length
} = u5s
;
230 const lengthMod8
= length
% 8;
231 if (lengthMod8
== 1 || lengthMod8
== 3 || lengthMod8
== 6) {
232 // The length is such that an entire letter would be dropped during
233 // a forgiving decode.
234 throw new RangeError(
235 `Piscēs: Base32 string has invalid length: ${source}.`,
238 // Every letter contributes at least some bits to the result.
239 const dataView
= new View(new Buffer(floor(length
* 5 / 8)));
240 for (let index
= 0; index
< length
- 1;) {
241 // The final index is not handled; if the string is not divisible
242 // by 8, some bits might be dropped. This matches the “forgiving
243 // decode” behaviour specified by WhatW·G for base64.
244 const dataIndex
= ceil(index
* 5 / 8);
245 const remainder
= index
% 8;
246 if (remainder
== 0) {
247 call(viewSetUint8
, dataView
, [
249 u5s
[index
] << 3 | u5s
[++index
] >> 2,
251 } else if (remainder
== 1) {
252 call(viewSetUint8
, dataView
, [
254 u5s
[index
] << 6 | u5s
[++index
] << 1 | u5s
[++index
] >> 4,
256 } else if (remainder
== 3) {
257 call(viewSetUint8
, dataView
, [
259 u5s
[index
] << 4 | u5s
[++index
] >> 1,
261 } else if (remainder
== 4) {
262 call(viewSetUint8
, dataView
, [
264 u5s
[index
] << 7 | u5s
[++index
] << 2 | u5s
[++index
] >> 3,
266 } else { // remainder == 6
267 call(viewSetUint8
, dataView
, [
269 u5s
[index
] << 5 | u5s
[++index
, index
++],
273 return call(getViewBuffer
, dataView
, []);
278 * Returns the result of decoding the provided base64 string into an
281 * ※ This function is not exposed.
283 const decodeBase64
= (source
, safe
= false) => {
285 source
.length
% 4 == 0
286 ? stringReplace(source
, /={1,2}$/u, "")
289 const code
= getCodeUnit(ucsCharacter
, 0);
290 const result
= code
>= 0x41 && code
<= 0x5A
292 : code
>= 0x61 && code
<= 0x7A
294 : code
>= 0x30 && code
<= 0x39
296 : code
== (safe
? 0x2D : 0x2B)
298 : code
== (safe
? 0x5F : 0x2F)
302 throw new RangeError(
303 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
310 const { length
} = u6s
;
311 if (length
% 4 == 1) {
312 // The length is such that an entire letter would be dropped during
313 // a forgiving decode.
314 throw new RangeError(
315 `Piscēs: Base64 string has invalid length: ${source}.`,
318 // Every letter contributes at least some bits to the result.
319 const dataView
= new View(new Buffer(floor(length
* 3 / 4)));
320 for (let index
= 0; index
< length
- 1;) {
321 // The final index is not handled; if the string is not divisible
322 // by 4, some bits might be dropped. This matches the “forgiving
323 // decode” behaviour specified by WhatW·G for base64.
324 const dataIndex
= ceil(index
* 3 / 4);
325 const remainder
= index
% 4;
326 if (remainder
== 0) {
327 call(viewSetUint8
, dataView
, [
329 u6s
[index
] << 2 | u6s
[++index
] >> 4,
331 } else if (remainder
== 1) {
332 call(viewSetUint8
, dataView
, [
334 u6s
[index
] << 4 | u6s
[++index
] >> 2,
336 } else { // remainder == 2
337 call(viewSetUint8
, dataView
, [
339 u6s
[index
] << 6 | u6s
[++index
, index
++],
343 return call(getViewBuffer
, dataView
, []);
348 * Returns the result of encoding the provided ArrayBuffer into a
351 * ※ This function is not exposed.
353 const encodeBase16
= (buffer
) => {
354 const dataView
= new View(buffer
);
355 const byteLength
= call(getBufferByteLength
, buffer
, []);
356 const minimumLengthOfResults
= byteLength
* 2;
357 const resultingCodeUnits
= fill(
359 binaryCodeUnitIterablePrototype
,
360 { length
: { value
: minimumLengthOfResults
} },
364 for (let index
= 0; index
< byteLength
;) {
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
) {
370 const result
= u4
< 10 ? u4
+ 48 : u4
< 16 ? u4
+ 55 : -1;
372 throw new RangeError(
373 `Piscēs: Unexpected Base16 value: ${u4}.`,
376 resultingCodeUnits
[codeUnitIndex
+ u4i
] = result
;
380 return stringFromCodeUnits(...resultingCodeUnits
);
384 * Returns the result of encoding the provided ArrayBuffer into a
387 * ※ This function is not exposed.
389 const encodeBase32
= (buffer
, wrmg
= false) => {
390 const dataView
= new View(buffer
);
391 const byteLength
= call(getBufferByteLength
, buffer
, []);
392 const minimumLengthOfResults
= ceil(byteLength
* 8 / 5);
393 const fillByte
= wrmg
? 0x2D : 0x3D;
394 const resultingCodeUnits
= fill(
396 binaryCodeUnitIterablePrototype
,
399 value
: minimumLengthOfResults
+
400 (8 - (minimumLengthOfResults
% 8)) % 8,
406 for (let index
= 0; index
< byteLength
;) {
407 const codeUnitIndex
= ceil(index
* 8 / 5);
408 const currentIndex
= codeUnitIndex
+ +(
409 0b01011 & 1 << index
% 5 &&
410 resultingCodeUnits
[codeUnitIndex
] != fillByte
411 ); // bytes 0, 1 & 3 handle two letters; this is for the second
412 const remainder
= currentIndex
% 8;
413 const currentByte
= call(viewGetUint8
, dataView
, [index
]);
415 0b01011010 & 1 << remainder
&& ++index
< byteLength
416 // digits 1, 3, 4 & 6 span multiple bytes
417 ? call(viewGetUint8
, dataView
, [index
])
419 const u5
= remainder
== 0
422 ? (currentByte
& 0b00000111) << 2 | nextByte
>> 6
424 ? (currentByte
& 0b00111111) >> 1
426 ? (currentByte
& 0b00000001) << 4 | nextByte
>> 4
428 ? (currentByte
& 0b00001111) << 1 | nextByte
>> 7
430 ? (currentByte
& 0b01111111) >> 2
432 ? (currentByte
& 0b00000011) << 3 | nextByte
>> 5
433 : (++index
, currentByte
& 0b00011111); // remainder == 7
435 ? u5
< 10 ? u5
+ 48 : u5
< 18
456 throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`);
458 resultingCodeUnits
[currentIndex
] = result
;
461 const answer
= stringFromCodeUnits(...resultingCodeUnits
);
462 return wrmg
? answer
.replace(/-+$/u, "") : answer
;
466 * Returns the result of encoding the provided ArrayBuffer into a
469 * ※ This function is not exposed.
471 const encodeBase64
= (buffer
, safe
= false) => {
472 const dataView
= new View(buffer
);
473 const byteLength
= call(getBufferByteLength
, buffer
, []);
474 const minimumLengthOfResults
= ceil(byteLength
* 4 / 3);
475 const resultingCodeUnits
= fill(
477 binaryCodeUnitIterablePrototype
,
480 value
: minimumLengthOfResults
+
481 (4 - (minimumLengthOfResults
% 4)) % 4,
487 for (let index
= 0; index
< byteLength
;) {
488 const codeUnitIndex
= ceil(index
* 4 / 3);
489 const currentIndex
= codeUnitIndex
+ +(
490 index
% 3 == 0 && resultingCodeUnits
[codeUnitIndex
] != 0x3D
491 ); // every third byte handles two letters; this is for the second
492 const remainder
= currentIndex
% 4;
493 const currentByte
= call(viewGetUint8
, dataView
, [index
]);
494 const nextByte
= remainder
% 3 && ++index
< byteLength
495 // digits 1 & 2 span multiple bytes
496 ? call(viewGetUint8
, dataView
, [index
])
498 const u6
= remainder
== 0
501 ? (currentByte
& 0b00000011) << 4 | nextByte
>> 4
503 ? (currentByte
& 0b00001111) << 2 | nextByte
>> 6
504 : (++index
, currentByte
& 0b00111111); // remainder == 3
505 const result
= u6
< 26
512 ? (safe
? 0x2D : 0x2B)
514 ? (safe
? 0x5F : 0x2F)
517 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
519 resultingCodeUnits
[currentIndex
] = result
;
522 return stringFromCodeUnits(...resultingCodeUnits
);
526 * Returns a source string generated from the arguments passed to a
529 * ※ This function is not exposed.
531 const sourceFromArgs
= ($, $s
) =>
533 typeof $ == "string" ? $ : hasOwnProperty($, "raw")
536 ...objectCreate(argumentIterablePrototype
, {
546 * Returns an ArrayBuffer generated from the provided base16 string.
548 * This function can also be used as a tag for a template literal. The
549 * literal will be interpreted akin to `String.raw`.
551 * ☡ This function throws if the provided string is not a valid base16
554 export const base16Binary
= ($, ...$s
) =>
555 decodeBase16(sourceFromArgs($, $s
));
558 * Returns a (big‐endian) base16 string created from the provided typed
559 * array, buffer, or (16‐bit) string.
561 * This function can also be used as a tag for a template literal. The
562 * literal will be interpreted akin to `String.raw`.
564 export const base16String
= ($, ...$s
) =>
565 encodeBase16(bufferFromArgs($, $s
));
568 * Returns an ArrayBuffer generated from the provided base32 string.
570 * This function can also be used as a tag for a template literal. The
571 * literal will be interpreted akin to `String.raw`.
573 * ☡ This function throws if the provided string is not a valid base32
576 export const base32Binary
= ($, ...$s
) =>
577 decodeBase32(sourceFromArgs($, $s
));
580 * Returns a (big‐endian) base32 string created from the provided typed
581 * array, buffer, or (16‐bit) string.
583 * This function can also be used as a tag for a template literal. The
584 * literal will be interpreted akin to `String.raw`.
586 export const base32String
= ($, ...$s
) =>
587 encodeBase32(bufferFromArgs($, $s
));
590 * Returns an ArrayBuffer generated from the provided base64 string.
592 * This function can also be used as a tag for a template literal. The
593 * literal will be interpreted akin to `String.raw`.
595 * ☡ This function throws if the provided string is not a valid base64
598 export const base64Binary
= ($, ...$s
) =>
599 decodeBase64(sourceFromArgs($, $s
));
602 * Returns a (big‐endian) base64 string created from the provided typed
603 * array, buffer, or (16‐bit) string.
605 * This function can also be used as a tag for a template literal. The
606 * literal will be interpreted akin to `String.raw`.
608 export const base64String
= ($, ...$s
) =>
609 encodeBase64(bufferFromArgs($, $s
));
612 * Returns an ArrayBuffer generated from the provided filename‐safe
615 * This function can also be used as a tag for a template literal. The
616 * literal will be interpreted akin to `String.raw`.
618 * ☡ This function throws if the provided string is not a valid
619 * filename‐safe base64 string.
621 export const filenameSafeBase64Binary
= ($, ...$s
) =>
622 decodeBase64(sourceFromArgs($, $s
), true);
625 * Returns a (big‐endian) filename‐safe base64 string created from the
626 * provided typed array, buffer, or (16‐bit) string.
628 * This function can also be used as a tag for a template literal. The
629 * literal will be interpreted akin to `String.raw`.
631 export const filenameSafeBase64String
= ($, ...$s
) =>
632 encodeBase64(bufferFromArgs($, $s
), true);
635 * Returns whether the provided value is a base16 string.
637 * ※ This function returns false if the provided value is not a string
640 export const isBase16
= ($) => {
641 if (typeof $ !== "string") {
644 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
645 return source
.length
% 2 != 1 &&
646 call(reExec
, /[^0-9A-F]/iu, [source
]) == null;
651 * Returns whether the provided value is a base32 string.
653 * ※ This function returns false if the provided value is not a string
656 export const isBase32
= ($) => {
657 if (typeof $ !== "string") {
660 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
661 const trimmed
= source
.length
% 8 == 0
662 ? stringReplace(source
, /(?:=|={3,4}|={6})$/u, "")
664 return trimmed
.length
% 8 != 1 &&
665 call(reExec
, /[^2-7A-Z/]/iu
, [trimmed
]) == null;
670 * Returns whether the provided value is a Base64 string.
672 * ※ This function returns false if the provided value is not a string
675 export const isBase64
= ($) => {
676 if (typeof $ !== "string") {
679 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
680 const trimmed
= source
.length
% 4 == 0
681 ? stringReplace(source
, /={1,2}$/u, "")
683 return trimmed
.length
% 4 != 1 &&
684 call(reExec
, /[^0-9A-Za-z+\/]/u, [trimmed
]) == null;
689 * Returns whether the provided value is a filename‐safe base64 string.
691 * ※ This function returns false if the provided value is not a string
694 export const isFilenameSafeBase64
= ($) => {
695 if (typeof $ !== "string") {
698 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
699 const trimmed
= source
.length
% 4 == 0
700 ? stringReplace(source
, /={1,2}$/u, "")
702 return trimmed
.length
% 4 != 1 &&
703 call(reExec
, /[^0-9A-Za-z_-]/u, [trimmed
]) == null;
708 * Returns whether the provided value is a W·R·M·G (Crockford) base32
709 * string. Check digits are not supported.
711 * ※ This function returns false if the provided value is not a string
714 export const isWRMGBase32
= ($) => {
715 if (typeof $ !== "string") {
718 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
719 const trimmed
= stringReplace(source
, /-/gu
, "");
720 return trimmed
.length
% 8 != 1 &&
721 call(reExec
, /[^0-9A-TV-Z]/iu, [trimmed
]) == null;
726 * Returns an ArrayBuffer generated from the provided W·R·M·G
727 * (Crockford) base32 string.
729 * This function can also be used as a tag for a template literal. The
730 * literal will be interpreted akin to `String.raw`.
732 * ☡ This function throws if the provided string is not a valid W·R·M·G
735 export const wrmgBase32Binary
= ($, ...$s
) =>
736 decodeBase32(sourceFromArgs($, $s
), true);
739 * Returns a (big‐endian) W·R·M·G (Crockford) base32 string created
740 * from the provided typed array, buffer, or (16‐bit) string.
742 * This function can also be used as a tag for a template literal. The
743 * literal will be interpreted akin to `String.raw`.
745 export const wrmgBase32String
= ($, ...$s
) =>
746 encodeBase32(bufferFromArgs($, $s
), true);