Add Enigma operation
This commit is contained in:
parent
c82971f8db
commit
088864fd9c
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ docs/*
|
||||
!docs/*.conf.json
|
||||
!docs/*.ico
|
||||
.vscode
|
||||
.*.swp
|
||||
src/core/config/modules/*
|
||||
src/core/config/OperationConfig.json
|
||||
src/core/operations/index.mjs
|
||||
|
@ -102,7 +102,8 @@
|
||||
"JWT Decode",
|
||||
"Citrix CTX1 Encode",
|
||||
"Citrix CTX1 Decode",
|
||||
"Pseudo-Random Number Generator"
|
||||
"Pseudo-Random Number Generator",
|
||||
"Enigma"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
349
src/core/lib/Enigma.mjs
Normal file
349
src/core/lib/Enigma.mjs
Normal file
@ -0,0 +1,349 @@
|
||||
/**
|
||||
* Emulation of the Enigma machine.
|
||||
*
|
||||
* @author s2224834
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
* Provided default Enigma rotor set.
|
||||
* These are specified as a list of mappings from the letters A through Z in order, optionally
|
||||
* followed by < and a list of letters at which the rotor steps.
|
||||
*/
|
||||
export const ROTORS = [
|
||||
{name: "I", value: "EKMFLGDQVZNTOWYHXUSPAIBRCJ<R"},
|
||||
{name: "II", value: "AJDKSIRUXBLHWTMCQGZNPYFVOE<F"},
|
||||
{name: "III", value: "BDFHJLCPRTXVZNYEIWGAKMUSQO<W"},
|
||||
{name: "IV", value: "ESOVPZJAYQUIRHXLNFTGKDCMWB<K"},
|
||||
{name: "V", value: "VZBRGITYUPSDNHLXAWMJQOFECK<A"},
|
||||
{name: "VI", value: "JPGVOUMFYQBENHZRDKASXLICTW<AN"},
|
||||
{name: "VII", value: "NZJHGRCXMYSWBOUFAIVLPEKQDT<AN"},
|
||||
{name: "VIII", value: "FKQHTLXOCBJSPDZRAMEWNIUYGV<AN"},
|
||||
{name: "Beta", value: "LEYJVCNIXWPBQMDRTAKZGFUHOS"},
|
||||
{name: "Gamma", value: "FSOKANUERHMBTIYCWLQPZXVGJD"},
|
||||
];
|
||||
|
||||
export const ROTORS_OPTIONAL = [].concat(ROTORS).concat([
|
||||
{name: "None", value: ""},
|
||||
]);
|
||||
|
||||
/**
|
||||
* Provided default Enigma reflector set.
|
||||
* These are specified as 13 space-separated transposed pairs covering every letter.
|
||||
*/
|
||||
export const REFLECTORS = [
|
||||
{name: "B", value: "AY BR CU DH EQ FS GL IP JX KN MO TZ VW"},
|
||||
{name: "C", value: "AF BV CP DJ EI GO HY KR LZ MX NW TQ SU"},
|
||||
{name: "B Thin", value: "AE BN CK DQ FU GY HW IJ LO MP RX SZ TV"},
|
||||
{name: "C Thin", value: "AR BD CO EJ FN GT HK IV LM PW QZ SX UY"},
|
||||
];
|
||||
|
||||
export const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
||||
|
||||
/**
|
||||
* Map a letter to a number in 0..25.
|
||||
*
|
||||
* @param {char} c
|
||||
* @param {boolean} permissive - Case insensitive; don't throw errors on other chars.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function a2i(c, permissive=false) {
|
||||
const i = Utils.ord(c);
|
||||
if (i >= 65 && i <= 90) {
|
||||
return i - 65;
|
||||
}
|
||||
if (permissive) {
|
||||
// Allow case insensitivity
|
||||
if (i >= 97 && i <= 122) {
|
||||
return i - 97;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
throw new OperationError("a2i called on non-uppercase ASCII character");
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a number in 0..25 to a letter.
|
||||
*
|
||||
* @param {number} i
|
||||
* @returns {char}
|
||||
*/
|
||||
export function i2a(i) {
|
||||
if (i >= 0 && i < 26) {
|
||||
return Utils.chr(i+65);
|
||||
}
|
||||
throw new OperationError("i2a called on value outside 0..25");
|
||||
}
|
||||
|
||||
/**
|
||||
* A rotor in the Enigma machine.
|
||||
*/
|
||||
export class Rotor {
|
||||
/**
|
||||
* Rotor constructor.
|
||||
*
|
||||
* @param {string} wiring - A 26 character string of the wiring order.
|
||||
* @param {string} steps - A 0..26 character string of stepping points.
|
||||
* @param {char} ringSetting - The ring setting.
|
||||
* @param {char} initialPosition - The initial position of the rotor.
|
||||
*/
|
||||
constructor(wiring, steps, ringSetting, initialPosition) {
|
||||
if (!/^[A-Z]{26}$/.test(wiring)) {
|
||||
throw new OperationError("Rotor wiring must be 26 unique uppercase letters");
|
||||
}
|
||||
if (!/^[A-Z]{0,26}$/.test(steps)) {
|
||||
throw new OperationError("Rotor steps must be 0-26 unique uppercase letters");
|
||||
}
|
||||
if (!/^[A-Z]$/.test(ringSetting)) {
|
||||
throw new OperationError("Rotor ring setting must be exactly one uppercase letter");
|
||||
}
|
||||
if (!/^[A-Z]$/.test(initialPosition)) {
|
||||
throw new OperationError("Rotor initial position must be exactly one uppercase letter");
|
||||
}
|
||||
this.map = {};
|
||||
this.revMap = {};
|
||||
for (let i=0; i<LETTERS.length; i++) {
|
||||
const a = a2i(LETTERS[i]);
|
||||
const b = a2i(wiring[i]);
|
||||
this.map[a] = b;
|
||||
this.revMap[b] = a;
|
||||
}
|
||||
if (Object.keys(this.revMap).length !== LETTERS.length) {
|
||||
throw new OperationError("Rotor wiring must have each letter exactly once");
|
||||
}
|
||||
const rs = a2i(ringSetting);
|
||||
this.steps = new Set();
|
||||
for (const x of steps) {
|
||||
this.steps.add(Utils.mod(a2i(x) - rs, 26));
|
||||
}
|
||||
if (this.steps.size !== steps.length) {
|
||||
// This isn't strictly fatal, but it's probably a mistake
|
||||
throw new OperationError("Rotor steps must be unique");
|
||||
}
|
||||
this.pos = Utils.mod(a2i(initialPosition) - rs, 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the rotor forward by one.
|
||||
*/
|
||||
step() {
|
||||
this.pos = Utils.mod(this.pos + 1, 26);
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this rotor forwards.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(c) {
|
||||
return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this rotor backwards.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
revTransform(c) {
|
||||
return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for plugboard and reflector (since these do effectively the same
|
||||
* thing).
|
||||
*/
|
||||
class PairMapBase {
|
||||
/**
|
||||
* PairMapBase constructor.
|
||||
*
|
||||
* @param {string} pairs - A whitespace separated string of letter pairs to swap.
|
||||
* @param {string} [name='PairMapBase'] - For errors, the name of this object.
|
||||
*/
|
||||
constructor(pairs, name="PairMapBase") {
|
||||
// I've chosen to make whitespace significant here to make a) code and
|
||||
// b) inputs easier to read
|
||||
this.map = {};
|
||||
if (pairs === "") {
|
||||
return;
|
||||
}
|
||||
pairs.split(/\s+/).forEach(pair => {
|
||||
if (!/^[A-Z]{2}$/.test(pair)) {
|
||||
throw new OperationError(name + " must be a whitespace-separated list of uppercase letter pairs");
|
||||
}
|
||||
const a = a2i(pair[0]), b = a2i(pair[1]);
|
||||
if (a === b) {
|
||||
throw new OperationError(`${name}: cannot connect ${pair[0]} to itself`);
|
||||
}
|
||||
if (this.map.hasOwnProperty(a)) {
|
||||
throw new OperationError(`${name} connects ${pair[0]} more than once`);
|
||||
}
|
||||
if (this.map.hasOwnProperty(b)) {
|
||||
throw new OperationError(`${name} connects ${pair[1]} more than once`);
|
||||
}
|
||||
this.map[a] = b;
|
||||
this.map[b] = a;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a character through this object.
|
||||
* Returns other characters unchanged.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
transform(c) {
|
||||
if (!this.map.hasOwnProperty(c)) {
|
||||
return c;
|
||||
}
|
||||
return this.map[c];
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for transform, to allow interchangeable use with rotors.
|
||||
*
|
||||
* @param {number} c - The character.
|
||||
* @returns {number}
|
||||
*/
|
||||
revTransform(c) {
|
||||
return this.transform(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflector. PairMapBase but requires that all characters are accounted for.
|
||||
*/
|
||||
export class Reflector extends PairMapBase {
|
||||
/**
|
||||
* Reflector constructor. See PairMapBase.
|
||||
* Additional restriction: every character must be accounted for.
|
||||
*/
|
||||
constructor(pairs) {
|
||||
super(pairs, "Reflector");
|
||||
const s = Object.keys(this.map).length;
|
||||
if (s !== 26) {
|
||||
throw new OperationError("Reflector must have exactly 13 pairs covering every letter");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugboard. Unmodified PairMapBase.
|
||||
*/
|
||||
export class Plugboard extends PairMapBase {
|
||||
/**
|
||||
* Plugboard constructor. See PairMapbase.
|
||||
*/
|
||||
constructor(pairs) {
|
||||
super(pairs, "Plugboard");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for the Enigma machine itself. Holds rotors, a reflector, and a plugboard.
|
||||
*/
|
||||
export class EnigmaBase {
|
||||
/**
|
||||
* EnigmaBase constructor.
|
||||
*
|
||||
* @param {Object[]} rotors - List of Rotors.
|
||||
* @param {Object} reflector - A Reflector.
|
||||
* @param {Plugboard} plugboard - A Plugboard.
|
||||
*/
|
||||
constructor(rotors, reflector, plugboard) {
|
||||
this.rotors = rotors;
|
||||
this.rotorsRev = [].concat(rotors).reverse();
|
||||
this.reflector = reflector;
|
||||
this.plugboard = plugboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the rotors forward by one.
|
||||
*
|
||||
* This happens before the output character is generated.
|
||||
*
|
||||
* Note that rotor 4, if it's there, never steps.
|
||||
*
|
||||
* Why is all the logic in EnigmaBase and not a nice neat method on
|
||||
* Rotor that knows when it should advance the next item?
|
||||
* Because the double stepping anomaly is a thing. tl;dr if the left rotor
|
||||
* should step the next time the middle rotor steps, the middle rotor will
|
||||
* immediately step.
|
||||
*/
|
||||
step() {
|
||||
const r0 = this.rotors[0];
|
||||
const r1 = this.rotors[1];
|
||||
r0.step();
|
||||
// The second test here is the double-stepping anomaly
|
||||
if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) {
|
||||
r1.step();
|
||||
if (r1.steps.has(r1.pos)) {
|
||||
const r2 = this.rotors[2];
|
||||
r2.step();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt (or decrypt) some data.
|
||||
* Takes an arbitrary string and runs the Engima machine on that data from
|
||||
* *its current state*, and outputs the result. Non-alphabetic characters
|
||||
* are returned unchanged.
|
||||
*
|
||||
* @param {string} input - Data to encrypt.
|
||||
* @returns {string}
|
||||
*/
|
||||
crypt(input) {
|
||||
let result = "";
|
||||
for (const c of input) {
|
||||
let letter = a2i(c, true);
|
||||
if (letter === -1) {
|
||||
result += c;
|
||||
continue;
|
||||
}
|
||||
// First, step the rotors forward.
|
||||
this.step();
|
||||
// Now, run through the plugboard.
|
||||
letter = this.plugboard.transform(letter);
|
||||
// Then through each wheel in sequence, through the reflector, and
|
||||
// backwards through the wheels again.
|
||||
for (const rotor of this.rotors) {
|
||||
letter = rotor.transform(letter);
|
||||
}
|
||||
letter = this.reflector.transform(letter);
|
||||
for (const rotor of this.rotorsRev) {
|
||||
letter = rotor.revTransform(letter);
|
||||
}
|
||||
// Finally, back through the plugboard.
|
||||
letter = this.plugboard.revTransform(letter);
|
||||
result += i2a(letter);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Enigma machine itself. Holds 3-4 rotors, a reflector, and a plugboard.
|
||||
*/
|
||||
export class EnigmaMachine extends EnigmaBase {
|
||||
/**
|
||||
* EnigmaMachine constructor.
|
||||
*
|
||||
* @param {Object[]} rotors - List of Rotors.
|
||||
* @param {Object} reflector - A Reflector.
|
||||
* @param {Plugboard} plugboard - A Plugboard.
|
||||
*/
|
||||
constructor(rotors, reflector, plugboard) {
|
||||
super(rotors, reflector, plugboard);
|
||||
if (rotors.length !== 3 && rotors.length !== 4) {
|
||||
throw new OperationError("Enigma must have 3 or 4 rotors");
|
||||
}
|
||||
}
|
||||
}
|
200
src/core/operations/Enigma.mjs
Normal file
200
src/core/operations/Enigma.mjs
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Emulation of the Enigma machine.
|
||||
*
|
||||
* @author s2224834
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import * as Enigma from "../lib/Enigma";
|
||||
|
||||
/**
|
||||
* Enigma operation
|
||||
*/
|
||||
class EnigmaOp extends Operation {
|
||||
/**
|
||||
* Enigma constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Enigma";
|
||||
this.module = "Default";
|
||||
this.description = "Encipher/decipher with the WW2 Enigma machine.<br><br>The standard set of German military rotors and reflectors are provided. To configure the plugboard, enter a string of connected pairs of letters, e.g. <code>AB CD EF</code> connects A to B, C to D, and E to F. This is also used to create your own reflectors. To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by <code><</code> then a list of stepping points.<br>This is deliberately fairly permissive with rotor placements etc compared to a real Enigma (on which, for example, a four-rotor Enigma uses the thin reflectors and the beta or gamma rotor in the 4th slot).";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Enigma_machine";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "1st (right-hand) rotor",
|
||||
type: "editableOption",
|
||||
value: Enigma.ROTORS,
|
||||
// Default config is the rotors I-III *left to right*
|
||||
defaultIndex: 2
|
||||
},
|
||||
{
|
||||
name: "1st rotor ring setting",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "1st rotor initial value",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "2nd rotor",
|
||||
type: "editableOption",
|
||||
value: Enigma.ROTORS,
|
||||
defaultIndex: 1
|
||||
},
|
||||
{
|
||||
name: "2nd rotor ring setting",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "2nd rotor initial value",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "3rd rotor",
|
||||
type: "editableOption",
|
||||
value: Enigma.ROTORS,
|
||||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "3rd rotor ring setting",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "3rd rotor initial value",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "4th rotor",
|
||||
type: "editableOption",
|
||||
value: Enigma.ROTORS_OPTIONAL,
|
||||
defaultIndex: 10
|
||||
},
|
||||
{
|
||||
name: "4th rotor initial value",
|
||||
type: "option",
|
||||
value: Enigma.LETTERS
|
||||
},
|
||||
{
|
||||
name: "Reflector",
|
||||
type: "editableOption",
|
||||
value: Enigma.REFLECTORS
|
||||
},
|
||||
{
|
||||
name: "Plugboard",
|
||||
type: "string",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
name: "Strict output",
|
||||
hint: "Remove non-alphabet letters and group output",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper - for ease of use rotors are specified as a single string; this
|
||||
* method breaks the spec string into wiring and steps parts.
|
||||
*
|
||||
* @param {string} rotor - Rotor specification string.
|
||||
* @param {number} i - For error messages, the number of this rotor.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
parseRotorStr(rotor, i) {
|
||||
if (rotor === "") {
|
||||
throw new OperationError(`Rotor ${i} must be provided.`);
|
||||
}
|
||||
if (!rotor.includes("<")) {
|
||||
return [rotor, ""];
|
||||
}
|
||||
return rotor.split("<", 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [
|
||||
rotor1str, rotor1ring, rotor1pos,
|
||||
rotor2str, rotor2ring, rotor2pos,
|
||||
rotor3str, rotor3ring, rotor3pos,
|
||||
rotor4str, rotor4pos,
|
||||
reflectorstr, plugboardstr,
|
||||
removeOther
|
||||
] = args;
|
||||
const rotors = [];
|
||||
const [rotor1wiring, rotor1steps] = this.parseRotorStr(rotor1str, 1);
|
||||
rotors.push(new Enigma.Rotor(rotor1wiring, rotor1steps, rotor1ring, rotor1pos));
|
||||
const [rotor2wiring, rotor2steps] = this.parseRotorStr(rotor2str, 2);
|
||||
rotors.push(new Enigma.Rotor(rotor2wiring, rotor2steps, rotor2ring, rotor2pos));
|
||||
const [rotor3wiring, rotor3steps] = this.parseRotorStr(rotor3str, 3);
|
||||
rotors.push(new Enigma.Rotor(rotor3wiring, rotor3steps, rotor3ring, rotor3pos));
|
||||
if (rotor4str !== "") {
|
||||
// Fourth rotor doesn't have a ring setting - A is equivalent to no setting
|
||||
const [rotor4wiring, rotor4steps] = this.parseRotorStr(rotor4str, 4);
|
||||
rotors.push(new Enigma.Rotor(rotor4wiring, rotor4steps, "A", rotor4pos));
|
||||
}
|
||||
const reflector = new Enigma.Reflector(reflectorstr);
|
||||
const plugboard = new Enigma.Plugboard(plugboardstr);
|
||||
if (removeOther) {
|
||||
input = input.replace(/[^A-Za-z]/g, "");
|
||||
}
|
||||
const enigma = new Enigma.EnigmaMachine(rotors, reflector, plugboard);
|
||||
let result = enigma.crypt(input);
|
||||
if (removeOther) {
|
||||
// Five character cipher groups is traditional
|
||||
result = result.replace(/([A-Z]{5})(?!$)/g, "$1 ");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Enigma
|
||||
* This is only possible if we're passing through non-alphabet characters.
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
if (args[13] === false) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Enigma in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
if (args[13] === false) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default EnigmaOp;
|
@ -82,6 +82,7 @@ import "./tests/TranslateDateTimeFormat";
|
||||
import "./tests/Magic";
|
||||
import "./tests/ParseTLV";
|
||||
import "./tests/Media";
|
||||
import "./tests/Enigma";
|
||||
|
||||
// Cannot test operations that use the File type yet
|
||||
//import "./tests/SplitColourChannels";
|
||||
|
518
tests/operations/tests/Enigma.mjs
Normal file
518
tests/operations/tests/Enigma.mjs
Normal file
@ -0,0 +1,518 @@
|
||||
/**
|
||||
* Enigma machine tests.
|
||||
* @author s2224834
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
// Simplest test: A single keypress in the default position on a basic
|
||||
// Enigma.
|
||||
name: "Enigma: basic wiring",
|
||||
input: "G",
|
||||
expectedOutput: "P",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
// Note: start on Z because it steps when the key is pressed
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Rotor position test: single keypress, basic rotors, random start
|
||||
// positions, no advancement of other rotors.
|
||||
name: "Enigma: rotor position",
|
||||
input: "A",
|
||||
expectedOutput: "T",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "W",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "F",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "N",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Rotor ring setting test: single keypress, basic rotors, one rotor
|
||||
// ring offset by one, basic start position, no advancement of other
|
||||
// rotors.
|
||||
name: "Enigma: rotor ring setting",
|
||||
input: "A",
|
||||
expectedOutput: "O",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "B", "Z",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Rotor ring setting test: single keypress, basic rotors, random ring
|
||||
// settings, basic start position, no advancement of other rotors.
|
||||
name: "Enigma: rotor ring setting 2",
|
||||
input: "A",
|
||||
expectedOutput: "F",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "W", "Z",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "F", "A",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "N", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Stepping: basic configuration, enough input to cause middle rotor to
|
||||
// step
|
||||
name: "Enigma: stepping",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "UBDZG OWCXL TKSBT MCDLP BMUQO F",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Ensure that we can decrypt an encrypted message.
|
||||
name: "Enigma: reflectivity",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Stepping: with rotors set so we're about to trigger the double step
|
||||
// anomaly
|
||||
name: "Enigma: double step anomaly",
|
||||
input: "AAAAA",
|
||||
expectedOutput: "EQIBM",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "U",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "D",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Stepping: with rotors set so we're about to trigger the double step
|
||||
// anomaly
|
||||
name: "Enigma: double step anomaly 2",
|
||||
input: "AAAA",
|
||||
expectedOutput: "BRNC",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "U",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "E",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Stepping: with rotors set so we're about to trigger the double step
|
||||
// anomaly
|
||||
name: "Enigma: double step anomaly 3",
|
||||
input: "AAAAA AAA",
|
||||
expectedOutput: "ZEEQI BMG",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "S",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "D",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Stepping: with a ring setting
|
||||
name: "Enigma: ring setting stepping",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "PBMFE BOUBD ZGOWC XLTKS BTXSH I",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "H", "Z",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Stepping: with a ring setting and double step
|
||||
name: "Enigma: ring setting double step",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "TEVFK UTIIW EDWVI JPMVP GDEZS P",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "H", "F",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "C", "D",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "Q", "A",
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Four-rotor Enigma, random settings, no plugboard
|
||||
name: "Enigma: four rotor",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "GZXGX QUSUW JPWVI GVBTU DQZNZ J",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "D", "Q",
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "P", "F",
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "O", "E",
|
||||
"LEYJVCNIXWPBQMDRTAKZGFUHOS", "X", // Beta
|
||||
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Four-rotor Enigma, different wheel set, no plugboard
|
||||
name: "Enigma: four rotor 2",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "HZJLP IKWBZ XNCWF FIHWL EROOZ C",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "W", "U", // IV
|
||||
"VZBRGITYUPSDNHLXAWMJQOFECK<A", "M", "G", // V
|
||||
"JPGVOUMFYQBENHZRDKASXLICTW<AN", "A", "J", // VI
|
||||
"FSOKANUERHMBTIYCWLQPZXVGJD", "L", // Gamma
|
||||
"AR BD CO EJ FN GT HK IV LM PW QZ SX UY", // C thin
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Four-rotor Enigma, different wheel set, random plugboard
|
||||
name: "Enigma: plugboard",
|
||||
input: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
expectedOutput: "GHLIM OJIUW DKLWM JGNJK DYJVD K",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "U", "Z", // VIII
|
||||
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "O", "O", // IV
|
||||
"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN", "I", "V", // VII
|
||||
"FSOKANUERHMBTIYCWLQPZXVGJD", "I", // Gamma
|
||||
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
|
||||
"WN MJ LX YB FP QD US IH CE GR"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Decryption test on above input
|
||||
name: "Enigma: decryption",
|
||||
input: "GHLIM OJIUW DKLWM JGNJK DYJVD K",
|
||||
expectedOutput: "AAAAA AAAAA AAAAA AAAAA AAAAA A",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "U", "Z", // VIII
|
||||
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "O", "O", // IV
|
||||
"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN", "I", "V", // VII
|
||||
"FSOKANUERHMBTIYCWLQPZXVGJD", "I", // Gamma
|
||||
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
|
||||
"WN MJ LX YB FP QD US IH CE GR"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Non-alphabet characters drop test
|
||||
name: "Enigma: non-alphabet drop",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "ILBDA AMTAZ MORNZ DDIOT U",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
"", true
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Non-alphabet characters passthrough test
|
||||
name: "Enigma: non-alphabet passthrough",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "ILBDA, AMTAZ. MORN ZD D IOTU.",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
"", false
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: rotor validation 1",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Rotor wiring must be 26 unique uppercase letters",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQ", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: rotor validation 2",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Rotor wiring must be 26 unique uppercase letters",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQo", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: rotor validation 3",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Rotor wiring must have each letter exactly once",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQA", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: rotor validation 4",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Rotor steps must be unique",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<RR", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: rotor validation 5",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Rotor steps must be 0-26 unique uppercase letters",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<a", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// The ring setting and positions are dropdowns in the interface so not
|
||||
// gonna bother testing them
|
||||
{
|
||||
name: "Enigma: reflector validation 1",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Reflector must have exactly 13 pairs covering every letter",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY BR CU DH EQ FS GL IP JX KN MO", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: reflector validation 2",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Reflector: cannot connect A to itself",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AA BR CU DH EQ FS GL IP JX KN MO TZ", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: reflector validation 3",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Reflector connects A more than once",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AY AR CU DH EQ FS GL IP JX KN MO TZ", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Enigma: reflector validation 4",
|
||||
input: "Hello, world. This is a test.",
|
||||
expectedOutput: "Reflector must be a whitespace-separated list of uppercase letter pairs",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Enigma",
|
||||
"args": [
|
||||
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
|
||||
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
|
||||
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
|
||||
"", "A",
|
||||
"AYBR CU DH EQ FS GL IP JX KN MO TZ", // B
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
]);
|
Loading…
Reference in New Issue
Block a user