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 throw new RangeError(
139 `Piscēs: Base16 string has invalid length: ${source}.`,
142 const dataView
= new View(new Buffer(floor(length
/ 2)));
143 for (let index
= 0; index
< length
- 1;) {
144 call(viewSetUint8
, dataView
, [
146 (u4s
[index
] << 4) | u4s
[++index
, index
++],
149 return call(getViewBuffer
, dataView
, []);
154 * Returns the result of decoding the provided base32 string into an
157 * If the second argument is truthy, uses Crockford’s encoding rather
158 * than the RFC’s (see <https://www.crockford.com/base32.html>). This
159 * is more human‐friendly and tolerant. Check digits are not supported.
161 * ※ This function is not exposed.
163 const decodeBase32
= (source
, wrmg
) => {
166 ? stringReplace(source
, /-/gu
, "")
167 : source
.length
% 8 == 0
168 ? stringReplace(source
, /(?:=|={3,4}|={6})$/u, "")
171 const code
= getCodeUnit(ucsCharacter
, 0);
173 ? code
>= 0x30 && code
<= 0x39
175 : code
>= 0x41 && code
<= 0x48
179 : code
>= 0x4A && code
<= 0x4B
183 : code
>= 0x4D && code
<= 0x4E
187 : code
>= 0x50 && code
<= 0x54
190 : code
>= 0x56 && code
<= 0x5A
192 : code
>= 0x61 && code
<= 0x68
196 : code
>= 0x6A && code
<= 0x6B
200 : code
>= 0x6D && code
<= 0x6E
204 : code
>= 0x70 && code
<= 0x74
207 : code
>= 0x76 && code
<= 0x7A
210 : code
>= 0x41 && code
<= 0x5A
212 : code
>= 0x61 && code
<= 0x7A
213 ? code
- 97 // same result as above; case insensitive
214 : code
>= 0x32 && code
<= 0x37
215 ? code
- 24 // digits 2–7 map to 26–31
218 throw new RangeError(
219 `Piscēs: Invalid character in Base32: ${ucsCharacter}.`,
226 const { length
} = u5s
;
227 if (length
% 8 == 1) {
228 throw new RangeError(
229 `Piscēs: Base32 string has invalid length: ${source}.`,
232 const dataView
= new View(new Buffer(floor(length
* 5 / 8)));
233 for (let index
= 0; index
< length
- 1;) {
234 // The final index is not handled; if the string is not divisible
235 // by 8, some bits might be dropped. This matches the “forgiving
236 // decode” behaviour specified by WhatW·G for base64.
237 const dataIndex
= ceil(index
* 5 / 8);
238 const remainder
= index
% 8;
239 if (remainder
== 0) {
240 call(viewSetUint8
, dataView
, [
242 u5s
[index
] << 3 | u5s
[++index
] >> 2,
244 } else if (remainder
== 1) {
245 call(viewSetUint8
, dataView
, [
247 u5s
[index
] << 6 | u5s
[++index
] << 1 | u5s
[++index
] >> 4,
249 } else if (remainder
== 3) {
250 call(viewSetUint8
, dataView
, [
252 u5s
[index
] << 4 | u5s
[++index
] >> 1,
254 } else if (remainder
== 4) {
255 call(viewSetUint8
, dataView
, [
257 u5s
[index
] << 7 | u5s
[++index
] << 2 | u5s
[++index
] >> 3,
259 } else { // remainder == 6
260 call(viewSetUint8
, dataView
, [
262 u5s
[index
] << 5 | u5s
[++index
, index
++],
266 return call(getViewBuffer
, dataView
, []);
271 * Returns the result of decoding the provided base64 string into an
274 * ※ This function is not exposed.
276 const decodeBase64
= (source
, safe
= false) => {
278 source
.length
% 4 == 0
279 ? stringReplace(source
, /={1,2}$/u, "")
282 const code
= getCodeUnit(ucsCharacter
, 0);
283 const result
= code
>= 0x41 && code
<= 0x5A
285 : code
>= 0x61 && code
<= 0x7A
287 : code
>= 0x30 && code
<= 0x39
289 : code
== (safe
? 0x2D : 0x2B)
291 : code
== (safe
? 0x5F : 0x2F)
295 throw new RangeError(
296 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
303 const { length
} = u6s
;
304 if (length
% 4 == 1) {
305 throw new RangeError(
306 `Piscēs: Base64 string has invalid length: ${source}.`,
309 const dataView
= new View(new Buffer(floor(length
* 3 / 4)));
310 for (let index
= 0; index
< length
- 1;) {
311 // The final index is not handled; if the string is not divisible
312 // by 4, some bits might be dropped. This matches the “forgiving
313 // decode” behaviour specified by WhatW·G for base64.
314 const dataIndex
= ceil(index
* 3 / 4);
315 const remainder
= index
% 4;
316 if (remainder
== 0) {
317 call(viewSetUint8
, dataView
, [
319 u6s
[index
] << 2 | u6s
[++index
] >> 4,
321 } else if (remainder
== 1) {
322 call(viewSetUint8
, dataView
, [
324 u6s
[index
] << 4 | u6s
[++index
] >> 2,
326 } else { // remainder == 2
327 call(viewSetUint8
, dataView
, [
329 u6s
[index
] << 6 | u6s
[++index
, index
++],
333 return call(getViewBuffer
, dataView
, []);
338 * Returns the result of encoding the provided ArrayBuffer into a
341 * ※ This function is not exposed.
343 const encodeBase16
= (buffer
) => {
344 const dataView
= new View(buffer
);
345 const byteLength
= call(getBufferByteLength
, buffer
, []);
346 const minimumLengthOfResults
= byteLength
* 2;
347 const resultingCodeUnits
= fill(
349 binaryCodeUnitIterablePrototype
,
350 { length
: { value
: minimumLengthOfResults
} },
354 for (let index
= 0; index
< byteLength
;) {
355 const codeUnitIndex
= index
* 2;
356 const datum
= call(viewGetUint8
, dataView
, [index
++]);
357 const u4s
= [datum
>> 4, datum
& 0xF];
358 for (let u4i
= 0; u4i
< 2; ++u4i
) {
360 const result
= u4
< 10 ? u4
+ 48 : u4
< 16 ? u4
+ 55 : -1;
362 throw new RangeError(
363 `Piscēs: Unexpected Base16 value: ${u4}.`,
366 resultingCodeUnits
[codeUnitIndex
+ u4i
] = result
;
370 return stringFromCodeUnits(...resultingCodeUnits
);
374 * Returns the result of encoding the provided ArrayBuffer into a
377 * ※ This function is not exposed.
379 const encodeBase32
= (buffer
, wrmg
= false) => {
380 const dataView
= new View(buffer
);
381 const byteLength
= call(getBufferByteLength
, buffer
, []);
382 const minimumLengthOfResults
= ceil(byteLength
* 8 / 5);
383 const fillByte
= wrmg
? 0x2D : 0x3D;
384 const resultingCodeUnits
= fill(
386 binaryCodeUnitIterablePrototype
,
389 value
: minimumLengthOfResults
+
390 (8 - (minimumLengthOfResults
% 8)) % 8,
396 for (let index
= 0; index
< byteLength
;) {
397 const codeUnitIndex
= ceil(index
* 8 / 5);
398 const currentIndex
= codeUnitIndex
+ +(
399 0b01011 & 1 << index
% 5 &&
400 resultingCodeUnits
[codeUnitIndex
] != fillByte
401 ); // bytes 0, 1 & 3 handle two letters; this is for the second
402 const remainder
= currentIndex
% 8;
403 const currentByte
= call(viewGetUint8
, dataView
, [index
]);
405 0b01011010 & 1 << remainder
&& ++index
< byteLength
406 // digits 1, 3, 4 & 6 span multiple bytes
407 ? call(viewGetUint8
, dataView
, [index
])
409 const u5
= remainder
== 0
412 ? (currentByte
& 0b00000111) << 2 | nextByte
>> 6
414 ? (currentByte
& 0b00111111) >> 1
416 ? (currentByte
& 0b00000001) << 4 | nextByte
>> 4
418 ? (currentByte
& 0b00001111) << 1 | nextByte
>> 7
420 ? (currentByte
& 0b01111111) >> 2
422 ? (currentByte
& 0b00000011) << 3 | nextByte
>> 5
423 : (++index
, currentByte
& 0b00011111); // remainder == 7
425 ? u5
< 10 ? u5
+ 48 : u5
< 18
446 throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`);
448 resultingCodeUnits
[currentIndex
] = result
;
451 const answer
= stringFromCodeUnits(...resultingCodeUnits
);
452 return wrmg
? answer
.replace(/-+$/u, "") : answer
;
456 * Returns the result of encoding the provided ArrayBuffer into a
459 * ※ This function is not exposed.
461 const encodeBase64
= (buffer
, safe
= false) => {
462 const dataView
= new View(buffer
);
463 const byteLength
= call(getBufferByteLength
, buffer
, []);
464 const minimumLengthOfResults
= ceil(byteLength
* 4 / 3);
465 const resultingCodeUnits
= fill(
467 binaryCodeUnitIterablePrototype
,
470 value
: minimumLengthOfResults
+
471 (4 - (minimumLengthOfResults
% 4)) % 4,
477 for (let index
= 0; index
< byteLength
;) {
478 const codeUnitIndex
= ceil(index
* 4 / 3);
479 const currentIndex
= codeUnitIndex
+ +(
480 index
% 3 == 0 && resultingCodeUnits
[codeUnitIndex
] != 0x3D
481 ); // every third byte handles two letters; this is for the second
482 const remainder
= currentIndex
% 4;
483 const currentByte
= call(viewGetUint8
, dataView
, [index
]);
484 const nextByte
= remainder
% 3 && ++index
< byteLength
485 // digits 1 & 2 span multiple bytes
486 ? call(viewGetUint8
, dataView
, [index
])
488 const u6
= remainder
== 0
491 ? (currentByte
& 0b00000011) << 4 | nextByte
>> 4
493 ? (currentByte
& 0b00001111) << 2 | nextByte
>> 6
494 : (++index
, currentByte
& 0b00111111); // remainder == 3
495 const result
= u6
< 26
502 ? (safe
? 0x2D : 0x2B)
504 ? (safe
? 0x5F : 0x2F)
507 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
509 resultingCodeUnits
[currentIndex
] = result
;
512 return stringFromCodeUnits(...resultingCodeUnits
);
516 * Returns a source string generated from the arguments passed to a
519 * ※ This function is not exposed.
521 const sourceFromArgs
= ($, $s
) =>
523 typeof $ == "string" ? $ : hasOwnProperty($, "raw")
526 ...objectCreate(argumentIterablePrototype
, {
536 * Returns an ArrayBuffer generated from the provided base16 string.
538 * This function can also be used as a tag for a template literal. The
539 * literal will be interpreted akin to `String.raw`.
541 * ☡ This function throws if the provided string is not a valid base16
544 export const base16Binary
= ($, ...$s
) =>
545 decodeBase16(sourceFromArgs($, $s
));
548 * Returns a (big‐endian) base16 string created from the provided typed
549 * array, buffer, or (16‐bit) string.
551 * This function can also be used as a tag for a template literal. The
552 * literal will be interpreted akin to `String.raw`.
554 export const base16String
= ($, ...$s
) =>
555 encodeBase16(bufferFromArgs($, $s
));
558 * Returns an ArrayBuffer generated from the provided base32 string.
560 * This function can also be used as a tag for a template literal. The
561 * literal will be interpreted akin to `String.raw`.
563 * ☡ This function throws if the provided string is not a valid base32
566 export const base32Binary
= ($, ...$s
) =>
567 decodeBase32(sourceFromArgs($, $s
));
570 * Returns a (big‐endian) base32 string created from the provided typed
571 * array, buffer, or (16‐bit) string.
573 * This function can also be used as a tag for a template literal. The
574 * literal will be interpreted akin to `String.raw`.
576 export const base32String
= ($, ...$s
) =>
577 encodeBase32(bufferFromArgs($, $s
));
580 * Returns an ArrayBuffer generated from the provided base64 string.
582 * This function can also be used as a tag for a template literal. The
583 * literal will be interpreted akin to `String.raw`.
585 * ☡ This function throws if the provided string is not a valid base64
588 export const base64Binary
= ($, ...$s
) =>
589 decodeBase64(sourceFromArgs($, $s
));
592 * Returns a (big‐endian) base64 string created from the provided typed
593 * array, buffer, or (16‐bit) string.
595 * This function can also be used as a tag for a template literal. The
596 * literal will be interpreted akin to `String.raw`.
598 export const base64String
= ($, ...$s
) =>
599 encodeBase64(bufferFromArgs($, $s
));
602 * Returns an ArrayBuffer generated from the provided filename‐safe
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 * ☡ This function throws if the provided string is not a valid
609 * filename‐safe base64 string.
611 export const filenameSafeBase64Binary
= ($, ...$s
) =>
612 decodeBase64(sourceFromArgs($, $s
), true);
615 * Returns a (big‐endian) filename‐safe base64 string created from the
616 * provided typed array, buffer, or (16‐bit) string.
618 * This function can also be used as a tag for a template literal. The
619 * literal will be interpreted akin to `String.raw`.
621 export const filenameSafeBase64String
= ($, ...$s
) =>
622 encodeBase64(bufferFromArgs($, $s
), true);
625 * Returns whether the provided value is a base16 string.
627 * ※ This function returns false if the provided value is not a string
630 export const isBase16
= ($) => {
631 if (typeof $ !== "string") {
634 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
635 return source
.length
% 2 != 1 &&
636 call(reExec
, /[^0-9A-F]/iu, [source
]) == null;
641 * Returns whether the provided value is a base32 string.
643 * ※ This function returns false if the provided value is not a string
646 export const isBase32
= ($) => {
647 if (typeof $ !== "string") {
650 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
651 const trimmed
= source
.length
% 8 == 0
652 ? stringReplace(source
, /(?:=|={3,4}|={6})$/u, "")
654 return trimmed
.length
% 8 != 1 &&
655 call(reExec
, /[^2-7A-Z/]/iu
, [trimmed
]) == null;
660 * Returns whether the provided value is a Base64 string.
662 * ※ This function returns false if the provided value is not a string
665 export const isBase64
= ($) => {
666 if (typeof $ !== "string") {
669 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
670 const trimmed
= source
.length
% 4 == 0
671 ? stringReplace(source
, /={1,2}$/u, "")
673 return trimmed
.length
% 4 != 1 &&
674 call(reExec
, /[^0-9A-Za-z+\/]/u, [trimmed
]) == null;
679 * Returns whether the provided value is a filename‐safe base64 string.
681 * ※ This function returns false if the provided value is not a string
684 export const isFilenameSafeBase64
= ($) => {
685 if (typeof $ !== "string") {
688 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
689 const trimmed
= source
.length
% 4 == 0
690 ? stringReplace(source
, /={1,2}$/u, "")
692 return trimmed
.length
% 4 != 1 &&
693 call(reExec
, /[^0-9A-Za-z_-]/u, [trimmed
]) == null;
698 * Returns whether the provided value is a W·R·M·G (Crockford) base32
699 * string. Check digits are not supported.
701 * ※ This function returns false if the provided value is not a string
704 export const isWRMGBase32
= ($) => {
705 if (typeof $ !== "string") {
708 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
709 const trimmed
= stringReplace(source
, /-/gu
, "");
710 return trimmed
.length
% 8 != 1 &&
711 call(reExec
, /[^0-9A-TV-Z]/iu, [trimmed
]) == null;
716 * Returns an ArrayBuffer generated from the provided W·R·M·G
717 * (Crockford) base32 string.
719 * This function can also be used as a tag for a template literal. The
720 * literal will be interpreted akin to `String.raw`.
722 * ☡ This function throws if the provided string is not a valid W·R·M·G
725 export const wrmgBase32Binary
= ($, ...$s
) =>
726 decodeBase32(sourceFromArgs($, $s
), true);
729 * Returns a (big‐endian) W·R·M·G (Crockford) base32 string created
730 * from the provided typed array, buffer, or (16‐bit) string.
732 * This function can also be used as a tag for a template literal. The
733 * literal will be interpreted akin to `String.raw`.
735 export const wrmgBase32String
= ($, ...$s
) =>
736 encodeBase32(bufferFromArgs($, $s
), true);