base32-ts
Version:
Base32 encode and decode implementation in Typescript
140 lines (117 loc) • 4.12 kB
text/typescript
export namespace Base32 {
const enum Alphabets {
RFC4648 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
RFC4648_HEX = '0123456789ABCDEFGHIJKLMNOPQRSTUV',
CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ',
}
export type Variant = 'RFC3548' | 'RFC4648' | 'RFC4648-HEX' | 'Crockford';
/**
* Encodes an ArrayBuffer in base32
*
* @param buffer Buffer to encode
* @param variant Base32 encoding format
* @returns The base32 encoded ArrayBuffer
*/
export function encode(buffer: ArrayBuffer, variant: Variant = 'RFC4648'): string {
let alphabet: string;
let defaultPadding: boolean;
switch (variant) {
case 'RFC3548':
case 'RFC4648':
alphabet = Alphabets.RFC4648;
defaultPadding = true;
break;
case 'RFC4648-HEX':
alphabet = Alphabets.RFC4648_HEX;
defaultPadding = true;
break;
case 'Crockford':
alphabet = Alphabets.CROCKFORD;
defaultPadding = false;
break;
default:
throw new Error(`Unknown base32 variant: ${variant}`);
}
const length = buffer.byteLength
const array = new Uint8Array(buffer);
let bits = 0;
let value = 0;
let output = '';
for (let i = 0; i < length; i++) {
value = (value << 8) | array[i];
bits += 8;
while (bits >= 5) {
output += alphabet[(value >>> (bits - 5)) & 31];
bits -= 5;
}
}
if (bits > 0) {
output += alphabet[(value << (5 - bits)) & 31]
}
if (defaultPadding) {
while (output.length % 8 !== 0) {
output += '=';
}
}
return output;
}
/**
* Decodes a base32 encoded string
*
* @param input The base32 encoded string to decode
* @param variant Base32 encoding format
* @returns The decoded string as an ArrayBuffer
*/
export function decode(input: string, variant: Variant = 'RFC4648'): ArrayBuffer {
let alphabet: string;
let cleanedInput: string;
switch (variant) {
case 'RFC3548':
case 'RFC4648':
alphabet = Alphabets.RFC4648;
cleanedInput = input.toUpperCase().replace(/\=+$/, '');
break;
case 'RFC4648-HEX':
alphabet = Alphabets.RFC4648_HEX;
cleanedInput = input.toUpperCase().replace(/\=+$/, '');
break;
case 'Crockford':
alphabet = Alphabets.CROCKFORD;
cleanedInput = input
.toUpperCase()
.replace(/0/g, '')
.replace(/[IL]/g, '1');
break;
default:
throw new Error(`Unknown base32 variant: ${variant}`);
}
const length = cleanedInput.length;
const output = new Uint8Array(((length * 5) / 8) | 0);
let bits = 0;
let value = 0;
let index = 0;
for (let i = 0; i < length; i++) {
value = (value << 5) | readChar(alphabet, cleanedInput[i]);
bits += 5;
if (bits >= 8) {
output[index++] = (value >>> (bits - 8)) & 255;
bits -= 8;
}
}
return output.buffer;
}
/**
* Finds a char index in an alphabet
*
* @param alphabet The alphbet
* @param char The char to find
* @returns The char index in the alphabet
*/
function readChar(alphabet: string, char: string): number {
const idx = alphabet.indexOf(char);
if (idx === -1) {
throw new Error(`Invalid character found: ${char}`);
}
return idx;
}
}