Skip to content

Commit b482d0c

Browse files
committed
Add Base94 encode and decode operations gchq#1174
2 parents c8b0a3b + 747eb1c commit b482d0c

File tree

4 files changed

+326
-0
lines changed

4 files changed

+326
-0
lines changed

src/core/config/Categories.json

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"From Base91",
3636
"To Base92",
3737
"From Base92",
38+
"To Base94",
39+
"From Base94",
3840
"To Base",
3941
"From Base",
4042
"To BCD",

src/core/lib/Base94.mjs

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Base94 functions.
3+
*
4+
* @author [email protected]]
5+
* @license Apache-2.0
6+
*/
7+
8+
import Utils from "../Utils.mjs";
9+
import OperationError from "../errors/OperationError.mjs";
10+
11+
/**
12+
* Base94's the input byte array, returning a string.
13+
* Every four bytes of input are converted to five bytes of
14+
* Base94 encoded output.
15+
*
16+
* @param {ArrayBuffer} data
17+
* @param {boolean} [strictLength="true"]
18+
* @returns {string}
19+
*
20+
* @example
21+
* // returns "@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# "
22+
* // toBase94([48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]);
23+
* // e.g. toBase94(ToHex("Hello World!"))
24+
*/
25+
export function toBase94(data, strictLength=true) {
26+
27+
if (!data) return "";
28+
29+
if (data instanceof ArrayBuffer) {
30+
data = new Uint8Array(data);
31+
} else {
32+
throw new OperationError(`Invalid - Input not instanceof ArrayBuffer.`);
33+
}
34+
35+
const dataModLen = data.length % 4;
36+
37+
if (dataModLen > 0 && strictLength) {
38+
throw new OperationError(`Invalid - Input byte length must be a multiple of 4.`);
39+
}
40+
41+
let output = "", i = 0, j = 0, acc = 0;
42+
43+
const dataPad = new Uint8Array(data.length + (dataModLen > 0 ? (4 - dataModLen) : 0));
44+
45+
dataPad.set(data, 0);
46+
47+
while (i < dataPad.length) {
48+
49+
acc = 0;
50+
51+
for (j = 0; j < 4; j++) {
52+
53+
acc *= 256;
54+
55+
acc += dataPad[i + (3 - j)];
56+
57+
}
58+
59+
for (j = 0; j < 5; j++) {
60+
61+
output += String.fromCharCode((acc % 94)+32);
62+
63+
acc = Math.floor(acc / 94);
64+
65+
}
66+
67+
i += 4;
68+
69+
}
70+
71+
return output;
72+
73+
}
74+
75+
76+
/**
77+
* Un-Base94's the input string, returning a byte array.
78+
* Every five bytes of Base94 encoded input are converted to
79+
* four bytes of output.
80+
*
81+
* @param {string} data // Base94 encoded string
82+
* @param {boolean} [strictLength="true"]
83+
* @param {boolean} [removeInvalidChars="false"]
84+
* @returns {byteArray}
85+
*
86+
* @example
87+
* // returns [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]
88+
* // fromBase94("@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIz&#J<[+][[4+trr# ", true, true);
89+
* // e.g. fromHex(fromBase94(....)); -> Hello World!
90+
*/
91+
export function fromBase94(data, strictLength=true, removeInvalidChars=false) {
92+
93+
if (!data) {
94+
return [];
95+
}
96+
97+
if (typeof data == "string") {
98+
99+
data = Utils.strToByteArray(data);
100+
101+
} else {
102+
103+
throw new OperationError(`Invalid - typeof base94 input is not a string.`);
104+
105+
}
106+
107+
const re = new RegExp("[^\x20-\x7e]", "g");
108+
109+
if (re.test(data)) {
110+
if (removeInvalidChars) {
111+
data = data.replace(re, "");
112+
} else {
113+
throw new OperationError(`Invalid content in Base94 string.`);
114+
}
115+
}
116+
117+
let stringModLen = data.length % 5;
118+
119+
if (stringModLen > 0) {
120+
121+
if (strictLength) {
122+
throw new OperationError(`Invalid - Input string length must be a multiple of 5.`);
123+
}
124+
125+
stringModLen = 5 - stringModLen;
126+
127+
while (stringModLen > 0) {
128+
129+
data.push(32);
130+
131+
stringModLen -= 1;
132+
133+
}
134+
135+
}
136+
137+
const output = [];
138+
let i = 0, j = 0, acc = 0;
139+
140+
while (i < data.length) {
141+
142+
acc = 0;
143+
144+
for (j = 0; j < 5; j++) {
145+
146+
acc = (acc * 94) + data[i + 4 - j] - 32;
147+
148+
}
149+
150+
for (j = 0; j < 4; j++) {
151+
152+
output.push(acc % 256);
153+
154+
acc = Math.floor(acc / 256);
155+
156+
}
157+
158+
i += 5;
159+
160+
}
161+
162+
return output;
163+
164+
}

src/core/operations/FromBase94.mjs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* @author [email protected]]
3+
* @license Apache-2.0
4+
*/
5+
6+
import Operation from "../Operation.mjs";
7+
import {fromBase94} from "../lib/Base94.mjs";
8+
9+
/**
10+
* From Base94 operation
11+
*/
12+
class FromBase94 extends Operation {
13+
14+
/**
15+
* FromBase94 constructor
16+
*/
17+
constructor() {
18+
super();
19+
20+
this.name = "From Base94";
21+
this.module = "Default";
22+
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.";
23+
this.inputType = "string";
24+
this.outputType = "byteArray";
25+
this.args = [
26+
{
27+
name: "Strict length",
28+
type: "boolean",
29+
value: true
30+
},
31+
{
32+
name: "Remove Invalid Chars",
33+
type: "boolean",
34+
value: false
35+
}
36+
];
37+
38+
}
39+
40+
/**
41+
* @param {string} input
42+
* @param {Object[]} args
43+
* @returns {byteArray}
44+
*/
45+
run(input, args) {
46+
47+
const [strictLength, removeInvalidChars] = args;
48+
49+
return fromBase94(input, strictLength, removeInvalidChars);
50+
51+
}
52+
53+
/**
54+
* Highlight to Base94
55+
*
56+
* @param {Object[]} pos
57+
* @param {number} pos[].start
58+
* @param {number} pos[].end
59+
* @param {Object[]} args
60+
* @returns {Object[]} pos
61+
*/
62+
highlight(pos, args) {
63+
pos[0].start = Math.ceil(pos[0].start / 4 * 5);
64+
pos[0].end = Math.floor(pos[0].end / 4 * 5);
65+
return pos;
66+
}
67+
68+
/**
69+
* Highlight from Base94
70+
*
71+
* @param {Object[]} pos
72+
* @param {number} pos[].start
73+
* @param {number} pos[].end
74+
* @param {Object[]} args
75+
* @returns {Object[]} pos
76+
*/
77+
highlightReverse(pos, args) {
78+
pos[0].start = Math.floor(pos[0].start / 5 * 4);
79+
pos[0].end = Math.ceil(pos[0].end / 5 * 4);
80+
return pos;
81+
}
82+
}
83+
84+
export default FromBase94;

src/core/operations/ToBase94.mjs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @author [email protected]]
3+
* @license Apache-2.0
4+
*/
5+
6+
import Operation from "../Operation.mjs";
7+
import {toBase94} from "../lib/Base94.mjs";
8+
9+
/**
10+
* To Base64 operation
11+
*/
12+
class ToBase94 extends Operation {
13+
14+
/**
15+
* ToBase94 constructor
16+
*/
17+
constructor() {
18+
super();
19+
20+
this.name = "To Base94";
21+
this.module = "Default";
22+
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).";
23+
this.inputType = "ArrayBuffer";
24+
this.outputType = "string";
25+
this.args = [
26+
{
27+
name: "Strict length",
28+
type: "boolean",
29+
value: true
30+
}
31+
];
32+
33+
}
34+
35+
/**
36+
* @param {ArrayBuffer} input
37+
* @param {Object[]} args
38+
* @returns {string}
39+
*/
40+
run(input, args) {
41+
const [strictLength] = args;
42+
return toBase94(input, strictLength);
43+
}
44+
45+
/**
46+
* Highlight to Base94
47+
*
48+
* @param {Object[]} pos
49+
* @param {number} pos[].start
50+
* @param {number} pos[].end
51+
* @param {Object[]} args
52+
* @returns {Object[]} pos
53+
*/
54+
highlight(pos, args) {
55+
pos[0].start = Math.floor(pos[0].start / 4 * 5);
56+
pos[0].end = Math.ceil(pos[0].end / 4 * 5);
57+
return pos;
58+
}
59+
60+
/**
61+
* Highlight from Base94
62+
*
63+
* @param {Object[]} pos
64+
* @param {number} pos[].start
65+
* @param {number} pos[].end
66+
* @param {Object[]} args
67+
* @returns {Object[]} pos
68+
*/
69+
highlightReverse(pos, args) {
70+
pos[0].start = Math.ceil(pos[0].start / 5 * 4);
71+
pos[0].end = Math.floor(pos[0].end / 5 * 4);
72+
return pos;
73+
}
74+
}
75+
76+
export default ToBase94;

0 commit comments

Comments
 (0)