diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index bebdd6a5..c62fe5da 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -35,6 +35,8 @@ "From Base92", "To Base85", "From Base85", + "To Base94", + "From Base94", "To Base", "From Base", "To BCD", diff --git a/src/core/lib/Base94.mjs b/src/core/lib/Base94.mjs new file mode 100644 index 00000000..9fe02c68 --- /dev/null +++ b/src/core/lib/Base94.mjs @@ -0,0 +1,164 @@ +/** + * 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.`); + } + + 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; + +} diff --git a/src/core/operations/FromBase94.mjs b/src/core/operations/FromBase94.mjs new file mode 100644 index 00000000..3bb56acb --- /dev/null +++ b/src/core/operations/FromBase94.mjs @@ -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.

This operation decodes an ASCII Base94 string returning a byteArray.

e.g. @Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# becomes [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]

This is a no frills, no soft toilet paper implementation. It's string in, byteArray out.

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).

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; diff --git a/src/core/operations/ToBase94.mjs b/src/core/operations/ToBase94.mjs new file mode 100644 index 00000000..8bc53232 --- /dev/null +++ b/src/core/operations/ToBase94.mjs @@ -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.

This operation encodes raw data into an ASCII Base94 string.

e.g. [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21] becomes @Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr#

This is a no frills, no soft toilet paper implementation. It's ArrayBuffer in, string out.

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;