]> Lady’s Gitweb - Pisces/commitdiff
Add base32 support to binary.js
authorLady <redacted>
Sat, 20 May 2023 01:05:19 +0000 (18:05 -0700)
committerLady <redacted>
Sat, 20 May 2023 01:05:19 +0000 (18:05 -0700)
Includes both the RFC base32 definition and the “W·R·M·G” definition by
Douglas Crockford.

binary.js
binary.test.js

index 25fd05586ee86a497b2efb662e433ec9055f0b31..5c78d3916cc1dabd25d528d3a72bba443091c8bb 100644 (file)
--- a/binary.js
+++ b/binary.js
@@ -150,6 +150,123 @@ const decodeBase16 = (source) => {
   }
 };
 
+/**
+ * Returns the result of decoding the provided base32 string into an
+ * ArrayBuffer.
+ *
+ * If the second argument is truthy, uses Crockford’s encoding rather
+ * than the RFC’s (see <https://www.crockford.com/base32.html>). This
+ * is more human‐friendly and tolerant. Check digits are not supported.
+ *
+ * ※ This function is not exposed.
+ */
+const decodeBase32 = (source, wrmg) => {
+  const u5s = map(
+    wrmg
+      ? stringReplace(source, /-/gu, "")
+      : source.length % 8 == 0
+      ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "")
+      : source,
+    (ucsCharacter) => {
+      const code = getCodeUnit(ucsCharacter, 0);
+      const result = wrmg
+        ? code >= 0x30 && code <= 0x39
+          ? code - 48
+          : code >= 0x41 && code <= 0x48
+          ? code - 55
+          : code == 0x49
+          ? 1 // I
+          : code >= 0x4A && code <= 0x4B
+          ? code - 56
+          : code == 0x4C
+          ? 1 // L
+          : code >= 0x4D && code <= 0x4E
+          ? code - 57
+          : code == 0x4F
+          ? 0 // O
+          : code >= 0x50 && code <= 0x54
+          ? code - 58
+          // U is skipped
+          : code >= 0x56 && code <= 0x5A
+          ? code - 59
+          : code >= 0x61 && code <= 0x68
+          ? code - 87
+          : code == 0x69
+          ? 1 // i
+          : code >= 0x6A && code <= 0x6B
+          ? code - 88
+          : code == 0x6C
+          ? 1 // l
+          : code >= 0x6D && code <= 0x6E
+          ? code - 89
+          : code == 0x6F
+          ? 0 // o
+          : code >= 0x70 && code <= 0x74
+          ? code - 90
+          // u is skipped
+          : code >= 0x76 && code <= 0x7A
+          ? code - 91
+          : -1
+        : code >= 0x41 && code <= 0x5A
+        ? code - 65
+        : code >= 0x61 && code <= 0x7A
+        ? code - 97 // same result as above; case insensitive
+        : code >= 0x32 && code <= 0x37
+        ? code - 24 // digits 2–7 map to 26–31
+        : -1;
+      if (result < 0) {
+        throw new RangeError(
+          `Piscēs: Invalid character in Base32: ${ucsCharacter}.`,
+        );
+      } else {
+        return result;
+      }
+    },
+  );
+  const { length } = u5s;
+  if (length % 8 == 1) {
+    throw new RangeError(
+      `Piscēs: Base32 string has invalid length: ${source}.`,
+    );
+  } else {
+    const dataView = new View(new Buffer(floor(length * 5 / 8)));
+    for (let index = 0; index < length - 1;) {
+      // The final index is not handled; if the string is not divisible
+      // by 8, some bits might be dropped. This matches the “forgiving
+      // decode” behaviour specified by WhatW·G for base64.
+      const dataIndex = ceil(index * 5 / 8);
+      const remainder = index % 8;
+      if (remainder == 0) {
+        call(viewSetUint8, dataView, [
+          dataIndex,
+          u5s[index] << 3 | u5s[++index] >> 2,
+        ]);
+      } else if (remainder == 1) {
+        call(viewSetUint8, dataView, [
+          dataIndex,
+          u5s[index] << 6 | u5s[++index] << 1 | u5s[++index] >> 4,
+        ]);
+      } else if (remainder == 3) {
+        call(viewSetUint8, dataView, [
+          dataIndex,
+          u5s[index] << 4 | u5s[++index] >> 1,
+        ]);
+      } else if (remainder == 4) {
+        call(viewSetUint8, dataView, [
+          dataIndex,
+          u5s[index] << 7 | u5s[++index] << 2 | u5s[++index] >> 3,
+        ]);
+      } else { // remainder == 6
+        call(viewSetUint8, dataView, [
+          dataIndex,
+          u5s[index] << 5 | u5s[++index, index++],
+        ]);
+      }
+    }
+    return call(getViewBuffer, dataView, []);
+  }
+};
+
 /**
  * Returns the result of decoding the provided base64 string into an
  * ArrayBuffer.
@@ -253,6 +370,88 @@ const encodeBase16 = (buffer) => {
   return stringFromCodeUnits(...resultingCodeUnits);
 };
 
+/**
+ * Returns the result of encoding the provided ArrayBuffer into a
+ * base32 string.
+ *
+ * ※ This function is not exposed.
+ */
+const encodeBase32 = (buffer, wrmg = false) => {
+  const dataView = new View(buffer);
+  const byteLength = call(getBufferByteLength, buffer, []);
+  const minimumLengthOfResults = ceil(byteLength * 8 / 5);
+  const fillByte = wrmg ? 0x2D : 0x3D;
+  const resultingCodeUnits = fill(
+    objectCreate(
+      binaryCodeUnitIterablePrototype,
+      {
+        length: {
+          value: minimumLengthOfResults +
+            (8 - (minimumLengthOfResults % 8)) % 8,
+        },
+      },
+    ),
+    fillByte,
+  );
+  for (let index = 0; index < byteLength;) {
+    const codeUnitIndex = ceil(index * 8 / 5);
+    const currentIndex = codeUnitIndex + +(
+      0b01011 & 1 << index % 5 &&
+      resultingCodeUnits[codeUnitIndex] != fillByte
+    ); // bytes 0, 1 & 3 handle two letters; this is for the second
+    const remainder = currentIndex % 8;
+    const currentByte = call(viewGetUint8, dataView, [index]);
+    const nextByte =
+      0b01011010 & 1 << remainder && ++index < byteLength
+        // digits 1, 3, 4 & 6 span multiple bytes
+        ? call(viewGetUint8, dataView, [index])
+        : 0;
+    const u5 = remainder == 0
+      ? currentByte >> 3
+      : remainder == 1
+      ? (currentByte & 0b00000111) << 2 | nextByte >> 6
+      : remainder == 2
+      ? (currentByte & 0b00111111) >> 1
+      : remainder == 3
+      ? (currentByte & 0b00000001) << 4 | nextByte >> 4
+      : remainder == 4
+      ? (currentByte & 0b00001111) << 1 | nextByte >> 7
+      : remainder == 5
+      ? (currentByte & 0b01111111) >> 2
+      : remainder == 6
+      ? (currentByte & 0b00000011) << 3 | nextByte >> 5
+      : (++index, currentByte & 0b00011111); // remainder == 7
+    const result = wrmg
+      ? u5 < 10 ? u5 + 48 : u5 < 18
+        ? u5 + 55
+        // skip I
+        : u5 < 20
+        ? u5 + 56
+        // skip L
+        : u5 < 22
+        ? u5 + 57
+        // skip O
+        : u5 < 27
+        ? u5 + 58
+        // skip U
+        : u5 < 32
+        ? u5 + 59
+        : -1
+      : u5 < 26
+      ? u5 + 65
+      : u5 < 32
+      ? u5 + 24
+      : -1;
+    if (result < 0) {
+      throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`);
+    } else {
+      resultingCodeUnits[currentIndex] = result;
+    }
+  }
+  const answer = stringFromCodeUnits(...resultingCodeUnits);
+  return wrmg ? answer.replace(/-+$/u, "") : answer;
+};
+
 /**
  * Returns the result of encoding the provided ArrayBuffer into a
  * base64 string.
@@ -355,6 +554,28 @@ export const base16Binary = ($, ...$s) =>
 export const base16String = ($, ...$s) =>
   encodeBase16(bufferFromArgs($, $s));
 
+/**
+ * Returns an ArrayBuffer generated from the provided base32 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 base32
+ * string.
+ */
+export const base32Binary = ($, ...$s) =>
+  decodeBase32(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base32 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 base32String = ($, ...$s) =>
+  encodeBase32(bufferFromArgs($, $s));
+
 /**
  * Returns an ArrayBuffer generated from the provided base64 string.
  *
@@ -416,6 +637,25 @@ export const isBase16 = ($) => {
   }
 };
 
+/**
+ * Returns whether the provided value is a base32 string.
+ *
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
+ */
+export const isBase32 = ($) => {
+  if (typeof $ !== "string") {
+    return false;
+  } else {
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    const trimmed = source.length % 8 == 0
+      ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "")
+      : source;
+    return trimmed.length % 8 != 1 &&
+      call(reExec, /[^2-7A-Z/]/iu, [trimmed]) == null;
+  }
+};
+
 /**
  * Returns whether the provided value is a Base64 string.
  *
@@ -453,3 +693,44 @@ export const isFilenameSafeBase64 = ($) => {
       call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null;
   }
 };
+
+/**
+ * Returns whether the provided value is a W·R·M·G (Crockford) base32
+ * string. Check digits are not supported.
+ *
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
+ */
+export const isWRMGBase32 = ($) => {
+  if (typeof $ !== "string") {
+    return false;
+  } else {
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    const trimmed = stringReplace(source, /-/gu, "");
+    return trimmed.length % 8 != 1 &&
+      call(reExec, /[^0-9A-TV-Z]/iu, [trimmed]) == null;
+  }
+};
+
+/**
+ * Returns an ArrayBuffer generated from the provided W·R·M·G
+ * (Crockford) base32 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 W·R·M·G
+ * base32 string.
+ */
+export const wrmgBase32Binary = ($, ...$s) =>
+  decodeBase32(sourceFromArgs($, $s), true);
+
+/**
+ * Returns a (big‐endian) W·R·M·G (Crockford) base32 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 wrmgBase32String = ($, ...$s) =>
+  encodeBase32(bufferFromArgs($, $s), true);
index 9dbce5753e7ee3970093973517a82fc6e595d6d1..f3c831764db4737ee98e7bc881601a881dc6ab8e 100755 (executable)
@@ -18,84 +18,122 @@ import {
 import {
   base16Binary,
   base16String,
+  base32Binary,
+  base32String,
   base64Binary,
   base64String,
   filenameSafeBase64Binary,
   filenameSafeBase64String,
   isBase16,
+  isBase32,
   isBase64,
   isFilenameSafeBase64,
+  isWRMGBase32,
+  wrmgBase32Binary,
+  wrmgBase32String,
 } from "./binary.js";
 
 // These tests assume a LITTLE‐ENDIAN environment.
 const data = new Map([
   ["", {
     base16: "",
+    base32: "",
     base64: "",
+    wrmg: "",
   }],
   ["base64", {
     base16: "006200610073006500360034",
+    base32: "ABRAAYIAOMAGKABWAA2A====",
     base64: "AGIAYQBzAGUANgA0",
+    wrmg: "01H00R80EC06A01P00T0",
   }],
   [new Uint16Array([62535]), {
     base16: "47F4",
+    base32: "I72A====",
     base64: "R/Q=",
+    wrmg: "8ZT0",
   }],
   [new Uint8ClampedArray([75, 73, 66, 73]).buffer, {
     base16: "4B494249",
+    base32: "JNEUESI=",
     base64: "S0lCSQ==",
+    wrmg: "9D4M4J8",
   }],
   [new DataView(new Uint8Array([98, 97, 115, 101, 54, 52]).buffer), {
     base16: "626173653634",
+    base32: "MJQXGZJWGQ======",
     base64: "YmFzZTY0",
+    wrmg: "C9GQ6S9P6G",
   }],
 
   // The following three examples are from RFC 3548.
   [new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]), {
     base16: "14FB9C03D97E",
+    base32: "CT5ZYA6ZPY======",
     base64: "FPucA9l+",
+    wrmg: "2KXSR0YSFR",
   }],
   [new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9]), {
     base16: "14FB9C03D9",
+    base32: "CT5ZYA6Z",
     base64: "FPucA9k=",
+    wrmg: "2KXSR0YS",
   }],
   [new Uint8Array([0x14, 0xFB, 0x9C, 0x03]), {
     base16: "14FB9C03",
+    base32: "CT5ZYAY=",
     base64: "FPucAw==",
+    wrmg: "2KXSR0R",
   }],
 
   // The following examples are from the Ruby base32 gem.
   [new Uint8Array([0x28]), {
     base16: "28",
+    base32: "FA======",
     base64: "KA==",
+    wrmg: "50",
   }],
   [new Uint8Array([0xD6]), {
     base16: "D6",
+    base32: "2Y======",
     base64: "1g==",
+    wrmg: "TR",
   }],
   [new Uint16Array([0xF8D6]), {
     base16: "D6F8",
+    base32: "234A====",
     base64: "1vg=",
+    wrmg: "TVW0",
   }],
   [new Uint8Array([0xD6, 0xF8, 0x00]), {
     base16: "D6F800",
+    base32: "234AA===",
     base64: "1vgA",
+    wrmg: "TVW00",
   }],
   [new Uint8Array([0xD6, 0xF8, 0x10]), {
     base16: "D6F810",
+    base32: "234BA===",
     base64: "1vgQ",
+    wrmg: "TVW10",
   }],
   [new Uint32Array([0x0C11F8D6]), {
     base16: "D6F8110C",
+    base32: "234BCDA=",
     base64: "1vgRDA==",
+    wrmg: "TVW1230",
   }],
   [new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]), {
     base16: "D6F8110C80",
+    base32: "234BCDEA",
     base64: "1vgRDIA=",
+    wrmg: "TVW12340",
   }],
   [new Uint16Array([0xF8D6, 0x0C11, 0x3085]), {
     base16: "D6F8110C8530",
+    base32: "234BCDEFGA======",
     base64: "1vgRDIUw",
+    wrmg: "TVW1234560",
   }],
 ]);
 
@@ -212,6 +250,126 @@ describe("base16String", () => {
   });
 });
 
+describe("base32Binary", () => {
+  it("[[Call]] returns the correct data", () => {
+    assertEquals(
+      new Uint8Array(base32Binary("")),
+      new Uint8Array([]),
+      "<empty>",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("ABRAAYIAOMAGKABWAA2A====")),
+      Uint8Array.from(
+        "\u{0}b\u{0}a\u{0}s\u{0}e\u{0}6\u{0}4",
+        ($) => $.charCodeAt(0),
+      ),
+      "ABRAAYIAOMAGKABWAA2A====",
+    );
+    assertEquals(
+      new Uint16Array(base32Binary("I72A====")),
+      new Uint16Array([62535]),
+      "I72A====",
+    );
+    assertEquals(
+      new Uint8ClampedArray(base32Binary("JNEUESI=")),
+      new Uint8ClampedArray([75, 73, 66, 73]),
+      "JNEUESI=",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("MJQXGZJWGQ======")),
+      new Uint8Array([98, 97, 115, 101, 54, 52]),
+      "MJQXGZJWGQ======",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("CT5ZYA6ZPY======")),
+      new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]),
+      "CT5ZYA6ZPY======",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("CT5ZYA6Z")),
+      new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9]),
+      "CT5ZYA6Z",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("CT5ZYAY=")),
+      new Uint8Array([0x14, 0xFB, 0x9C, 0x03]),
+      "CT5ZYAY=",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("FA======")),
+      new Uint8Array([0x28]),
+      "FA======",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("2Y======")),
+      new Uint8Array([0xD6]),
+      "2Y======",
+    );
+    assertEquals(
+      new Uint16Array(base32Binary("234A====")),
+      new Uint16Array([0xF8D6]),
+      "234A====",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("234AA===")),
+      new Uint8Array([0xD6, 0xF8, 0x00]),
+      "234AA===",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("234BA===")),
+      new Uint8Array([0xD6, 0xF8, 0x10]),
+      "234BA===",
+    );
+    assertEquals(
+      new Uint32Array(base32Binary("234BCDA=")),
+      new Uint32Array([0x0C11F8D6]),
+      "234BCDA=",
+    );
+    assertEquals(
+      new Uint8Array(base32Binary("234BCDEA")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "234BCDEA",
+    );
+    assertEquals(
+      new Uint16Array(base32Binary("234BCDEFGA======")),
+      new Uint16Array([0xF8D6, 0x0C11, 0x3085]),
+      "234BCDEFGA======",
+    );
+  });
+
+  it("[[Call]] is case‐insensitive", () => {
+    assertEquals(
+      new Uint8Array(base32Binary("234bcdEA")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "234bcdEA",
+    );
+  });
+
+  it("[[Call]] throws when provided with an invalid character", () => {
+    assertThrows(() => base32Binary("ABC0"));
+    assertThrows(() => base32Binary("ABC1"));
+    assertThrows(() => base32Binary("ABC8"));
+    assertThrows(() => base32Binary("ABC9"));
+  });
+
+  it("[[Call]] throws when provided with an invalid equals", () => {
+    assertThrows(() => base32Binary("CT3ZYAY=="));
+  });
+
+  it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 4", () => {
+    assertThrows(() => base32Binary("234BCDEAA"));
+    assertThrows(() => base32Binary("234BCDEAA======="));
+  });
+});
+
+describe("base32String", () => {
+  it("[[Call]] returns the correct string", () => {
+    for (const [source, { base32 }] of data) {
+      assertStrictEquals(base32String(source), base32);
+    }
+  });
+});
+
 describe("base64Binary", () => {
   it("[[Call]] returns the correct data", () => {
     assertEquals(
@@ -446,6 +604,33 @@ describe("isBase16", () => {
   });
 });
 
+describe("isBase32", () => {
+  it("[[Call]] returns true for base32 strings", () => {
+    for (const { base32 } of data.values()) {
+      assert(isBase32(base32));
+      assert(isBase32(base32.toLowerCase()));
+    }
+  });
+
+  it("[[Call]] returns false for others", () => {
+    [
+      undefined,
+      null,
+      true,
+      Symbol(),
+      27,
+      98n,
+      {},
+      [],
+      () => {},
+      new Proxy({}, {}),
+      "ABC1",
+      "A=======",
+      "ABCDEFGHI",
+    ].forEach((value) => assert(!isBase32(value)));
+  });
+});
+
 describe("isBase64", () => {
   it("[[Call]] returns true for base64 strings", () => {
     for (const { base64 } of data.values()) {
@@ -501,3 +686,193 @@ describe("isFilenameSafeBase64", () => {
     ].forEach((value) => assert(!isFilenameSafeBase64(value)));
   });
 });
+
+describe("isWRMGBase32", () => {
+  it("[[Call]] returns true for W·R·M·G base32 strings", () => {
+    for (const { wrmg } of data.values()) {
+      assert(isWRMGBase32(wrmg));
+      assert(isWRMGBase32(wrmg.toLowerCase()));
+      assert(isWRMGBase32(`--${wrmg}--`));
+      assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "I")));
+      assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "L")));
+      assert(isWRMGBase32(wrmg.replaceAll(/0/gu, "O")));
+      assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "i")));
+      assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "l")));
+      assert(isWRMGBase32(wrmg.replaceAll(/0/gu, "o")));
+      assert(isWRMGBase32(wrmg.replaceAll(/./gu, ($) => {
+        const rand = Math.random();
+        return rand < 0.25
+          ? $
+          : rand < 0.5
+          ? `-${$}`
+          : rand < 0.75
+          ? `${$}-`
+          : `-${$}-`;
+      })));
+    }
+  });
+
+  it("[[Call]] returns false for others", () => {
+    [
+      undefined,
+      null,
+      true,
+      Symbol(),
+      27,
+      98n,
+      {},
+      [],
+      () => {},
+      new Proxy({}, {}),
+      "ABCU",
+      "A",
+      "ABCDEFGH1",
+    ].forEach((value) => assert(!isWRMGBase32(value)));
+  });
+});
+
+describe("wrmgBase32Binary", () => {
+  it("[[Call]] returns the correct data", () => {
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("")),
+      new Uint8Array([]),
+      "<empty>",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("01H00R80EC06A01P00T0")),
+      Uint8Array.from(
+        "\u{0}b\u{0}a\u{0}s\u{0}e\u{0}6\u{0}4",
+        ($) => $.charCodeAt(0),
+      ),
+      "01H00R80EC06A01P00T0",
+    );
+    assertEquals(
+      new Uint16Array(wrmgBase32Binary("8ZT0")),
+      new Uint16Array([62535]),
+      "8ZT0",
+    );
+    assertEquals(
+      new Uint8ClampedArray(wrmgBase32Binary("9D4M4J8")),
+      new Uint8ClampedArray([75, 73, 66, 73]),
+      "9D4M4J8",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("C9GQ6S9P6G")),
+      new Uint8Array([98, 97, 115, 101, 54, 52]),
+      "C9GQ6S9P6G",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("2KXSR0YSFR")),
+      new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]),
+      "2KXSR0YSFR",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("2KXSR0YS")),
+      new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9]),
+      "2KXSR0YS",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("2KXSR0R")),
+      new Uint8Array([0x14, 0xFB, 0x9C, 0x03]),
+      "2KXSR0R",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("50")),
+      new Uint8Array([0x28]),
+      "50",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TR")),
+      new Uint8Array([0xD6]),
+      "TR",
+    );
+    assertEquals(
+      new Uint16Array(wrmgBase32Binary("TVW0")),
+      new Uint16Array([0xF8D6]),
+      "TVW0",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVW00")),
+      new Uint8Array([0xD6, 0xF8, 0x00]),
+      "TVW00",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVW10")),
+      new Uint8Array([0xD6, 0xF8, 0x10]),
+      "TVW10",
+    );
+    assertEquals(
+      new Uint32Array(wrmgBase32Binary("TVW1230")),
+      new Uint32Array([0x0C11F8D6]),
+      "TVW1230",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVW12340")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "TVW12340",
+    );
+    assertEquals(
+      new Uint16Array(wrmgBase32Binary("TVW1234560")),
+      new Uint16Array([0xF8D6, 0x0C11, 0x3085]),
+      "TVW1234560",
+    );
+  });
+
+  it("[[Call]] is case‐insensitive", () => {
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("tVw12340")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "tVw12340",
+    );
+  });
+
+  it("[[Call]] casts I, L & O", () => {
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVWI234O")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "TVWI234O",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVWi234o")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "TVWi234o",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVWL234O")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "TVWL234O",
+    );
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("TVWl234o")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "TVWl234o",
+    );
+  });
+
+  it("[[Call]] ignores hyphens", () => {
+    assertEquals(
+      new Uint8Array(wrmgBase32Binary("--TVW---123-40-----")),
+      new Uint8Array([0xD6, 0xF8, 0x11, 0x0C, 0x80]),
+      "--TVW---123-40-----",
+    );
+  });
+
+  it("[[Call]] throws when provided with an invalid character", () => {
+    assertThrows(() => wrmgBase32Binary("ABCu"));
+    assertThrows(() => wrmgBase32Binary("ABCU"));
+    assertThrows(() => wrmgBase32Binary("ABC="));
+  });
+
+  it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 4", () => {
+    assertThrows(() => wrmgBase32Binary("234BCDEAA"));
+    assertThrows(() => wrmgBase32Binary("2-34-B--CD-EA-A-"));
+  });
+});
+
+describe("wrmgBase32String", () => {
+  it("[[Call]] returns the correct string", () => {
+    for (const [source, { wrmg }] of data) {
+      assertStrictEquals(wrmgBase32String(source), wrmg);
+    }
+  });
+});
This page took 0.282069 seconds and 4 git commands to generate.