From 16577751ebd6a5da301bdb96fc736acc2838a4d4 Mon Sep 17 00:00:00 2001 From: Sebastian Ganson Date: Sun, 14 Feb 2021 12:17:25 -0500 Subject: [PATCH 1/2] 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. --- src/core/config/Categories.json | 2 + src/core/lib/Base94.mjs | 178 +++++++++++++++++++++++++++++ src/core/operations/FromBase94.mjs | 84 ++++++++++++++ src/core/operations/ToBase94.mjs | 76 ++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 src/core/lib/Base94.mjs create mode 100644 src/core/operations/FromBase94.mjs create mode 100644 src/core/operations/ToBase94.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 3a5eb0d556..70caf2ebca 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -29,6 +29,8 @@ "Show Base64 offsets", "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 0000000000..88ad16a5c8 --- /dev/null +++ b/src/core/lib/Base94.mjs @@ -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; + +} diff --git a/src/core/operations/FromBase94.mjs b/src/core/operations/FromBase94.mjs new file mode 100644 index 0000000000..634a6ddd79 --- /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 0000000000..cf2991588d --- /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; From 747eb1c192f376704de4e497204db41fda50bd81 Mon Sep 17 00:00:00 2001 From: Sebastian Ganson Date: Sun, 14 Feb 2021 13:25:20 -0500 Subject: [PATCH 2/2] Lint corrections Lint corrections --- src/core/lib/Base94.mjs | 90 +++++++++++++----------------- src/core/operations/FromBase94.mjs | 2 +- src/core/operations/ToBase94.mjs | 2 +- 3 files changed, 40 insertions(+), 54 deletions(-) diff --git a/src/core/lib/Base94.mjs b/src/core/lib/Base94.mjs index 88ad16a5c8..9fe02c6827 100644 --- a/src/core/lib/Base94.mjs +++ b/src/core/lib/Base94.mjs @@ -28,46 +28,39 @@ export function toBase94(data, strictLength=true) { if (data instanceof ArrayBuffer) { data = new Uint8Array(data); + } else { + throw new OperationError(`Invalid - Input not instanceof ArrayBuffer.`); } - else - { - throw new OperationError(`Invalid - Input not instanceof ArrayBuffer.`); - } - - let dataModLen = data.length % 4; - if (dataModLen > 0 && strictLength) - { + 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; - let dataPad = new Uint8Array(data.length + (dataModLen > 0 ? (4 - dataModLen) : 0)); + const dataPad = new Uint8Array(data.length + (dataModLen > 0 ? (4 - dataModLen) : 0)); - dataPad.set(data,0); + dataPad.set(data, 0); while (i < dataPad.length) { acc = 0; - for(j = 0; j < 4; j++) - { + for (j = 0; j < 4; j++) { - acc *= 256; + acc *= 256; - acc += dataPad[i + (3 - j)]; + acc += dataPad[i + (3 - j)]; } - for(j = 0; j < 5; j++) - { + for (j = 0; j < 5; j++) { - output += String.fromCharCode((acc % 94)+32); + output += String.fromCharCode((acc % 94)+32); - acc = Math.floor(acc / 94); + acc = Math.floor(acc / 94); } @@ -105,73 +98,66 @@ export function fromBase94(data, strictLength=true, removeInvalidChars=false) { data = Utils.strToByteArray(data); - } - else { + } else { - throw new OperationError(`Invalid - typeof base94 input is not a string.`); + 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.`); - } + 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 (stringModLen > 0) { - if(strictLength) - { - throw new OperationError(`Invalid - Input string length must be a multiple of 5.`); - } + if (strictLength) { + throw new OperationError(`Invalid - Input string length must be a multiple of 5.`); + } - stringModLen = 5 - stringModLen; + stringModLen = 5 - stringModLen; - while(stringModLen > 0) - { + while (stringModLen > 0) { - data.push(32); + data.push(32); - stringModLen -= 1; + stringModLen -= 1; - } + } } - let output = [], i = 0, j = 0, acc = 0; + const output = []; + let i = 0, j = 0, acc = 0; while (i < data.length) { - acc = 0; + acc = 0; - for (j = 0; j < 5; j++) - { + for (j = 0; j < 5; j++) { acc = (acc * 94) + data[i + 4 - j] - 32; - } + } - for (j = 0; j < 4; j++) - { + for (j = 0; j < 4; j++) { output.push(acc % 256); acc = Math.floor(acc / 256); - } + } - i += 5; + i += 5; - } + } return output; diff --git a/src/core/operations/FromBase94.mjs b/src/core/operations/FromBase94.mjs index 634a6ddd79..3bb56acbfe 100644 --- a/src/core/operations/FromBase94.mjs +++ b/src/core/operations/FromBase94.mjs @@ -44,7 +44,7 @@ class FromBase94 extends Operation { */ run(input, args) { - const [strictLength,removeInvalidChars] = args; + const [strictLength, removeInvalidChars] = args; return fromBase94(input, strictLength, removeInvalidChars); diff --git a/src/core/operations/ToBase94.mjs b/src/core/operations/ToBase94.mjs index cf2991588d..8bc53232d6 100644 --- a/src/core/operations/ToBase94.mjs +++ b/src/core/operations/ToBase94.mjs @@ -39,7 +39,7 @@ class ToBase94 extends Operation { */ run(input, args) { const [strictLength] = args; - return toBase94(input,strictLength); + return toBase94(input, strictLength); } /**