Skip to content
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

Added Base85 operations (new) #340

Merged
merged 1 commit into from
Aug 23, 2018
Merged
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 @@ -25,6 +25,8 @@
"From Base32",
"To Base58",
"From Base58",
"To Base85",
"From Base85",
"To Base",
"From Base",
"To BCD",
Expand Down
45 changes: 45 additions & 0 deletions src/core/lib/Base85.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Base85 resources.
*
* @author PenguinGeorge [george@penguingeorge.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/

/**
* Base85 alphabet options.
*/
export const ALPHABET_OPTIONS = [
{
name: "Standard",
value: "!&quot;#$%&&apos;()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[&bsol;]^_`abcdefghijklmnopqrstu",
},
{
name: "Z85 (ZeroMQ)",
value: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#",
},
{
name: "IPv6",
value: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|~}",
}
];


/**
* Returns the name of the alphabet, when given the alphabet.
*
* @param {string} alphabet
* @returns {string}
*/
export function alphabetName(alphabet) {
alphabet = alphabet.replace("'", "&apos;");
alphabet = alphabet.replace("\"", "&quot;");
alphabet = alphabet.replace("\\", "&bsol;");
let name;

ALPHABET_OPTIONS.forEach(function(a) {
if (escape(alphabet) === escape(a.value)) name = a.name;
});

return name;
}
104 changes: 104 additions & 0 deletions src/core/operations/FromBase85.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @author PenguinGeorge [george@penguingeorge.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/

import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85";

/**
* From Base85 operation
*/
class FromBase85 extends Operation {

/**
* From Base85 constructor
*/
constructor() {
super();

this.name = "From Base85";
this.module = "Default";
this.description = "Base85 (similar to Base64) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.<br><br>This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>BOu!rD]j7BEbo7</code> becomes <code>hello world</code><br><br>Base85 is commonly used in Adobe's PostScript and PDF file formats.";
this.infoURL = "https://wikipedia.org/wiki/Ascii85";
this.inputType = "string";
this.outputType = "byteArray";
this.args = [
{
name: "Alphabet",
type: "editableOption",
value: ALPHABET_OPTIONS
},
];
}

/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const alphabet = args[0] || ALPHABET_OPTIONS[0].value,
encoding = alphabetName(alphabet),
result = [];

if (alphabet.length !== 85 ||
[].unique.call(alphabet).length !== 85) {
throw new OperationError("Alphabet must be of length 85");
}

if (input.length === 0) return [];

const matches = input.match(/<~(.+?)~>/);
if (matches !== null) input = matches[1];

let i = 0;
let block, blockBytes;
while (i < input.length) {
if (encoding === "Standard" && input[i] === "z") {
result.push(0, 0, 0, 0);
i++;
} else {
let digits = [];
digits = input
.substr(i, 5)
.split("")
.map((chr, idx) => {
const digit = alphabet.indexOf(chr);
if (digit < 0 || digit > 84) {
throw "Invalid character '" + chr + "' at index " + idx;
}
return digit;
});

block =
digits[0] * 52200625 +
digits[1] * 614125 +
(i + 2 < input.length ? digits[2] : 84) * 7225 +
(i + 3 < input.length ? digits[3] : 84) * 85 +
(i + 4 < input.length ? digits[4] : 84);

blockBytes = [
(block >> 24) & 0xff,
(block >> 16) & 0xff,
(block >> 8) & 0xff,
block & 0xff
];

if (input.length < i + 5) {
blockBytes.splice(input.length - (i + 5), 5);
}

result.push.apply(result, blockBytes);
i += 5;
}
}

return result;
}

}

export default FromBase85;
93 changes: 93 additions & 0 deletions src/core/operations/ToBase85.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* @author PenguinGeorge [george@penguingeorge.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/

import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85";

/**
* To Base85 operation
*/
class ToBase85 extends Operation {

/**
* To Base85 constructor
*/
constructor() {
super();

this.name = "To Base85";
this.module = "Default";
this.description = "Base85 (similar to Base64) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.<br><br>This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>hello world</code> becomes <code>BOu!rD]j7BEbo7</code><br><br>Base85 is commonly used in Adobe's PostScript and PDF file formats.<br><br><strong>Options</strong><br><u>Alphabet</u><ul><li>Standard - The standard alphabet, referred to as Ascii85</li><li>Z85 (ZeroMQ) - A string-safe variant of Base85, which avoids quote marks and backslash characters</li><li>IPv6 - A variant of Base85 suitable for encoding IPv6 addresses (RFC 1924)</li></ul><u>Include delimiter</u><br>Adds a '<~' and '~>' delimiter to the start and end of the data. This is standard for Adobe's implementation of Base85.";
this.infoURL = "https://wikipedia.org/wiki/Ascii85";
this.inputType = "byteArray";
this.outputType = "string";
this.args = [
{
name: "Alphabet",
type: "editableOption",
value: ALPHABET_OPTIONS
},
{
name: "Include Delimeter",
type: "boolean",
value: false
}
];
}

/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const alphabet = args[0] || ALPHABET_OPTIONS[0].value,
encoding = alphabetName(alphabet);
let result = "";

if (alphabet.length !== 85 ||
[].unique.call(alphabet).length !== 85) {
throw new OperationError("Error: alphabet must be of length 85");
}

if (input.length === 0) return "";

let block;
for (let i = 0; i < input.length; i += 4) {
block = (
((input[i]) << 24) +
((input[i + 1] || 0) << 16) +
((input[i + 2] || 0) << 8) +
((input[i + 3] || 0))
) >>> 0;

if (encoding !== "Standard" || block > 0) {
let digits = [];
for (let j = 0; j < 5; j++) {
digits.push(block % 85);
block = Math.floor(block / 85);
}

digits = digits.reverse();

if (input.length < i + 4) {
digits.splice(input.length - (i + 4), 4);
}

result += digits.map(digit => alphabet[digit]).join("");
} else {
result += (encoding === "Standard") ? "z" : null;
}
}

if (args[1] === true) result = "<~" + result + "~>";

return result;
}
}

export default ToBase85;