universal-encoder
Version:
Convert any number or ASCII text to any base, using RFC4648 character set or your own custom character sets.
238 lines (209 loc) • 6.68 kB
JavaScript
"use strict";
class BaseConverter{
constructor(){
this.input_symbols = null;
this.input_base = null;
this.input_preset = null;
this.output_symbols = null;
this.output_base = null;
this.output_preset = null;
// based on RFC4648
this.symbol_presets = {
binary: {
symbols: '01',
base: 2
},
decimal: {
symbols: '0123456789',
base: 10
},
base16: {
symbols: '0123456789ABCDEF',
base: 16
},
base32: {
symbols: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
base: 32
},
base32hex: {
symbols: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
base: 32
},
base64: {
symbols: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
base: 64
},
base64url: {
symbols: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
base: 64
}
};
this.statusStrings = {
no_errors: "Values are safe",
unsafe_symbols_type: "Symbols must be set to a string value",
unsafe_base_type: "Base must be an integer",
unsafe_symbol_len: "Length of symbols must be equal to or grater than base",
dupe_symbol_values: "Symbols must not be repeated",
no_preset: "Preset does not exist",
safe_base_value: "Base must be greater than zero and less than "+Number.MAX_SAFE_INTEGER,
invalid_input: "Unknown symbol in input",
input_too_large: "Input value too large to convert"
};
}
setInputPreset(preset){
this.input_preset = ""+preset;
return this;
}
setOutputPreset(preset){
this.output_preset = ""+preset;
return this;
}
setInputSymbols(symbols){
this.input_preset = null;
this.input_symbols = ""+symbols;
return this;
}
setOutputSymbols(symbols){
this.output_preset = null;
this.output_symbols = ""+symbols;
return this;
}
setInputBase(base){
this.input_preset = null;
this.input_base = parseInt(base);
}
setOutputBase(base){
this.output_preset = null;
this.output_base = parseInt(base);
}
prepAndGetStatus(value=false){
if(this.input_preset){
if(!this.symbol_presets[this.input_preset]) return this.statusStrings.no_preset;
this.input_base = this.symbol_presets[this.input_preset].base;
this.input_symbols = this.symbol_presets[this.input_preset].symbols;
}
if(this.output_preset){
if(!this.symbol_presets[this.output_preset]) return this.statusStrings.no_preset;
this.output_base = this.symbol_presets[this.output_preset].base;
this.output_symbols = this.symbol_presets[this.output_preset].symbols;
}
var safe_symbols_type =
typeof this.input_symbols === 'string' &&
typeof this.output_symbols === 'string';
if(!safe_symbols_type) return this.statusStrings.unsafe_symbols_type;
var safe_base_type =
typeof this.input_base === 'number' &&
typeof this.output_base === 'number';
if(!safe_base_type) return this.statusStrings.unsafe_base_type;
var safe_base_value =
this.input_base > 0 && this.input_base < Number.MAX_SAFE_INTEGER &&
this.output_base > 0 && this.output_base < Number.MAX_SAFE_INTEGER;
if(!safe_base_value) return this.statusStrings.unsafe_base_type;
var safe_symbol_len =
this.input_base >= this.input_symbols.length &&
this.output_base >= this.output_symbols.length;
if(!safe_symbol_len) return this.statusStrings.unsafe_symbol_len;
var nondupe_symbol_values =
[...new Set(this.input_symbols.split(''))].length === this.input_symbols.length &&
[...new Set(this.output_symbols.split(''))].length === this.output_symbols.length;
if(!nondupe_symbol_values) return this.statusStrings.dupe_symbol_values;
if(value){
value = ""+value;
for(let i=0, l=value.length; i<l; i++){
if(!~this.input_symbols.indexOf(value[i])) return this.statusStrings.invalid_input;
}
}
return this.statusStrings.no_errors;
}
convert(value) {
value = "" + value;
var status = this.prepAndGetStatus(value);
if (status !== this.statusStrings.no_errors) throw new Error(status);
var dec_value = value.split('').reverse().reduce((carry, digit, index)=>{
return carry += this.input_symbols.indexOf(digit) * (Math.pow(this.input_base, index));
}, 0);
if(dec_value > Number.MAX_SAFE_INTEGER) throw new Error(this.statusStrings.input_too_large);
var new_value = '';
while (dec_value > 0) {
new_value = this.output_symbols[dec_value % this.output_base] + new_value;
dec_value = (dec_value - (dec_value % this.output_base)) / this.output_base;
}
return new_value;
}
generateHelper(){
return this.convert.bind(this);
}
}
class ASCIIEncoder extends BaseConverter{
constructor(){
super();
this.setInputPreset('binary');
this.pad_char = '=';
}
setPadChar(char){
this.pad_char = char.toString().charAt(0);
return this;
}
static asciiToBinary(ascii){
let bytes = [];
for(let i=0, l=ascii.length; i<l; i++){
bytes.push(ascii.charCodeAt(i).toString(2).padStart(8, '0'));
}
return bytes.join('');
}
static binaryToASCII(binary){
var ascii = [];
for(var i=0, l=binary.length; i<l; i+=8){
ascii.push(String.fromCharCode(parseInt(binary.substring(i, i+8), 2)));
}
return ascii.join('');
}
static leastCommonMultiple(a, b){
if(!a || !b) return 0;
var x = Math.abs(a);
var y = Math.abs(b);
while(y) {
var t = y;
y = x % y;
x = t;
}
var gcd = x;
return Math.abs((a * b) / gcd);
}
encode(ascii){
var binary = this.constructor.asciiToBinary(ascii);
var status = this.prepAndGetStatus(binary);
if (status !== this.statusStrings.no_errors) throw new Error(status);
var bits_per_char = (this.output_base-1).toString(2).length;
var bit_grp_size = this.constructor.leastCommonMultiple(bits_per_char, 8);
var encoded = [];
for(var i=0, l=binary.length; i<l; i+=bits_per_char){
encoded.push(this.output_symbols.charAt(parseInt(binary.substring(i, i+bits_per_char).padEnd(bits_per_char, '0'),2)));
}
var padlen = ((encoded.length * bits_per_char) % bit_grp_size) / bits_per_char;
encoded.push(this.pad_char.repeat(padlen));
return encoded.join('');
}
decode(str){
var bits_per_char = (this.output_base-1).toString(2).length;
var binary = [];
for(var i=0, l=str.length; i<l; i++){
if(this.pad_char !== str.charAt(i)){
binary.push(this.output_symbols.indexOf(str.charAt(i)).toString(2).padStart(bits_per_char, '0'));
}
}
binary = binary.join('');
binary = binary.substring(0, binary.length - (binary.length%8));
return this.constructor.binaryToASCII(binary);
}
generateEncoder(){
return this.encode.bind(this);
}
generateDecoder(){
return this.decode.bind(this);
}
}
if(typeof module === 'object' && module.exports) module.exports = {
BaseConverter,
ASCIIEncoder
};