diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js
index 9d531f8d..ab84bc98 100755
--- a/src/core/config/Categories.js
+++ b/src/core/config/Categories.js
@@ -44,6 +44,8 @@ const Categories = [
"From Base32",
"To Base58",
"From Base58",
+ "To Base85",
+ "From Base85",
"To Base",
"From Base",
"To BCD",
diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js
index 500a8803..f6c72b83 100644
--- a/src/core/config/OperationConfig.js
+++ b/src/core/config/OperationConfig.js
@@ -2,6 +2,7 @@ import Arithmetic from "../operations/Arithmetic.js";
import Base from "../operations/Base.js";
import Base58 from "../operations/Base58.js";
import Base64 from "../operations/Base64.js";
+import Base85 from "../operations/Base85.js";
import BCD from "../operations/BCD.js";
import BitwiseOp from "../operations/BitwiseOp.js";
import ByteRepr from "../operations/ByteRepr.js";
@@ -320,6 +321,37 @@ const OperationConfig = {
}
]
},
+ "To Base85": {
+ module: "Default",
+ description: "Base85 (similar to Base64) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.
This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).
e.g. hello world
becomes BOu!rD]j7BEbo7
Base85 is commonly used in Adobe's PostScript and PDF file formats.
Options
Alphabet
- Standard - The standard alphabet, referred to as Ascii85
- Z85 (ZeroMQ) - A string-safe variant of Base85, which avoids quote marks and backslash characters
- IPv6 - A variant of Base85 suitable for encoding IPv6 addresses (RFC 1924)
Include delimiter
Adds a '<~' and '~>' delimiter to the start and end of the data. This is standard for Adobe's implementation of Base85.",
+ inputType: "byteArray",
+ outputType: "string",
+ args: [
+ {
+ name: "Alphabet",
+ type: "editableOption",
+ value: Base85.ALPHABET_OPTIONS
+ },
+ {
+ name: "Include delimiter",
+ type: "boolean",
+ value: Base85.INCLUDE_DELIMITER
+ },
+ ]
+ },
+ "From Base85": {
+ module: "Default",
+ description: "Base85 (similar to Base64) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.
This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included).
e.g. BOu!rD]j7BEbo7
becomes hello world
Base85 is commonly used in Adobe's PostScript and PDF file formats.",
+ inputType: "string",
+ outputType: "byteArray",
+ args: [
+ {
+ name: "Alphabet",
+ type: "editableOption",
+ value: Base85.ALPHABET_OPTIONS
+ },
+ ]
+ },
"Show Base64 offsets": {
module: "Default",
description: "When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.
This operation shows all possible offsets for a given string so that each possible encoding can be considered.",
diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js
index 6b9f60f9..9c7c28bc 100644
--- a/src/core/config/modules/Default.js
+++ b/src/core/config/modules/Default.js
@@ -2,6 +2,7 @@ import FlowControl from "../../FlowControl.js";
import Arithmetic from "../../operations/Arithmetic.js";
import Base from "../../operations/Base.js";
import Base58 from "../../operations/Base58.js";
+import Base85 from "../../operations/Base85.js";
import Base64 from "../../operations/Base64.js";
import BCD from "../../operations/BCD.js";
import BitwiseOp from "../../operations/BitwiseOp.js";
@@ -74,6 +75,8 @@ OpModules.Default = {
"From Base32": Base64.runFrom32,
"To Base58": Base58.runTo,
"From Base58": Base58.runFrom,
+ "To Base85": Base85.runTo,
+ "From Base85": Base85.runFrom,
"To Base": Base.runTo,
"From Base": Base.runFrom,
"To BCD": BCD.runToBCD,
diff --git a/src/core/operations/Base85.js b/src/core/operations/Base85.js
new file mode 100644
index 00000000..ec0445b6
--- /dev/null
+++ b/src/core/operations/Base85.js
@@ -0,0 +1,165 @@
+/**
+ * Base85 operations.
+ *
+ * @author George J [george@penguingeorge.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+const Base85 = {
+
+ /**
+ * @constant
+ * @default
+ */
+ ALPHABET_OPTIONS: [
+ {
+ name: "Standard",
+ value: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu",
+ },
+ {
+ name: "Z85 (ZeroMQ)",
+ value: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#",
+ },
+ {
+ name: "IPv6",
+ value: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|~}",
+ },
+ ],
+
+ /**
+ * Includes a '<~' and '~>' at the beginning and end of the Base85 data.
+ * @constant
+ * @default
+ */
+ INCLUDE_DELIMITER: false,
+
+ /**
+ * To Base85 operation.
+ *
+ * @param {byteArray} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ runTo: function(input, args) {
+ let alphabet = args[0] || Base85.ALPHABET_OPTIONS[0].value,
+ encoding = Base85._alphabetName(alphabet),
+ result = "";
+
+ if (alphabet.length !== 85 || [].unique.call(alphabet).length !== 85) {
+ throw ("Alphabet must be of length 85");
+ }
+
+ 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] || Base85.INCLUDE_DELIMITER) result = "<~" + result + "~>";
+
+ return result;
+ },
+
+
+ /**
+ * From Base85 operation.
+ *
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {byteArray}
+ */
+ runFrom: function(input, args) {
+ let alphabet = args[0] || Base85.ALPHABET_OPTIONS[0].value,
+ encoding = Base85._alphabetName(alphabet),
+ result = [];
+
+ if (alphabet.length !== 85 || [].unique.call(alphabet).length !== 85) {
+ throw ("Alphabet must be of length 85");
+ }
+
+ let 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) => {
+ let 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;
+ },
+
+ /**
+ * Returns the name of the alphabet, when given the alphabet.
+ */
+ _alphabetName: function(alphabet) {
+ alphabet = alphabet.replace("'", "'");
+ let name;
+ Base85.ALPHABET_OPTIONS.forEach(function(a) {
+ if (escape(alphabet) === escape(a.value)) name = a.name;
+ });
+ return name;
+ }
+
+};
+
+export default Base85;
diff --git a/test/index.js b/test/index.js
index b602fe8a..c075eb8a 100644
--- a/test/index.js
+++ b/test/index.js
@@ -15,6 +15,7 @@ import "babel-polyfill";
import TestRegister from "./TestRegister.js";
import "./tests/operations/Base58.js";
import "./tests/operations/Base64.js";
+import "./tests/operations/Base85.js";
import "./tests/operations/BCD.js";
import "./tests/operations/BitwiseOp.js";
import "./tests/operations/BSON.js";
diff --git a/test/tests/operations/Base85.js b/test/tests/operations/Base85.js
new file mode 100644
index 00000000..d03af226
--- /dev/null
+++ b/test/tests/operations/Base85.js
@@ -0,0 +1,89 @@
+/**
+ * Base85 tests.
+ *
+ * @author George J [george@penguingeorge.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister.js";
+
+TestRegister.addTests([
+ {
+ name: "To Base85 (Standard): nothing",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "To Base85",
+ args: [null, false],
+ },
+ ],
+ },
+ {
+ name: "To Base85 (Standard (Ascii85)): Hello, World!",
+ input: "Hello, World!",
+ expectedOutput: "87cURD_*#4DfTZ)+T",
+ recipeConfig: [
+ {
+ op: "To Base85",
+ args: [null, false],
+ },
+ ],
+ },
+ {
+ name: "To Base85 (Standard (Ascii85)): UTF-8",
+ input: "ნუ პანიკას",
+ expectedOutput: "iIdZZK;0RJK:_%SOPth^iIdNVK:1\\NOPthc",
+ recipeConfig: [
+ {
+ op: "To Base85",
+ args: [null, false],
+ },
+ ],
+ },
+ {
+ name: "To Base85 (Z85 (ZeroMQ)): Hello, World!",
+ input: "Hello, World!",
+ expectedOutput: "nm2QNz.92jz5PV8aP",
+ recipeConfig: [
+ {
+ op: "To Base85",
+ args: ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#", false],
+ },
+ ],
+ },
+ {
+ name: "From Base85 (Standard): nothing",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "From Base85",
+ args: [null, false],
+ },
+ ],
+ },
+ {
+ name: "From Base85 (Standard): Hello, World!",
+ input: "87cURD_*#4DfTZ)+T",
+ expectedOutput: "Hello, World!",
+ recipeConfig: [
+ {
+ op: "From Base85",
+ args: [null, false],
+ },
+ ],
+ },
+ {
+ name: "From Base85 (Standard): UTF-8",
+ input: "iIdZZK;0RJK:_%SOPth^iIdNVK:1\\NOPthc",
+ expectedOutput: "ნუ პანიკას",
+ recipeConfig: [
+ {
+ op: "From Base85",
+ args: [null, false],
+ },
+ ],
+ },
+]);