Created
November 4, 2025 00:35
-
-
Save iso2022jp/b71d82d8875d0228e3bc708277dfe505 to your computer and use it in GitHub Desktop.
Base32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'use strict'; | |
| // @ts-check | |
| /** | |
| * The Base 32 Encoding and Base 32 Encoding with Extended Hex Alphabet | |
| * | |
| * This module provides Base32 encoding and decoding functionality similar to Uint8Array's Base64 methods | |
| * @see https://www.rfc-editor.org/rfc/rfc4648.html#section-6 | |
| * @see https://www.rfc-editor.org/rfc/rfc4648.html#section-7 | |
| */ | |
| /** | |
| * Radix 32 code units to base32 characters | |
| * 0123456789abcdefghijklmnopqrstuv => ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 | |
| * @type {Map<string, string>} | |
| */ | |
| const BASE32_ENCODE_MAPPING = new Map([ | |
| ['0', 'A'], ['1', 'B'], ['2', 'C'], ['3', 'D'], ['4', 'E'], ['5', 'F'], ['6', 'G'], ['7', 'H'], | |
| ['8', 'I'], ['9', 'J'], ['a', 'K'], ['b', 'L'], ['c', 'M'], ['d', 'N'], ['e', 'O'], ['f', 'P'], | |
| ['g', 'Q'], ['h', 'R'], ['i', 'S'], ['j', 'T'], ['k', 'U'], ['l', 'V'], ['m', 'W'], ['n', 'X'], | |
| ['o', 'Y'], ['p', 'Z'], ['q', '2'], ['r', '3'], ['s', '4'], ['t', '5'], ['u', '6'], ['v', '7'], | |
| ]); | |
| const BIAS = [undefined, 0x100000000, 0x1000000, 0x10000, 0x100, 1]; | |
| const PADDINGS = [undefined, '======', '====', '===', '=', '']; | |
| const CODE_LENGTHS = [undefined, 2, 4, 5, 7, 8]; | |
| /** | |
| * Base32 characters to radix 32 code units | |
| * ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 => 0123456789abcdefghijklmnopqrstuv | |
| * @type {Map<string, string>} | |
| */ | |
| const BASE32_DECODE_MAPPING = new Map(Array.from(BASE32_ENCODE_MAPPING, ([k, v]) => [v, k])); | |
| BASE32_DECODE_MAPPING.set('=', '='); | |
| /** | |
| * @param {number} value 40-bit integer | |
| * @returns {string} Radix 32 codes | |
| */ | |
| const toRadix32 = value => value.toString(32).padStart(8, '0'); | |
| /** | |
| * @param {string} digits Radix 32 codes | |
| * @returns {number} 40-bit integer | |
| */ | |
| const fromRadix32 = digits => parseInt(digits, 32); | |
| /** | |
| * @param {Uint8Array} buffer | |
| * @param {{alphabet?: string, omitPadding?: boolean}} [options] | |
| * @returns {string} | |
| */ | |
| const encode = (buffer, options = undefined) => { | |
| const {alphabet = 'base32', omitPadding = false} = options || {}; // ?? | |
| if (!['base32', 'base32hex'].includes(alphabet)) { | |
| throw new TypeError(`Unsupported alphabet: ${alphabet}`); | |
| } | |
| const mapCodes = alphabet === 'base32' | |
| ? characters => Array.from(characters, c => BASE32_ENCODE_MAPPING.get(c)).join('') | |
| : characters => characters.toUpperCase(); | |
| const outputGroups = []; | |
| for (let i = 0; i < buffer.length; i += 5) { | |
| const group = buffer.slice(i, i + 5); | |
| // Bit-wise operation cannot be used for 40-bit integer | |
| const quantum = group.reduce((a, c) => a * 256 + c, 0) * BIAS[group.length]; | |
| const characters = toRadix32(quantum); | |
| const outputGroup = mapCodes(characters).slice(0, CODE_LENGTHS[group.length]); | |
| outputGroups.push(outputGroup); | |
| if (group.length < 5 && !omitPadding) { | |
| outputGroups.push(PADDINGS[group.length]); | |
| } | |
| } | |
| return outputGroups.join(''); | |
| }; | |
| /** | |
| * @param {string} string | |
| * @param {{alphabet?: string, lastChunkHandling?: string}} [options] | |
| * @returns {Uint8Array} | |
| */ | |
| const decode = (string, options = undefined) => { | |
| const {alphabet = 'base32', lastChunkHandling = 'loose'} = options || {}; // ?? | |
| if (!['base32', 'base32hex'].includes(alphabet)) { | |
| throw new TypeError(`Unsupported alphabet: ${alphabet}`); | |
| } | |
| if (!['strict', 'loose', 'stop-before-partial'].includes(lastChunkHandling)) { | |
| throw new TypeError(`Unsupported lastChunkHandling: ${lastChunkHandling}`); | |
| } | |
| string = string.replace(/[\t\n\f\r ]/g, ''); | |
| string = string.toUpperCase(); | |
| if (alphabet === 'base32') { | |
| if (!/^[2-7A-Z=]*$/.test(string)) { | |
| throw new SyntaxError(`Found a character that cannot be part of a valid ${alphabet} string`); | |
| } | |
| string = Array.from(string, c => BASE32_DECODE_MAPPING.get(c)).join('').toUpperCase(); | |
| } else { | |
| if (!/^[\dA-V=]*$/.test(string)) { | |
| throw new SyntaxError(`Found a character that cannot be part of a valid ${alphabet} string`); | |
| } | |
| } | |
| if (lastChunkHandling === 'strict') { | |
| if (!/^(?:[\dA-V]{8})*(?:[\dA-V][048CGKOS]======|[\dA-V]{3}[0G]====|[\dA-V]{4}[02468ACEGIKMOQSU]===|[\dA-V]{6}[08GO]=)?$/.test(string)) { | |
| throw new SyntaxError(`Invalid ${alphabet} string`); | |
| } | |
| } | |
| if (lastChunkHandling === 'loose') { | |
| if (!/^(?:[\dA-V]{8})*(?:[\dA-V]{2}={0,6}|[\dA-V]{4}={0,4}|[\dA-V]{5}={0,3}|[\dA-V]{7}=?)?$/.test(string)) { | |
| throw new SyntaxError(`Invalid ${alphabet} string`); | |
| } | |
| } | |
| if (lastChunkHandling === 'stop-before-partial') { | |
| if (!/^(?:[\dA-V]{8})*(?:[\dA-V](?:[048CGKOS]={0,6})?|[\dA-V](?:[\dA-V](?:[\dA-V](?:[0G]={0,4})?)?)?|[\dA-V](?:[\dA-V](?:[\dA-V](?:[\dA-V](?:[02468ACEGIKMOQSU]={0,3})?)?)?)?|[\dA-V](?:[\dA-V](?:[\dA-V](?:[\dA-V](?:[\dA-V](?:[\dA-V](?:[08GO]=?)?)?)?)?)?)?)?$/.test(string)) { | |
| throw new SyntaxError(`Invalid ${alphabet} string`); | |
| } | |
| string = string.slice(0, string.length & -8); | |
| } | |
| string = string.replace(/=+$/, ''); | |
| const outputLength = Math.floor(string.length * 5 / 8); | |
| string = string.padEnd((string.length + 7) & -8, '0'); | |
| const buffer = new ArrayBuffer(Math.floor(string.length * 5 / 8)); | |
| const view = new DataView(buffer); | |
| let o = 0; | |
| for (let i = 0; i < string.length; i += 8) { | |
| const group = string.slice(i, i + 8); | |
| const quantum = fromRadix32(group); | |
| view.setInt8(o++, (quantum / 0x100000000) | 0); | |
| view.setUint32(o, quantum); | |
| o += 4; | |
| } | |
| return new Uint8Array(buffer, 0, outputLength); | |
| } | |
| module.exports = { | |
| encode, | |
| decode | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'use strict'; | |
| const base32 = require('../src/base32'); | |
| const encoder = new TextEncoder(); | |
| const utf8 = input => encoder.encode(input) | |
| const decoder = new TextDecoder(); | |
| const fromUtf8 = input => decoder.decode(input); | |
| describe('RFC 4648: ', () => { | |
| describe('Base 32', () => { | |
| describe('RFC 4648 Test Vectors', () => { | |
| it('should encode base32 strings with default options', () => { | |
| const o = undefined; | |
| expect(base32.encode(utf8(''), o)).toBe(''); | |
| expect(base32.encode(utf8('f'), o)).toBe('MY======'); | |
| expect(base32.encode(utf8('fo'), o)).toBe('MZXQ===='); | |
| expect(base32.encode(utf8('foo'), o)).toBe('MZXW6==='); | |
| expect(base32.encode(utf8('foob'), o)).toBe('MZXW6YQ='); | |
| expect(base32.encode(utf8('fooba'), o)).toBe('MZXW6YTB'); | |
| expect(base32.encode(utf8('foobar'), o)).toBe('MZXW6YTBOI======'); | |
| }); | |
| it('should encode base32 strings with base32 alphabet', () => { | |
| const o = {alphabet: 'base32'}; | |
| expect(base32.encode(utf8(''), o)).toBe(''); | |
| expect(base32.encode(utf8('f'), o)).toBe('MY======'); | |
| expect(base32.encode(utf8('fo'), o)).toBe('MZXQ===='); | |
| expect(base32.encode(utf8('foo'), o)).toBe('MZXW6==='); | |
| expect(base32.encode(utf8('foob'), o)).toBe('MZXW6YQ='); | |
| expect(base32.encode(utf8('fooba'), o)).toBe('MZXW6YTB'); | |
| expect(base32.encode(utf8('foobar'), o)).toBe('MZXW6YTBOI======'); | |
| }); | |
| it('should encode base32 strings with base32 alphabet and no padding', () => { | |
| const o = {alphabet: 'base32', omitPadding: true}; | |
| expect(base32.encode(utf8(''), o)).toBe(''); | |
| expect(base32.encode(utf8('f'), o)).toBe('MY'); | |
| expect(base32.encode(utf8('fo'), o)).toBe('MZXQ'); | |
| expect(base32.encode(utf8('foo'), o)).toBe('MZXW6'); | |
| expect(base32.encode(utf8('foob'), o)).toBe('MZXW6YQ'); | |
| expect(base32.encode(utf8('fooba'), o)).toBe('MZXW6YTB'); | |
| expect(base32.encode(utf8('foobar'), o)).toBe('MZXW6YTBOI'); | |
| }); | |
| it('should decode base32 strings with default options', () => { | |
| const o = undefined; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MY', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('MZXQ', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('MZXW6', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('MZXW6YQ', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('MZXW6YTB', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBOI', o))).toBe('foobar'); | |
| }); | |
| it('should decode base32 strings with base32 alphabet with strict handling', () => { | |
| const o = {alphabet: 'base32', lastChunkHandling: 'strict'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MY======', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('MZXQ====', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('MZXW6===', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('MZXW6YQ=', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('MZXW6YTB', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBOI======', o))).toBe('foobar'); | |
| }); | |
| it('should decode base32 strings with base32 alphabet with loose handling', () => { | |
| const o = {alphabet: 'base32', lastChunkHandling: 'loose'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MY', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('MZXQ', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('MZXW6', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('MZXW6YQ', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('MZXW6YTB', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBOI', o))).toBe('foobar'); | |
| }); | |
| it('should decode base32 strings with base32 alphabet with stop-before-partial handling', () => { | |
| const o = {alphabet: 'base32', lastChunkHandling: 'stop-before-partial'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MY', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MY=====', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MY======', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('M', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZ', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZX', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZXQ', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZXQ=', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZXQ====', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('MZXW6', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZXW6==', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZXW6===', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('MZXW6YQ=', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('MZXW6YQ', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('MZXW6YTB', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBO', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBOI', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBOI=====', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('MZXW6YTBOI======', o))).toBe('foobar'); | |
| }); | |
| }); | |
| describe('whitespace and case-insensitive decode', () => { | |
| it('should ignore ASCII whitespace in input', () => { | |
| expect(fromUtf8(base32.decode(' \tM\nZ\rX\fQ '))).toBe('fo'); | |
| }); | |
| it('should accept ASCII lowercase in input', () => { | |
| expect(fromUtf8(base32.decode('mzxq===='))).toBe('fo'); | |
| }); | |
| }); | |
| describe('lastChunkHandling', () => { | |
| it('strict: should accept only correctly padded strings and throw otherwise', () => { | |
| expect(() => base32.decode('MY', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MY===', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MY======', {lastChunkHandling: 'strict'})).not.toThrow(); // 'f' | |
| }); | |
| it('loose: should accept partially padded/unpadded last chunk', () => { | |
| expect(fromUtf8(base32.decode('MY', {lastChunkHandling: 'loose'}))).toBe('f'); | |
| expect(fromUtf8(base32.decode('MY===', {lastChunkHandling: 'loose'}))).toBe('f'); | |
| expect(fromUtf8(base32.decode('MY======', {lastChunkHandling: 'loose'}))).toBe('f'); | |
| }); | |
| it('strict: should throw on invalid base32 strings even if correctly padded', () => { | |
| expect(() => base32.decode('M=======', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('M3======', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MZX=====', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MZX7====', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MZXW7===', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MZXW6Y==', {lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('MZXW6YX=', {lastChunkHandling: 'strict'})).toThrow(); | |
| }); | |
| it('loose: should decode invalid base32 strings to best effort result', () => { | |
| expect(() => base32.decode('M=======', {lastChunkHandling: 'loose'})).toThrow(); | |
| expect(fromUtf8(base32.decode('M3======', {lastChunkHandling: 'loose'}))).toBe('f'); | |
| expect(() => base32.decode('MZX=====', {lastChunkHandling: 'loose'})).toThrow(); | |
| expect(fromUtf8(base32.decode('MZX7====', {lastChunkHandling: 'loose'}))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('MZXW7===', {lastChunkHandling: 'loose'}))).toBe('foo'); | |
| expect(() => base32.decode('MZXW6Y==', {lastChunkHandling: 'loose'})).toThrow(); | |
| expect(fromUtf8(base32.decode('MZXW6YX=', {lastChunkHandling: 'loose'}))).toBe('foob'); | |
| }); | |
| }); | |
| }); | |
| describe('Base 32 Encoding with Extended Hex Alphabet', () => { | |
| describe('RFC 4648 Test Vectors', () => { | |
| it('should encode base32hex strings with padding', () => { | |
| const o = {alphabet: 'base32hex'}; | |
| expect(base32.encode(utf8(''), o)).toBe(''); | |
| expect(base32.encode(utf8('f'), o)).toBe('CO======'); | |
| expect(base32.encode(utf8('fo'), o)).toBe('CPNG===='); | |
| expect(base32.encode(utf8('foo'), o)).toBe('CPNMU==='); | |
| expect(base32.encode(utf8('foob'), o)).toBe('CPNMUOG='); | |
| expect(base32.encode(utf8('fooba'), o)).toBe('CPNMUOJ1'); | |
| expect(base32.encode(utf8('foobar'), o)).toBe('CPNMUOJ1E8======'); | |
| }); | |
| it('should encode base32hex strings without padding', () => { | |
| const o = {alphabet: 'base32hex', omitPadding: true}; | |
| expect(base32.encode(utf8(''), o)).toBe(''); | |
| expect(base32.encode(utf8('f'), o)).toBe('CO'); | |
| expect(base32.encode(utf8('fo'), o)).toBe('CPNG'); | |
| expect(base32.encode(utf8('foo'), o)).toBe('CPNMU'); | |
| expect(base32.encode(utf8('foob'), o)).toBe('CPNMUOG'); | |
| expect(base32.encode(utf8('fooba'), o)).toBe('CPNMUOJ1'); | |
| expect(base32.encode(utf8('foobar'), o)).toBe('CPNMUOJ1E8'); | |
| }); | |
| it('should decode base32hex strings with default options', () => { | |
| const o = {alphabet: 'base32hex'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CO', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('CPNG', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('CPNMU', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('CPNMUOG', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E8', o))).toBe('foobar'); | |
| }); | |
| it('should decode base32hex strings with strict handling', () => { | |
| const o = {alphabet: 'base32hex', lastChunkHandling: 'strict'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CO======', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('CPNG====', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('CPNMU===', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('CPNMUOG=', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E8======', o))).toBe('foobar'); | |
| }); | |
| it('should decode base32hex strings with loose handling', () => { | |
| const o = {alphabet: 'base32hex', lastChunkHandling: 'loose'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CO', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('CPNG', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('CPNMU', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('CPNMUOG', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E8', o))).toBe('foobar'); | |
| }); | |
| it('should decode base32hex strings with stop-before-partial handling', () => { | |
| const o = {alphabet: 'base32hex', lastChunkHandling: 'stop-before-partial'}; | |
| expect(fromUtf8(base32.decode('', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CO', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CO=====', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CO======', o))).toBe('f'); | |
| expect(fromUtf8(base32.decode('C', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CP', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPN', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPNG', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPNG===', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPNG====', o))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('CPNMU', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPNMU==', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPNMU===', o))).toBe('foo'); | |
| expect(fromUtf8(base32.decode('CPNMUOG', o))).toBe(''); | |
| expect(fromUtf8(base32.decode('CPNMUOG=', o))).toBe('foob'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E8', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E8=====', o))).toBe('fooba'); | |
| expect(fromUtf8(base32.decode('CPNMUOJ1E8======', o))).toBe('foobar'); | |
| }); | |
| }); | |
| describe('whitespace and case-insensitive decode', () => { | |
| it('should ignore ASCII whitespace in input', () => { | |
| expect(fromUtf8(base32.decode(' \tC\nP\rN\fG ', {alphabet: 'base32hex'}))).toBe('fo'); | |
| }); | |
| it('should accept ASCII lowercase in input', () => { | |
| expect(fromUtf8(base32.decode('cpng====', {alphabet: 'base32hex'}))).toBe('fo'); | |
| }); | |
| }); | |
| describe('lastChunkHandling', () => { | |
| it('strict: should accept only correctly padded strings and throw otherwise', () => { | |
| expect(() => base32.decode('CO', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CO===', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CO======', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).not.toThrow(); // 'f' | |
| }); | |
| it('loose: should accept partially padded/unpadded last chunk', () => { | |
| expect(fromUtf8(base32.decode('CO', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('f'); | |
| expect(fromUtf8(base32.decode('CO===', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('f'); | |
| expect(fromUtf8(base32.decode('CO======', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('f'); | |
| }); | |
| it('strict: should throw on invalid base32 strings even if correctly padded', () => { | |
| expect(() => base32.decode('C=======', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CR======', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CPN=====', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CPNV====', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CPNMV===', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CPNMUO==', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| expect(() => base32.decode('CPNMUON=', {alphabet: 'base32hex', lastChunkHandling: 'strict'})).toThrow(); | |
| }); | |
| it('loose: should decode invalid base32 strings to best effort result', () => { | |
| expect(() => base32.decode('C=======', {alphabet: 'base32hex', lastChunkHandling: 'loose'})).toThrow(); | |
| expect(fromUtf8(base32.decode('CR======', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('f'); | |
| expect(() => base32.decode('CPN=====', {alphabet: 'base32hex', lastChunkHandling: 'loose'})).toThrow(); | |
| expect(fromUtf8(base32.decode('CPNV====', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('fo'); | |
| expect(fromUtf8(base32.decode('CPNMV===', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('foo'); | |
| expect(() => base32.decode('CPNMUO==', {alphabet: 'base32hex', lastChunkHandling: 'loose'})).toThrow(); | |
| expect(fromUtf8(base32.decode('CPNMUON=', {alphabet: 'base32hex', lastChunkHandling: 'loose'}))).toBe('foob'); | |
| }); | |
| }); | |
| }); | |
| describe('round-trip arbitrary bytes', () => { | |
| const cases = [ | |
| new Uint8Array([]), | |
| new Uint8Array([0]), | |
| new Uint8Array([0, 1]), | |
| new Uint8Array([0, 1, 2]), | |
| new Uint8Array([0, 1, 2, 3]), | |
| new Uint8Array([0, 1, 2, 3, 4]), | |
| new Uint8Array([255, 254, 253, 252, 251]), | |
| new Uint8Array([0, 255, 16, 32, 64, 128, 255]) | |
| ]; | |
| const optionsSet = [ | |
| undefined, | |
| {alphabet: 'base32'}, | |
| {alphabet: 'base32hex'}, | |
| ]; | |
| for (const bytes of cases) { | |
| it(JSON.stringify(Array.from(bytes)), () => { | |
| for (const options of optionsSet) { | |
| const encoded = base32.encode(bytes, options); | |
| const decoded = base32.decode(encoded, options); | |
| expect(decoded).toEqual(bytes); | |
| } | |
| }); | |
| } | |
| }); | |
| describe('error handling', () => { | |
| it('encode should throw on unsupported alphabet', () => { | |
| expect(() => base32.encode(encoder.encode(''), {alphabet: 'unsupported'})).toThrowError(TypeError); | |
| }); | |
| it('decode should throw on unsupported lastChunkHandling', () => { | |
| expect(() => base32.decode('', {lastChunkHandling: 'unsupported'})).toThrowError(TypeError); | |
| }); | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment