From: Lady Date: Sat, 20 May 2023 01:05:19 +0000 (-0700) Subject: Add base32 support to binary.js X-Git-Tag: 0.3.0~2 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/d4fe7e59e1444b5e29a71b80d89a8ad5aa1158bd?ds=sidebyside Add base32 support to binary.js Includes both the RFC base32 definition and the “W·R·M·G” definition by Douglas Crockford. --- diff --git a/binary.js b/binary.js index 25fd055..5c78d39 100644 --- 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 ). 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); diff --git a/binary.test.js b/binary.test.js index 9dbce57..f3c8317 100755 --- a/binary.test.js +++ b/binary.test.js @@ -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([]), + "", + ); + 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([]), + "", + ); + 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); + } + }); +});