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

Base85 Operations #299

Closed
wants to merge 3 commits into from
Closed
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.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const Categories = [
"From Base32",
"To Base58",
"From Base58",
"To Base85",
"From Base85",
"To Base",
"From Base",
"To BCD",
Expand Down
32 changes: 32 additions & 0 deletions src/core/config/OperationConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.<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.",
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.<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.",
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.<br><br>This operation shows all possible offsets for a given string so that each possible encoding can be considered.",
Expand Down
3 changes: 3 additions & 0 deletions src/core/config/modules/Default.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
165 changes: 165 additions & 0 deletions src/core/operations/Base85.js
Original file line number Diff line number Diff line change
@@ -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: "!\"#$%&&apos;()*+,-./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("'", "&apos;");
let name;
Base85.ALPHABET_OPTIONS.forEach(function(a) {
if (escape(alphabet) === escape(a.value)) name = a.name;
});
return name;
}

};

export default Base85;
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
89 changes: 89 additions & 0 deletions test/tests/operations/Base85.js
Original file line number Diff line number Diff line change
@@ -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],
},
],
},
]);