: `${$}`,
);
+/**
+ * Returns the result of decoding the provided base16 string into an
+ * ArrayBuffer.
+ *
+ * ※ This function is not exposed.
+ */
+const decodeBase16 = (source) => {
+ const u4s = map(
+ source,
+ (ucsCharacter) => {
+ const code = getCodeUnit(ucsCharacter, 0);
+ const result = code >= 0x30 && code <= 0x39
+ ? code - 48
+ : code >= 0x41 && code <= 0x46
+ ? code - 55
+ : code >= 0x61 && code <= 0x66
+ ? code - 87
+ : -1;
+ if (result < 0) {
+ throw new RangeError(
+ `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
+ );
+ } else {
+ return result;
+ }
+ },
+ );
+ const { length } = u4s;
+ if (length % 2 == 1) {
+ throw new RangeError(
+ `Piscēs: Base16 string has invalid length: ${source}.`,
+ );
+ } else {
+ const dataView = new View(new Buffer(floor(length / 2)));
+ for (let index = 0; index < length - 1;) {
+ call(viewSetUint8, dataView, [
+ floor(index / 2),
+ (u4s[index] << 4) | u4s[++index, index++],
+ ]);
+ }
+ return call(getViewBuffer, dataView, []);
+ }
+};
+
/**
* Returns the result of decoding the provided base64 string into an
* ArrayBuffer.
}
};
+/**
+ * Returns the result of encoding the provided ArrayBuffer into a
+ * base16 string.
+ *
+ * ※ This function is not exposed.
+ */
+const encodeBase16 = (buffer) => {
+ const dataView = new View(buffer);
+ const byteLength = call(getBufferByteLength, buffer, []);
+ const minimumLengthOfResults = byteLength * 2;
+ const resultingCodeUnits = fill(
+ objectCreate(
+ binaryCodeUnitIterablePrototype,
+ { length: { value: minimumLengthOfResults } },
+ ),
+ 0x3D,
+ );
+ for (let index = 0; index < byteLength;) {
+ const codeUnitIndex = index * 2;
+ const datum = call(viewGetUint8, dataView, [index++]);
+ const u4s = [datum >> 4, datum & 0xF];
+ for (let u4i = 0; u4i < 2; ++u4i) {
+ const u4 = u4s[u4i];
+ const result = u4 < 10 ? u4 + 48 : u4 < 16 ? u4 + 55 : -1;
+ if (result < 0) {
+ throw new RangeError(
+ `Piscēs: Unexpected Base16 value: ${u4}.`,
+ );
+ } else {
+ resultingCodeUnits[codeUnitIndex + u4i] = result;
+ }
+ }
+ }
+ return stringFromCodeUnits(...resultingCodeUnits);
+};
+
/**
* Returns the result of encoding the provided ArrayBuffer into a
* base64 string.
"",
);
+/**
+ * Returns an ArrayBuffer generated from the provided base16 string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ *
+ * ☡ This function throws if the provided string is not a valid base16
+ * string.
+ */
+export const base16Binary = ($, ...$s) =>
+ decodeBase16(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base16 string created from the provided typed
+ * array, buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base16String = ($, ...$s) =>
+ encodeBase16(bufferFromArgs($, $s));
+
/**
* Returns an ArrayBuffer generated from the provided base64 string.
*
export const filenameSafeBase64String = ($, ...$s) =>
encodeBase64(bufferFromArgs($, $s), true);
+/**
+ * Returns whether the provided value is a base16 string.
+ *
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
+ */
+export const isBase16 = ($) => {
+ if (typeof $ !== "string") {
+ return false;
+ } else {
+ const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+ return source.length % 2 != 1 &&
+ call(reExec, /[^0-9A-F]/iu, [source]) == null;
+ }
+};
+
/**
* Returns whether the provided value is a Base64 string.
*
it,
} from "./dev-deps.js";
import {
+ base16Binary,
+ base16String,
base64Binary,
base64String,
filenameSafeBase64Binary,
filenameSafeBase64String,
+ isBase16,
isBase64,
isFilenameSafeBase64,
} from "./binary.js";
// These tests assume a LITTLE‐ENDIAN environment.
const data = new Map([
["", {
+ base16: "",
base64: "",
}],
["base64", {
+ base16: "006200610073006500360034",
base64: "AGIAYQBzAGUANgA0",
}],
[new Uint16Array([62535]), {
+ base16: "47F4",
base64: "R/Q=",
}],
[new Uint8ClampedArray([75, 73, 66, 73]).buffer, {
+ base16: "4B494249",
base64: "S0lCSQ==",
}],
[new DataView(new Uint8Array([98, 97, 115, 101, 54, 52]).buffer), {
+ base16: "626173653634",
base64: "YmFzZTY0",
}],
// The following three examples are from RFC 3548.
[new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]), {
+ base16: "14FB9C03D97E",
base64: "FPucA9l+",
}],
[new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9]), {
+ base16: "14FB9C03D9",
base64: "FPucA9k=",
}],
[new Uint8Array([0x14, 0xFB, 0x9C, 0x03]), {
+ base16: "14FB9C03",
base64: "FPucAw==",
}],
// The following examples are from the Ruby base32 gem.
[new Uint8Array([0x28]), {
+ base16: "28",
base64: "KA==",
}],
[new Uint8Array([0xD6]), {
+ base16: "D6",
base64: "1g==",
}],
[new Uint16Array([0xF8D6]), {
+ base16: "D6F8",
base64: "1vg=",
}],
[new Uint8Array([0xD6, 0xF8, 0x00]), {
+ base16: "D6F800",
base64: "1vgA",
}],
[new Uint8Array([0xD6, 0xF8, 0x10]), {
+ base16: "D6F810",
base64: "1vgQ",
}],
[new Uint32Array([0x0C11F8D6]), {
+ base16: "D6F8110C",
base64: "1vgRDA==",
}],
[new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]), {
+ base16: "D6F8110C80",
base64: "1vgRDIA=",
}],
[new Uint16Array([0xF8D6, 0x0C11, 0x3085]), {
+ base16: "D6F8110C8530",
base64: "1vgRDIUw",
}],
]);
+describe("base16Binary", () => {
+ it("[[Call]] returns the correct data", () => {
+ assertEquals(
+ new Uint8Array(base16Binary("")),
+ new Uint8Array([]),
+ "<empty>",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("006200610073006500360034")),
+ Uint8Array.from(
+ "\u{0}b\u{0}a\u{0}s\u{0}e\u{0}6\u{0}4",
+ ($) => $.charCodeAt(0),
+ ),
+ "006200610073006500360034",
+ );
+ assertEquals(
+ new Uint16Array(base16Binary("47F4")),
+ new Uint16Array([62535]),
+ "47F4",
+ );
+ assertEquals(
+ new Uint8ClampedArray(base16Binary("4B494249")),
+ new Uint8ClampedArray([75, 73, 66, 73]),
+ "4B494249",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("626173653634")),
+ new Uint8Array([98, 97, 115, 101, 54, 52]),
+ "626173653634",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("14FB9C03D97E")),
+ new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]),
+ "14FB9C03D97E",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("14FB9C03D9")),
+ new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9]),
+ "14FB9C03D9",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("14FB9C03")),
+ new Uint8Array([0x14, 0xFB, 0x9C, 0x03]),
+ "14FB9C03",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("28")),
+ new Uint8Array([0x28]),
+ "28",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("D6")),
+ new Uint8Array([0xD6]),
+ "D6",
+ );
+ assertEquals(
+ new Uint16Array(base16Binary("D6F8")),
+ new Uint16Array([0xF8D6]),
+ "D6F8",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("D6F800")),
+ new Uint8Array([0xD6, 0xF8, 0x00]),
+ "D6F800",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("D6F810")),
+ new Uint8Array([0xD6, 0xF8, 0x10]),
+ "D6F810",
+ );
+ assertEquals(
+ new Uint32Array(base16Binary("D6F8110C")),
+ new Uint32Array([0x0C11F8D6]),
+ "D6F8110C",
+ );
+ assertEquals(
+ new Uint8Array(base16Binary("D6F8110C80")),
+ new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+ "D6F8110C80",
+ );
+ assertEquals(
+ new Uint16Array(base16Binary("D6F8110C8530")),
+ new Uint16Array([0xF8D6, 0x0C11, 0x3085]),
+ "D6F8110C8530",
+ );
+ });
+
+ it("[[Call]] is case‐insensitive", () => {
+ assertEquals(
+ new Uint8Array(base16Binary("d6f8110C80")),
+ new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+ "d6f8110C80",
+ );
+ });
+
+ it("[[Call]] throws when provided with an invalid character", () => {
+ assertThrows(() => base16Binary("ABCG"));
+ });
+
+ it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 2", () => {
+ assertThrows(() => base16Binary("A"));
+ assertThrows(() => base16Binary("ABC"));
+ });
+});
+
+describe("base16String", () => {
+ it("[[Call]] returns the correct string", () => {
+ for (const [source, { base16 }] of data) {
+ assertStrictEquals(base16String(source), base16);
+ }
+ });
+});
+
describe("base64Binary", () => {
it("[[Call]] returns the correct data", () => {
assertEquals(
});
});
+describe("isBase16", () => {
+ it("[[Call]] returns true for base64 strings", () => {
+ for (const { base16 } of data.values()) {
+ assert(isBase16(base16));
+ assert(isBase16(base16.toLowerCase()));
+ }
+ });
+
+ it("[[Call]] returns false for others", () => {
+ [
+ undefined,
+ null,
+ true,
+ Symbol(),
+ 27,
+ 98n,
+ {},
+ [],
+ () => {},
+ new Proxy({}, {}),
+ "abc_",
+ "a",
+ "abc",
+ "abcg",
+ ].forEach((value) => assert(!isBase16(value)));
+ });
+});
+
describe("isBase64", () => {
it("[[Call]] returns true for base64 strings", () => {
for (const { base64 } of data.values()) {