Merge branch 'master' into fix/from-decimal-auto-delim

This commit is contained in:
min23asdw 2026-07-18 12:27:23 +07:00 committed by GitHub
commit 3a698d4c37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 3973 additions and 2892 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ build
src/core/config/modules/*
src/core/config/OperationConfig.json
src/core/operations/index.mjs
src/core/lib/HTMLEntities.mjs
src/node/config/OperationConfig.json
src/node/index.mjs
tests/operations/index.mjs

View File

@ -367,6 +367,7 @@ module.exports = function (grunt) {
command: chainCommands([
"echo '\n--- Regenerating config files. ---'",
"echo [] > src/core/config/OperationConfig.json",
`node ${nodeFlags} src/core/config/scripts/generateHTMLEntities.mjs`,
`node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`,
`node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`,
"echo '--- Config scripts finished. ---\n'"

View File

@ -83,7 +83,9 @@
"Rison Decode",
"To Modhex",
"From Modhex",
"MIME Decoding"
"MIME Decoding",
"To COBS",
"From COBS"
]
},
{
@ -173,6 +175,7 @@
"AES Key Wrap",
"AES Key Unwrap",
"Pseudo-Random Number Generator",
"Pseudo-Random Prime Generator",
"Enigma",
"Bombe",
"Multiple Bombe",
@ -241,9 +244,8 @@
"Subtract",
"Multiply",
"Divide",
"Modular Exponentiation",
"Modular Inverse",
"Extended GCD",
"Modular Inverse",
"Mean",
"Median",
"Standard Deviation",

View File

@ -0,0 +1,139 @@
/**
* This script automatically generates src/core/lib/HTMLEntities.mjs, the shared
* HTML entity lookup tables used by the "To HTML Entity" and "From HTML Entity"
* operations.
*
* The data is derived from the vendored WHATWG named character reference set
* (src/core/vendor/htmlEntities/entity.json, from
* https://html.spec.whatwg.org/entities.json) so the
* two operations cannot drift apart and every entity is spec-conformant:
*
* - HTML_ENTITY_REVERSE_LOOKUP (decode) is every single-code-point spec name.
* - HTML_ENTITY_LOOKUP (encode) picks one canonical name per code point via a
* deterministic tiebreak, overridden by htmlEntityOverrides.mjs where a
* specific historical name is preferred.
*
* @author roberson-io [michaelroberson@gmail.com]
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
/* eslint no-console: ["off"] */
import path from "path";
import fs from "fs";
import process from "process";
import { fileURLToPath } from "url";
import { HTML_ENTITY_CANONICAL_OVERRIDES } from "./htmlEntityOverrides.mjs";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
if (!fs.existsSync(path.join(process.cwd(), "src/core/lib"))) {
console.log("\nCWD: " + process.cwd());
console.log("Error: generateHTMLEntities.mjs should be run from the project root");
console.log("Example> node src/core/config/scripts/generateHTMLEntities.mjs");
process.exit(1);
}
const SPEC = JSON.parse(fs.readFileSync(
path.join(scriptDir, "..", "..", "vendor", "htmlEntities", "entity.json"), "utf8"));
// Build code point -> [spec names] for single-code-point, semicolon-terminated
// references (the representable subset; multi-code-point entities are skipped).
const codePointNames = {};
for (const [key, val] of Object.entries(SPEC)) {
if (!key.endsWith(";") || val.codepoints.length !== 1) continue;
const name = key.slice(1, -1); // strip leading "&" and trailing ";"
(codePointNames[val.codepoints[0]] ??= []).push(name);
}
// Deterministic canonical-name tiebreak: prefer a lower-case name, then the
// shortest, then alphabetical. Overridden per code point where a specific
// historical name is preferred.
const isAllUpper = s => s === s.toUpperCase() && s !== s.toLowerCase();
/**
* Choose the single canonical entity name for a code point.
*
* @param {number} codePoint - the Unicode code point being encoded
* @param {string[]} names - all valid WHATWG names for that code point
* @returns {string} the canonical name to emit when encoding
*/
function canonicalName(codePoint, names) {
const override = HTML_ENTITY_CANONICAL_OVERRIDES[codePoint];
if (override !== undefined) {
if (!names.includes(override))
throw new Error(`Override &${override}; is not a spec name for code point ${codePoint} (spec: ${names})`);
return override;
}
return [...names].sort((a, b) =>
(isAllUpper(a) - isAllUpper(b)) ||
(a.length - b.length) ||
(a < b ? -1 : a > b ? 1 : 0)
)[0];
}
const forward = {}; // code point -> canonical name (encode)
const reverse = {}; // name -> code point (decode, every spec name)
for (const [cpStr, names] of Object.entries(codePointNames)) {
const cp = Number(cpStr);
forward[cp] = canonicalName(cp, names);
for (const name of names) reverse[name] = cp;
}
// Decode-only aliases = spec names that are not the canonical encode name.
const aliases = {};
for (const [name, cp] of Object.entries(reverse)) {
if (forward[cp] !== name) aliases[name] = cp;
}
// --- emit -----------------------------------------------------------------
const fwdEntries = Object.keys(forward).map(Number).sort((a, b) => a - b)
.map(cp => ` ${cp}: "${forward[cp]}",`).join("\n").replace(/,$/, "");
const aliasEntries = Object.keys(aliases).sort()
.map(n => ` ${JSON.stringify(n)}: ${aliases[n]},`).join("\n").replace(/,$/, "");
const code = `/**
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateHTMLEntities.mjs
*
* HTML entity lookup tables shared by the "To HTML Entity" and "From HTML Entity"
* operations, derived from the WHATWG named character reference set
* (https://html.spec.whatwg.org/entities.json). Do not edit by hand — change the
* vendored src/core/vendor/htmlEntities/entity.json or htmlEntityOverrides.mjs
* and regenerate.
*
* @author roberson-io [michaelroberson@gmail.com]
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
* @license Apache-2.0
*/
/**
* Canonical lookup: Unicode code point -> entity name (without "&" and ";"),
* used for ENCODING. One canonical name per code point.
*/
export const HTML_ENTITY_LOOKUP = {
${fwdEntries}
};
/**
* Legacy / alias names that only DECODE (spelling variants, deprecated names,
* box-drawing aliases, etc.); not used for encoding.
*/
export const HTML_ENTITY_DECODE_ALIASES = {
${aliasEntries}
};
/**
* Derived reverse lookup: entity name -> code point, used for DECODING. Built
* from the canonical lookup plus the legacy aliases.
*/
export const HTML_ENTITY_REVERSE_LOOKUP = {...HTML_ENTITY_DECODE_ALIASES};
for (const codePoint in HTML_ENTITY_LOOKUP) {
HTML_ENTITY_REVERSE_LOOKUP[HTML_ENTITY_LOOKUP[codePoint]] = Number(codePoint);
}
`;
fs.writeFileSync(path.join(process.cwd(), "src/core/lib/HTMLEntities.mjs"), code);
console.log(`generateHTMLEntities: ${Object.keys(forward).length} encode names, ` +
`${Object.keys(reverse).length} decode names, ${Object.keys(aliases).length} aliases, ` +
`${Object.keys(HTML_ENTITY_CANONICAL_OVERRIDES).length} overrides applied.`);

View File

@ -0,0 +1,86 @@
/**
* Canonical entity-name overrides for generateHTMLEntities.mjs.
*
* The WHATWG named character reference set assigns MANY names to some code
* points (e.g. U+2211 is both &sum; and &Sum;), but does not designate a
* canonical one. The generator therefore needs a rule to pick a single name per
* code point for ENCODING. It uses a deterministic tiebreak (prefer a
* lower-case name, then the shortest, then alphabetical), which reproduces the
* historically-emitted name for ~1355 of the ~1414 encodable code points.
*
* This file pins the canonical name for the code points where the tiebreak
* would otherwise change the emitted entity. Every value here is still a valid
* WHATWG name for that code point (the generator asserts this) these are
* editorial choices, not correctness fixes, kept so "To HTML Entity" output
* stays stable. Trim an entry to let the tiebreak decide instead.
*
* @author roberson-io [michaelroberson@gmail.com]
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
/**
* @constant
* @type {Object.<number, string>}
*/
export const HTML_ENTITY_CANONICAL_OVERRIDES = {
124: "verbar",
168: "uml",
177: "plusmn",
189: "frac12",
247: "divide",
711: "caron",
728: "breve",
937: "Omega",
949: "epsilon",
965: "upsilon",
977: "thetasym",
978: "upsih",
981: "straightphi",
8208: "hyphen",
8214: "Verbar",
8230: "hellip",
8289: "ApplyFunction",
8290: "InvisibleTimes",
8291: "InvisibleComma",
8459: "hamilt",
8461: "quaternions",
8463: "planck",
8465: "image",
8472: "weierp",
8474: "rationals",
8476: "real",
8477: "reals",
8484: "integers",
8492: "bernou",
8499: "phmmat",
8500: "order",
8501: "alefsym",
8518: "DifferentialD",
8519: "ExponentialE",
8520: "ImaginaryI",
8612: "LeftTeeArrow",
8613: "UpTeeArrow",
8615: "DownTeeArrow",
8624: "lsh",
8625: "rsh",
8660: "hArr",
8704: "forall",
8711: "nabla",
8712: "isin",
8721: "sum",
8723: "mnplus",
8730: "radic",
8750: "conint",
8768: "wreath",
8776: "asymp",
8781: "asympeq",
8784: "esdot",
8788: "colone",
8869: "perp",
8896: "xwedge",
8897: "xvee",
8902: "sstarf",
10536: "nesear",
10537: "seswar"
};

View File

@ -70,4 +70,3 @@ export function modPow(base, exponent, modulus) {
return result;
}

86
src/core/lib/COBS.mjs Normal file
View File

@ -0,0 +1,86 @@
/**
* @author Imantas Lukenskas [imantas@lukenskas.dev]
* @copyright Imantas Lukenskas 2026
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
/**
* COBS-encode a byte array
* @param {Uint8Array} data
* @return {Uint8Array}
*/
export function toCobs(data) {
if (!data || data.length === 0) {
return new Uint8Array();
}
const output = [];
data = [0, ...data];
while (data.length > 0) {
const endIndex = data.findIndex((value, index) => value === 0 && index > 0);
if ((endIndex < 0 || endIndex > 254) && data.length > 254) {
output.push(255);
output.push(...data.slice(1, 255));
data = data.slice(255);
if (data.length !== 0) {
data = [0, ...data];
}
} else if (endIndex < 0) {
output.push(data.length);
output.push(...data.slice(1));
data = [];
} else {
output.push(endIndex);
output.push(...data.slice(1, endIndex));
data = data.slice(endIndex);
}
}
return output;
}
/**
* COBS-decode a byte array
* @param {Uint8Array} data
* @return {Uint8Array}
*/
export function fromCobs(data) {
if (!data || data.length === 0) {
return new Uint8Array();
}
if (data.findIndex((value) => value === 0) >= 0) {
throw new OperationError("Could not decode from COBS: payload must not contain a 0x00 byte");
}
const output = [];
while (data.length > 0) {
if (data[0] === 0xFF) {
output.push(...data.slice(1, 255));
data = data.slice(255);
} else {
const nextZeroIndex = data[0];
output.push(...data.slice(1, nextZeroIndex));
data = data.slice(nextZeroIndex);
let blockSize = data[0];
while (data.length > 0) {
output.push(0, ...data.slice(1, blockSize));
data = data.slice(blockSize);
if (blockSize === 0xFF) {
break;
}
blockSize = data[0];
}
}
}
return output;
}

View File

@ -0,0 +1,101 @@
/**
* @author p-leriche [philip.leriche@cantab.net]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { parseBigInt, egcd } from "../lib/BigIntUtils.mjs";
/* ---------- operation class ---------- */
/**
* Extended GCD operation
*/
class ExtendedGCD extends Operation {
/**
* ExtendedGCD constructor
*/
constructor() {
super();
this.name = "Extended GCD";
this.module = "Crypto";
this.description =
"Computes the Extended Euclidean Algorithm for integers <i>a</i> and <i>b</i>.<br><br>" +
"Finds integers <i>x</i> and <i>y</i> (Bezout coefficients) such that:<br>" +
"a*x + b*y = gcd(a, b)<br><br>" +
"This is fundamental to many number theory algorithms including modular inverse, " +
"solving linear Diophantine equations, and cryptographic operations.<br><br>" +
"<b>Input handling:</b> If either <i>a</i> or <i>b</i> is left blank, " +
"its value is taken from the Input field.";
this.infoURL = "https://wikipedia.org/wiki/Extended_Euclidean_algorithm";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Value a",
type: "string",
value: ""
},
{
name: "Value b",
type: "string",
value: ""
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [aStr, bStr] = args;
// Trim everything so "" and " " count as empty
const aParam = aStr?.trim();
const bParam = bStr?.trim();
const inputVal = input?.trim();
let a, b;
if (aParam && bParam) {
// Case 1: both values given as parameters
a = aParam;
b = bParam;
} else if (!aParam && bParam) {
// Case 2: a missing - take from input
a = inputVal;
b = bParam;
if (!a) throw new OperationError("Value a must be defined");
} else if (aParam && !bParam) {
// Case 3: b missing - take from input
a = aParam;
b = inputVal;
if (!b) throw new OperationError("Value b must be defined");
} else if (!aParam && !bParam) {
// Case 4: both values missing
throw new OperationError("Values a and b must be defined");
}
const aBI = parseBigInt(a, "Value a");
const bBI = parseBigInt(b, "Value b");
const [g, x, y] = egcd(aBI, bBI);
const gcd = g < 0n ? -g : g;
// Format output string bearing in mind that crypto-grade numbers
// may greatly exceed the line length.
let output = "gcd: " + gcd.toString() + "\n\n";
output += "Bezout coefficients:\n";
output += "x = " + x.toString() + "\n";
output += "y = " + y.toString() + "\n\n";
return output;
}
}
export default ExtendedGCD;

View File

@ -0,0 +1,38 @@
/**
* @author Imantas Lukenskas [imantas@lukenskas.dev]
* @copyright Imantas Lukenskas 2026
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {fromCobs} from "../lib/COBS.mjs";
/**
* From COBS operation
*/
class FromCOBS extends Operation {
/**
* FromCOBS constructor
*/
constructor() {
super();
this.name = "From COBS";
this.module = "Default";
this.description = "Decodes COBS encoded bytes";
this.infoURL = "https://wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing";
this.inputType = "byteArray";
this.outputType = "byteArray";
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
return fromCobs(input);
}
}
export default FromCOBS;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
/**
* @author p-leriche [philip.leriche@cantab.net]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { modPow } from "../lib/BigIntUtils.mjs";
/* ---------- helper functions ---------- */
/**
* Generate random BigInt with specified bit length
*/
function randBigInt(bits) {
const bytes = Math.ceil(bits / 8);
const a = new Uint8Array(bytes);
crypto.getRandomValues(a);
// Set high bit to ensure correct bit length
a[0] |= 1 << (7 - ((8 * bytes - bits)));
// Set low bit to ensure odd (primes > 2 are odd)
a[bytes - 1] |= 1;
let h = "";
for (const b of a) h += b.toString(16).padStart(2, "0");
return BigInt("0x" + h);
}
/**
* Miller-Rabin primality test
*/
function isProbablePrime(n, rounds) {
if (n < 2n) return false;
if (n === 2n || n === 3n) return true;
if (n % 2n === 0n) return false;
// Write n-1 as 2^r * d
let d = n - 1n;
let r = 0n;
while (d % 2n === 0n) {
d /= 2n;
r++;
}
// Witness loop
for (let i = 0; i < rounds; i++) {
const a = randBigInt(n.toString(2).length - 1) % (n - 3n) + 2n;
let x = modPow(a, d, n);
if (x === 1n || x === n - 1n) continue;
let composite = true;
for (let j = 0n; j < r - 1n; j++) {
x = modPow(x, 2n, n);
if (x === n - 1n) {
composite = false;
break;
}
}
if (composite) return false;
}
return true;
}
/* ---------- operation class ---------- */
/**
* Generate Prime Number operation
*/
class GeneratePrime extends Operation {
/**
* GeneratePrime constructor
*/
constructor() {
super();
this.name = "Pseudo-Random Prime Generator";
this.module = "Crypto";
this.description =
"Generates a random probable prime number of specified bit length using the Miller-Rabin primality test.<br><br>" +
"<b>Primality guarantee:</b><br>" +
"For numbers . 3,317, the result is guaranteed prime (deterministic test).<br>" +
"For larger numbers, uses probabilistic testing:<br>" +
"- <b>Standard (7 rounds):</b> Probability of composite approx 1 in 16,000)<br><br>" +
"- <b>Crypto grade (40 rounds):</b> Probability of composite(approx 1 in 10^24)<br>" +
"Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).<br><br>" ;
this.infoURL = "https://wikipedia.org/wiki/Miller-Rabin_primality_test";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Bit length",
type: "number",
value: 512,
min: 2
},
{
name: "Crypto grade",
type: "boolean",
value: false
},
{
name: "Output format",
type: "option",
value: ["Decimal", "Hexadecimal"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [bits, cryptoGrade, outputFormat] = args;
if (bits < 2) {
throw new OperationError("Bit length must be at least 2");
}
if (bits > 4096) {
throw new OperationError("Bit length limited to 4096 bits for performance reasons");
}
const rounds = cryptoGrade ? 40 : 7;
let attempts = 0;
const maxAttempts = 10000;
let n = randBigInt(bits);
while (!isProbablePrime(n, rounds)) {
n = randBigInt(bits);
attempts++;
if (attempts > maxAttempts) {
throw new OperationError(`Failed to generate prime after ${maxAttempts} attempts. Try a different bit length.`);
}
}
// Return only the prime for pipeability
if (outputFormat === "Hexadecimal") {
return "0x" + n.toString(16);
} else {
return n.toString();
}
}
}
export default GeneratePrime;

View File

@ -0,0 +1,107 @@
/**
* @author p-leriche [philip.leriche@cantab.net]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { parseBigInt, egcd } from "../lib/BigIntUtils.mjs";
/* ---------- operation class ---------- */
/**
* Modular Inverse operation
*/
class ModularInverse extends Operation {
/**
* ModularInverse constructor
*/
constructor() {
super();
this.name = "Modular Inverse";
this.module = "Crypto";
this.description =
"Computes the modular multiplicative inverse of <i>a</i> modulo <i>m</i>.<br><br>" +
"Finds <i>x</i> such that a*x = 1 (mod m).<br><br>" +
"<b>Input handling:</b> If either <i>a</i> or <i>m</i> is left blank, " +
"its value is taken from the Input field.";
this.infoURL = "https://wikipedia.org/wiki/Modular_multiplicative_inverse";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Value (a)",
type: "string",
value: ""
},
{
name: "Modulus (m)",
type: "string",
value: ""
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [aStr, mStr] = args;
// Trim everything so "" and " " count as empty
const aParam = aStr?.trim();
const mParam = mStr?.trim();
const inputVal = input?.trim();
let a, m;
if (aParam && mParam) {
// Case 1: value and modulus both given as parameters
a = aParam;
m = mParam;
} else if (!aParam && mParam) {
// Case 2: value missing - take from input
a = inputVal;
m = mParam;
if (!a) throw new OperationError("Value (a) must be defined");
} else if (aParam && !mParam) {
// Case 3: modulus missing - take from input
a = aParam;
m = inputVal;
if (!m) throw new OperationError("Modulus (m) must be defined");
} else if (!aParam && !mParam) {
// Case 4: value and modulus both missing
throw new OperationError("Value (a) and Modulus (m) must be defined");
}
const aBI = parseBigInt(a, "Value (a)");
const mBI = parseBigInt(m, "Modulus (m)");
if (mBI <= 0n) {
throw new OperationError("Modulus must be greater than zero");
}
const aNorm = ((aBI % mBI) + mBI) % mBI;
const [g, x] = egcd(aNorm, mBI);
if (g !== 1n && g !== -1n) {
throw new OperationError("Inverse does not exist because gcd(a, m) ≠ 1");
}
let inv = x;
if (g === -1n) inv = -inv;
inv = ((inv % mBI) + mBI) % mBI;
return inv.toString();
}
}
export default ModularInverse;

View File

@ -0,0 +1,154 @@
/**
* @author p-leriche [philip.leriche@cantab.net]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { modPow } from "../lib/BigIntUtils.mjs";
/* ---------- helper functions ---------- */
/**
* Generate random BigInt with specified bit length
*/
function randBigInt(bits) {
const bytes = Math.ceil(bits / 8);
const a = new Uint8Array(bytes);
crypto.getRandomValues(a);
// Set high bit to ensure correct bit length
a[0] |= 1 << (7 - ((8 * bytes - bits)));
// Set low bit to ensure odd (primes > 2 are odd)
a[bytes - 1] |= 1;
let h = "";
for (const b of a) h += b.toString(16).padStart(2, "0");
return BigInt("0x" + h);
}
/**
* Miller-Rabin primality test
*/
function isProbablePrime(n, rounds) {
if (n < 2n) return false;
if (n === 2n || n === 3n) return true;
if (n % 2n === 0n) return false;
// Write n-1 as 2^r * d
let d = n - 1n;
let r = 0n;
while (d % 2n === 0n) {
d /= 2n;
r++;
}
// Witness loop
for (let i = 0; i < rounds; i++) {
const a = randBigInt(n.toString(2).length - 1) % (n - 3n) + 2n;
let x = modPow(a, d, n);
if (x === 1n || x === n - 1n) continue;
let composite = true;
for (let j = 0n; j < r - 1n; j++) {
x = modPow(x, 2n, n);
if (x === n - 1n) {
composite = false;
break;
}
}
if (composite) return false;
}
return true;
}
/* ---------- operation class ---------- */
/**
* Generate Prime Number operation
*/
class GeneratePrime extends Operation {
/**
* GeneratePrime constructor
*/
constructor() {
super();
this.name = "Pseudo-Random Prime Generator";
this.module = "Crypto";
this.description =
"Generates a random probable prime number of specified bit length using the Miller-Rabin primality test.<br><br>" +
"<b>Primality guarantee:</b><br>" +
"For numbers . 3,317, the result is guaranteed prime (deterministic test).<br>" +
"For larger numbers, uses probabilistic testing:<br>" +
"- <b>Standard (7 rounds):</b> Probability of composite approx 1 in 16,000)<br><br>" +
"- <b>Crypto grade (40 rounds):</b> Probability of composite(approx 1 in 10^24)<br>" +
"Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).<br><br>" ;
this.infoURL = "https://wikipedia.org/wiki/Miller-Rabin_primality_test";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Bit length",
type: "number",
value: 512,
min: 2
},
{
name: "Crypto grade",
type: "boolean",
value: false
},
{
name: "Output format",
type: "option",
value: ["Decimal", "Hexadecimal"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [bits, cryptoGrade, outputFormat] = args;
if (bits < 2) {
throw new OperationError("Bit length must be at least 2");
}
if (bits > 4096) {
throw new OperationError("Bit length limited to 4096 bits for performance reasons");
}
const rounds = cryptoGrade ? 40 : 7;
let attempts = 0;
const maxAttempts = 10000;
let n = randBigInt(bits);
while (!isProbablePrime(n, rounds)) {
n = randBigInt(bits);
attempts++;
if (attempts > maxAttempts) {
throw new OperationError(`Failed to generate prime after ${maxAttempts} attempts. Try a different bit length.`);
}
}
// Return only the prime for pipeability
if (outputFormat === "Hexadecimal") {
return "0x" + n.toString(16);
} else {
return n.toString();
}
}
}
export default GeneratePrime;

View File

@ -0,0 +1,38 @@
/**
* @author Imantas Lukenskas [imantas@lukenskas.dev]
* @copyright Imantas Lukenskas 2026
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {toCobs} from "../lib/COBS.mjs";
/**
* To COBS operation
*/
class ToCOBS extends Operation {
/**
* ToCOBS constructor
*/
constructor() {
super();
this.name = "To COBS";
this.module = "Default";
this.description = "Encodes bytes in COBS format";
this.infoURL = "https://wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing";
this.inputType = "byteArray";
this.outputType = "byteArray";
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
return toCobs(input);
}
}
export default ToCOBS;

File diff suppressed because it is too large Load Diff

2233
src/core/vendor/htmlEntities/entity.json vendored Normal file

File diff suppressed because it is too large Load Diff

14
src/core/vendor/htmlEntities/entity.txt vendored Normal file
View File

@ -0,0 +1,14 @@
entity.json is the WHATWG named character reference set, retrieved verbatim from:
https://html.spec.whatwg.org/entities.json
It is used by src/core/config/scripts/generateHTMLEntities.mjs to generate the
shared HTML entity lookup tables (src/core/lib/HTMLEntities.mjs) consumed by the
"To HTML Entity" and "From HTML Entity" operations.
Source: HTML Standard — 13.5 Named character references
https://html.spec.whatwg.org/multipage/named-characters.html
Copyright and licence
---------------------
Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). This work is licensed under a Creative Commons Attribution 4.0 International License. To the extent portions of it are incorporated into source code, such portions in the source code are licensed under the BSD 3-Clause License instead.

View File

@ -1,7 +1,20 @@
import TestRegister from "../../lib/TestRegister.mjs";
import ToHTMLEntity from "../../../src/core/operations/ToHTMLEntity.mjs";
import FromHTMLEntity from "../../../src/core/operations/FromHTMLEntity.mjs";
import { HTML_ENTITY_LOOKUP, HTML_ENTITY_REVERSE_LOOKUP } from "../../../src/core/lib/HTMLEntities.mjs";
import it from "../assertionHandler.mjs";
import assert from "assert";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import path from "path";
// Vendored WHATWG named character reference set (https://html.spec.whatwg.org/entities.json).
const SPEC = JSON.parse(readFileSync(path.join(
path.dirname(fileURLToPath(import.meta.url)),
"../../../src/core/vendor/htmlEntities/entity.json"), "utf8"));
const specByName = {};
for (const [key, val] of Object.entries(SPEC))
if (key.endsWith(";")) specByName[key.slice(1, -1)] = val.codepoints;
TestRegister.addApiTests([
it("To HTML Entity: every named entity in the table is well-formed", () => {
@ -30,4 +43,40 @@ TestRegister.addApiTests([
}
assert.deepStrictEqual(malformed, [], `Malformed entity value(s) near: ${JSON.stringify(malformed)}`);
}),
it("HTML Entity: named encoding round-trips through From HTML Entity", () => {
// Because both operations share one lookup table, encoding a character to
// a named entity and decoding it must return the original character for
// every BMP code point: FromHTMLEntity(ToHTMLEntity(x)) === x.
const toOp = new ToHTMLEntity(),
fromOp = new FromHTMLEntity();
const mismatches = [];
for (let cp = 0; cp <= 0xFFFF; cp++) {
if (cp >= 0xD800 && cp <= 0xDFFF) continue; // skip surrogate range
const char = String.fromCodePoint(cp);
const encoded = toOp.run(char, [true, "Named entities"]);
const decoded = fromOp.run(encoded, []);
if (decoded !== char)
mismatches.push(`U+${cp.toString(16).toUpperCase().padStart(4, "0")} -> ${encoded} -> U+${decoded.codePointAt(0).toString(16).toUpperCase()}`);
}
assert.deepStrictEqual(mismatches, [], `Round-trip failed for: ${JSON.stringify(mismatches.slice(0, 20))}`);
}),
it("HTML Entity: every table entry is conformant with the WHATWG spec", () => {
// Both lookup tables must agree with entities.json: every encode name is a
// real spec name mapping to exactly that code point, and every decode name
// maps to the spec code point.
const violations = [];
for (const [cp, name] of Object.entries(HTML_ENTITY_LOOKUP)) {
const spec = specByName[name];
if (!spec || spec.length !== 1 || spec[0] !== Number(cp))
violations.push(`encode ${cp} -> &${name}; (spec: ${spec ? JSON.stringify(spec) : "none"})`);
}
for (const [name, cp] of Object.entries(HTML_ENTITY_REVERSE_LOOKUP)) {
const spec = specByName[name];
if (!spec || spec.length !== 1 || spec[0] !== cp)
violations.push(`decode &${name}; -> ${cp} (spec: ${spec ? JSON.stringify(spec) : "none"})`);
}
assert.deepStrictEqual(violations, [], `Spec violations: ${JSON.stringify(violations.slice(0, 20))}`);
}),
]);

View File

@ -0,0 +1,476 @@
/**
* @author Imantas Lukenskas [imantas@lukenskas.dev]
* @copyright Imantas Lukenskas 2026
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
// values in "COBS" testcases are taken from here:
// https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
TestRegister.addTests([
{
"name": "To COBS Example #1",
"input": "00",
"expectedOutput": "01 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #2",
"input": "00 00",
"expectedOutput": "01 01 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #3",
"input": "00 11 00",
"expectedOutput": "01 02 11 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #4",
"input": "11 22 00 33",
"expectedOutput": "03 11 22 02 33",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #5",
"input": "11 22 33 44",
"expectedOutput": "05 11 22 33 44",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #6",
"input": "11 00 00 00",
"expectedOutput": "02 11 01 01 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #7",
"input": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"expectedOutput": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #8",
"input": "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"expectedOutput": "01 ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #9",
"input": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff",
"expectedOutput": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe 02 ff",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #10",
"input": "02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00",
"expectedOutput": "ff 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 01 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "To COBS Example #11",
"input": "03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00 01",
"expectedOutput": "fe 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 02 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "To COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #1",
"input": "01 01",
"expectedOutput": "00",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #2",
"input": "01 01 01",
"expectedOutput": "00 00",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #3",
"input": "01 02 11 01",
"expectedOutput": "00 11 00",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #4",
"input": "03 11 22 02 33",
"expectedOutput": "11 22 00 33",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #5",
"input": "05 11 22 33 44",
"expectedOutput": "11 22 33 44",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #6",
"input": "02 11 01 01 01",
"expectedOutput": "11 00 00 00",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #7",
"input": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"expectedOutput": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #8",
"input": "01 ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"expectedOutput": "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #9",
"input": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe 02 ff",
"expectedOutput": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #10",
"input": "ff 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 01 01",
"expectedOutput": "02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Example #11",
"input": "fe 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 02 01",
"expectedOutput": "03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00 01",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
},
{
"op": "To Hex",
"args": ["Space"]
}
]
},
{
"name": "From COBS Invalid input #1",
"input": "00 48 45 4C 4C 4F",
"expectedOutput": "Could not decode from COBS: payload must not contain a 0x00 byte",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
}
],
},
{
"name": "From COBS Invalid input #2",
"input": "48 45 00 4C 4C 4F",
"expectedOutput": "Could not decode from COBS: payload must not contain a 0x00 byte",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
}
],
},
{
"name": "From COBS Invalid input #3",
"input": "48 45 4C 4C 4F 00",
"expectedOutput": "Could not decode from COBS: payload must not contain a 0x00 byte",
"recipeConfig": [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "From COBS",
"args": []
}
],
},
]);

View File

@ -0,0 +1,78 @@
/**
* Extended GCD tests.
*
* @author p-leriche [philip.leriche@cantab.net]
*
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Extended GCD: coprime numbers (3, 11)",
input: "",
expectedOutput: "gcd: 1\n\nBezout coefficients:\nx = 4\ny = -1\n\n",
recipeConfig: [
{
op: "Extended GCD",
args: ["3", "11"],
},
],
},
{
name: "Extended GCD: non-coprime numbers (240, 46)",
input: "",
expectedOutput: "gcd: 2\n\nBezout coefficients:\nx = -9\ny = 47\n\n",
recipeConfig: [
{
op: "Extended GCD",
args: ["240", "46"],
},
],
},
{
name: "Extended GCD: with zero (17, 0)",
input: "",
expectedOutput: "gcd: 17\n\nBezout coefficients:\nx = 1\ny = 0\n\n",
recipeConfig: [
{
op: "Extended GCD",
args: ["17", "0"],
},
],
},
{
name: "Extended GCD: hexadecimal input (0xFF, 0x11)",
input: "",
expectedOutput: "gcd: 17\n\nBezout coefficients:\nx = 0\ny = 1\n\n",
recipeConfig: [
{
op: "Extended GCD",
args: ["0xFF", "0x11"],
},
],
},
{
name: "Extended GCD: using input field for value a",
input: "42",
expectedOutput: "gcd: 7\n\nBezout coefficients:\nx = 1\ny = -1\n\n",
recipeConfig: [
{
op: "Extended GCD",
args: ["", "35"],
},
],
},
{
name: "Extended GCD: large numbers",
input: "",
expectedOutput: "gcd: 2\n\nBezout coefficients:\nx = 12703973750415151\ny = -1577756566311408967124629843\n\n",
recipeConfig: [
{
op: "Extended GCD",
args: ["123456789012345678901234567890", "994064509324197316"],
},
],
},
]);

View File

@ -0,0 +1,126 @@
/**
* To/From HTML Entity tests.
*
* @author roberson-io [michaelroberson@gmail.com]
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "To HTML Entity: named",
input: "<a href='#'>\"&\"</a>",
expectedOutput: "&lt;a href&equals;&apos;&num;&apos;&gt;&quot;&amp;&quot;&lt;&sol;a&gt;",
recipeConfig: [
{ op: "To HTML Entity", args: [false, "Named entities"] },
],
},
{
// Regression: this value was "&rlhar;;" (stray trailing semicolon) in the
// original byteToEntity table. See issue #2645.
name: "To HTML Entity: malformed value fixed (U+21CC)",
input: "⇌",
expectedOutput: "&rlhar;",
recipeConfig: [
{ op: "To HTML Entity", args: [false, "Named entities"] },
],
},
{
// Regression: U+03F5 emitted "&epsi;," before; the spec name is &epsiv;
// (&epsi; is U+03B5). See issue #2645.
name: "To HTML Entity: spec-conformant name (U+03F5)",
input: "ϵ",
expectedOutput: "&epsiv;",
recipeConfig: [
{ op: "To HTML Entity", args: [false, "Named entities"] },
],
},
{
// Regression: the OHM SIGN previously emitted the non-conformant &ohm;
// (spec &ohm; is U+03A9). It now encodes numerically.
name: "To HTML Entity: non-conformant name dropped (U+2126 ohm sign)",
input: "Ω",
expectedOutput: "&#8486;",
recipeConfig: [
{ op: "To HTML Entity", args: [false, "Named entities"] },
],
},
{
// The FULL BLOCK previously decoded to &block; but never encoded to it.
// Generating both tables from the spec fixes this asymmetry.
name: "To HTML Entity: encode/decode symmetry (U+2588 block)",
input: "█",
expectedOutput: "&block;",
recipeConfig: [
{ op: "To HTML Entity", args: [false, "Named entities"] },
],
},
{
name: "To HTML Entity: numeric, convert all",
input: "A<",
expectedOutput: "&#65;&#60;",
recipeConfig: [
{ op: "To HTML Entity", args: [true, "Numeric entities"] },
],
},
{
name: "To HTML Entity: hex, convert all",
input: "A<",
expectedOutput: "&#x41;&#x3c;",
recipeConfig: [
{ op: "To HTML Entity", args: [true, "Hex entities"] },
],
},
{
name: "From HTML Entity: named",
input: "&lt;a href=&apos;#&apos;&gt;&quot;&amp;&quot;&lt;/a&gt;",
expectedOutput: "<a href='#'>\"&\"</a>",
recipeConfig: [
{ op: "From HTML Entity", args: [] },
],
},
{
// Legacy/alias names still decode, now to the spec code point.
name: "From HTML Entity: alias to spec code point (&ohm;)",
input: "&ohm;",
expectedOutput: "Ω",
recipeConfig: [
{ op: "From HTML Entity", args: [] },
],
},
{
name: "From HTML Entity: decimal numeric entity",
input: "&#65;",
expectedOutput: "A",
recipeConfig: [
{ op: "From HTML Entity", args: [] },
],
},
{
name: "From HTML Entity: hex numeric entity",
input: "&#x41;",
expectedOutput: "A",
recipeConfig: [
{ op: "From HTML Entity", args: [] },
],
},
{
// Unknown entities are passed through unchanged.
name: "From HTML Entity: invalid entity passed through",
input: "a &notreal; b",
expectedOutput: "a &notreal; b",
recipeConfig: [
{ op: "From HTML Entity", args: [] },
],
},
{
name: "HTML Entity: round-trips through both operations",
input: "Hello <World> & \"friends\" — ⇌ █",
expectedOutput: "Hello <World> & \"friends\" — ⇌ █",
recipeConfig: [
{ op: "To HTML Entity", args: [true, "Named entities"] },
{ op: "From HTML Entity", args: [] },
],
},
]);

View File

@ -0,0 +1,78 @@
/**
* Modular Inverse tests.
*
* @author p-leriche [philip.leriche@cantab.net]
*
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Modular Inverse: basic example (3 mod 11)",
input: "",
expectedOutput: "4",
recipeConfig: [
{
op: "Modular Inverse",
args: ["3", "11"],
},
],
},
{
name: "Modular Inverse: another coprime pair (7 mod 26)",
input: "",
expectedOutput: "15",
recipeConfig: [
{
op: "Modular Inverse",
args: ["7", "26"],
},
],
},
{
name: "Modular Inverse: hexadecimal input (0x10 mod 0x11)",
input: "",
expectedOutput: "16",
recipeConfig: [
{
op: "Modular Inverse",
args: ["0x10", "0x11"],
},
],
},
{
name: "Modular Inverse: using input field for value",
input: "5",
expectedOutput: "21",
recipeConfig: [
{
op: "Modular Inverse",
args: ["", "26"],
},
],
},
{
name: "Modular Inverse: using input field for modulus",
input: "17",
expectedOutput: "7",
recipeConfig: [
{
op: "Modular Inverse",
args: ["5", ""],
},
],
},
{
name: "Modular Inverse: large number (RSA-like)",
input: "",
expectedOutput: "934281398294",
recipeConfig: [
{
op: "Modular Inverse",
args: ["65537", "9999999999999"],
},
],
},
]);