Skip to content

Add Base94 encode and decode operations #1174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"From Base92",
"To Base85",
"From Base85",
"To Base94",
"From Base94",
"To Base",
"From Base",
"To BCD",
Expand Down
164 changes: 164 additions & 0 deletions src/core/lib/Base94.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/**
* Base94 functions.
*
* @author [email protected]]
* @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.`);
}

const 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;

const 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;

}

}

const output = [];
let 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;

}
84 changes: 84 additions & 0 deletions src/core/operations/FromBase94.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @author [email protected]]
* @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;
76 changes: 76 additions & 0 deletions src/core/operations/ToBase94.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @author [email protected]]
* @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;
Loading