Add Base94 encode and decode operations

Base94 is a notation for encoding arbitrary byte data using a restricted set of symbols and is found primarily in the finance/ATM technology space.
This commit is contained in:
Sebastian Ganson 2021-02-14 12:17:25 -05:00
parent 22fe5a6ae7
commit 16577751eb
4 changed files with 340 additions and 0 deletions

View file

@ -29,6 +29,8 @@
"Show Base64 offsets", "Show Base64 offsets",
"To Base85", "To Base85",
"From Base85", "From Base85",
"To Base94",
"From Base94",
"To Base", "To Base",
"From Base", "From Base",
"To BCD", "To BCD",

178
src/core/lib/Base94.mjs Normal file
View file

@ -0,0 +1,178 @@
/**
* Base94 functions.
*
* @author sganson@trustedsecurity.com]
* @license Apache-2.0
*/
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Base94's the input byte array, returning a string.
* Every four bytes of input are converted to five bytes of
* Base94 encoded output.
*
* @param {ArrayBuffer} data
* @param {boolean} [strictLength="true"]
* @returns {string}
*
* @example
* // returns "@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# "
* // toBase94([48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]);
* // e.g. toBase94(ToHex("Hello World!"))
*/
export function toBase94(data, strictLength=true) {
if (!data) return "";
if (data instanceof ArrayBuffer) {
data = new Uint8Array(data);
}
else
{
throw new OperationError(`Invalid - Input not instanceof ArrayBuffer.`);
}
let dataModLen = data.length % 4;
if (dataModLen > 0 && strictLength)
{
throw new OperationError(`Invalid - Input byte length must be a multiple of 4.`);
}
let output = "", i = 0, j = 0, acc = 0;
let dataPad = new Uint8Array(data.length + (dataModLen > 0 ? (4 - dataModLen) : 0));
dataPad.set(data,0);
while (i < dataPad.length) {
acc = 0;
for(j = 0; j < 4; j++)
{
acc *= 256;
acc += dataPad[i + (3 - j)];
}
for(j = 0; j < 5; j++)
{
output += String.fromCharCode((acc % 94)+32);
acc = Math.floor(acc / 94);
}
i += 4;
}
return output;
}
/**
* Un-Base94's the input string, returning a byte array.
* Every five bytes of Base94 encoded input are converted to
* four bytes of output.
*
* @param {string} data // Base94 encoded string
* @param {boolean} [strictLength="true"]
* @param {boolean} [removeInvalidChars="false"]
* @returns {byteArray}
*
* @example
* // returns [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]
* // fromBase94("@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# ", true, true);
* // e.g. fromHex(fromBase94(....)); -> Hello World!
*/
export function fromBase94(data, strictLength=true, removeInvalidChars=false) {
if (!data) {
return [];
}
if (typeof data == "string") {
data = Utils.strToByteArray(data);
}
else {
throw new OperationError(`Invalid - typeof base94 input is not a string.`);
}
const re = new RegExp("[^\x20-\x7e]", "g");
if(re.test(data))
{
if (removeInvalidChars) {
data = data.replace(re, "");
}
else {
throw new OperationError(`Invalid content in Base94 string.`);
}
}
let stringModLen = data.length % 5;
if (stringModLen > 0)
{
if(strictLength)
{
throw new OperationError(`Invalid - Input string length must be a multiple of 5.`);
}
stringModLen = 5 - stringModLen;
while(stringModLen > 0)
{
data.push(32);
stringModLen -= 1;
}
}
let output = [], i = 0, j = 0, acc = 0;
while (i < data.length) {
acc = 0;
for (j = 0; j < 5; j++)
{
acc = (acc * 94) + data[i + 4 - j] - 32;
}
for (j = 0; j < 4; j++)
{
output.push(acc % 256);
acc = Math.floor(acc / 256);
}
i += 5;
}
return output;
}

View file

@ -0,0 +1,84 @@
/**
* @author sganson@trustedsecurity.com]
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {fromBase94} from "../lib/Base94.mjs";
/**
* From Base94 operation
*/
class FromBase94 extends Operation {
/**
* FromBase94 constructor
*/
constructor() {
super();
this.name = "From Base94";
this.module = "Default";
this.description = "Base94 is a notation for encoding arbitrary byte data using a restricted set of symbols and is found primarily in the finance/ATM technology space.<br/><br/>This operation decodes an ASCII Base94 string returning a byteArray.<br/><br/>e.g. <code>@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# </code> becomes <code>[48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]</code><br/><br/>This is a no frills, no soft toilet paper implementation. It's string in, byteArray out.<br/><br/>By default, input length is expected to by a multiple of 5. Unchecking 'Strict length' will pad non mod 5 length input with space(s).<br/><br/>Base94 encoded content is expected to be in ASCII range '0x20 thru 0x7e'. Leaving 'Remove Invalid Chars' unchecked will enforce this.";
this.inputType = "string";
this.outputType = "byteArray";
this.args = [
{
name: "Strict length",
type: "boolean",
value: true
},
{
name: "Remove Invalid Chars",
type: "boolean",
value: false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const [strictLength,removeInvalidChars] = args;
return fromBase94(input, strictLength, removeInvalidChars);
}
/**
* Highlight to Base94
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight(pos, args) {
pos[0].start = Math.ceil(pos[0].start / 4 * 5);
pos[0].end = Math.floor(pos[0].end / 4 * 5);
return pos;
}
/**
* Highlight from Base94
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlightReverse(pos, args) {
pos[0].start = Math.floor(pos[0].start / 5 * 4);
pos[0].end = Math.ceil(pos[0].end / 5 * 4);
return pos;
}
}
export default FromBase94;

View file

@ -0,0 +1,76 @@
/**
* @author sganson@trustedsecurity.com]
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {toBase94} from "../lib/Base94.mjs";
/**
* To Base64 operation
*/
class ToBase94 extends Operation {
/**
* ToBase94 constructor
*/
constructor() {
super();
this.name = "To Base94";
this.module = "Default";
this.description = "Base94 is a notation for encoding arbitrary byte data using a restricted set of symbols and is found primarily in the finance/ATM technology space.<br/><br/>This operation encodes raw data into an ASCII Base94 string.<br/><br/>e.g. <code>[48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]</code> becomes <code>@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# </code><br/><br/>This is a no frills, no soft toilet paper implementation. It's ArrayBuffer in, string out.<br/><br/>By default, input length is expected to by a multiple of 4. Unchecking 'Strict length' will pad non mod 4 length input with zero(es).";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
name: "Strict length",
type: "boolean",
value: true
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [strictLength] = args;
return toBase94(input,strictLength);
}
/**
* Highlight to Base94
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight(pos, args) {
pos[0].start = Math.floor(pos[0].start / 4 * 5);
pos[0].end = Math.ceil(pos[0].end / 4 * 5);
return pos;
}
/**
* Highlight from Base94
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlightReverse(pos, args) {
pos[0].start = Math.ceil(pos[0].start / 5 * 4);
pos[0].end = Math.floor(pos[0].end / 5 * 4);
return pos;
}
}
export default ToBase94;