@ondoher/enigma 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/enigma/Encoder.js +102 -0
- package/lib/enigma/Enigma.js +246 -0
- package/lib/enigma/EntryDisc.js +36 -0
- package/lib/enigma/Inventory.js +115 -0
- package/lib/enigma/PlugBoard.js +74 -0
- package/lib/enigma/Reflector.js +51 -0
- package/lib/enigma/Rotor.js +170 -0
- package/lib/enigma/consts.js +1 -0
- package/lib/enigma/index.js +7 -0
- package/lib/enigma/standardInventory.js +95 -0
- package/lib/enigma/tests/EnigmaData.js +236 -0
- package/lib/enigma/tests/EnigmaSpec.js +123 -0
- package/lib/enigma/tests/PlugBoardData.js +4 -0
- package/lib/enigma/tests/PlugBoardSpec.js +46 -0
- package/lib/enigma/tests/RotorData.js +13 -0
- package/lib/enigma/tests/RotorSpec.js +263 -0
- package/lib/generator/Generator.js +452 -0
- package/lib/generator/hamlet.js +1675 -0
- package/lib/generator/index.js +1 -0
- package/lib/index.js +2 -0
- package/package.json +16 -0
- package/spec/helpers/consoleReporter.js +11 -0
- package/spec/support/jasmine.json +14 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import Encoder from "./Encoder.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create an instance of this class to construct a Rotor object. The Rotor class
|
|
5
|
+
* encapsulates many of the peculiar behaviors of the Enigma. All connector
|
|
6
|
+
* values here are specified in physical space.
|
|
7
|
+
*/
|
|
8
|
+
export default class Rotor extends Encoder {
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
|
|
12
|
+
- **name**
|
|
13
|
+
- **settings**
|
|
14
|
+
- _alphabet_ (optional)
|
|
15
|
+
- _map_
|
|
16
|
+
- _ringSetting_
|
|
17
|
+
- _turnovers_
|
|
18
|
+
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This is the constructor for the rotor.
|
|
27
|
+
*
|
|
28
|
+
* @param {String} name the name of the rotor; under normal circumstances
|
|
29
|
+
* this will be the string 'rotor-' plus the standard name for the rotor,
|
|
30
|
+
* for example 'rotor-IV'
|
|
31
|
+
* @param {Object} settings an object that contains the various options that
|
|
32
|
+
* define the the rotor and how it is configured.
|
|
33
|
+
* @property [alphabet] set this to a string of letters that are an
|
|
34
|
+
* alternative to the standard A-Z. Defaults to A-Z
|
|
35
|
+
* @property {String} map a string that defines the mapping between the
|
|
36
|
+
* input and output connectors. The index into the string is the input
|
|
37
|
+
* connector and the value of this string at that index is the output
|
|
38
|
+
* connector. For example 'EKMFLGDQVZNTOWYHXUSPAIBRCJ', which is the map
|
|
39
|
+
* for standard rotor I.
|
|
40
|
+
* @property {Number} [ringSetting] a number that specifies how far forward
|
|
41
|
+
* to offset the outer ring relative to the internal wiring.
|
|
42
|
+
* @property {String} turnovers a string that specifies the relative
|
|
43
|
+
* location of where on the rotor turnover will occur. The value here is the
|
|
44
|
+
* rotation value would be displayed in the window when turnover happens,
|
|
45
|
+
* expressed as a character. The standard rotors VI-VIII, available in the
|
|
46
|
+
* later model M3 had two turnover locations, M and Z. Pass an empty string
|
|
47
|
+
* when the rotor does not rotate during stepping
|
|
48
|
+
*/
|
|
49
|
+
constructor(name, settings) {
|
|
50
|
+
super(name, settings);
|
|
51
|
+
var { map, turnovers = '', ringSetting = 0} = settings
|
|
52
|
+
|
|
53
|
+
this.map = [...map];
|
|
54
|
+
this.rightMap = this.makeMap(map);
|
|
55
|
+
this.leftMap = this.makeReverseMap(this.rightMap);
|
|
56
|
+
this.length = map.length;
|
|
57
|
+
this.ringOffset = ringSetting;
|
|
58
|
+
|
|
59
|
+
this.turnoverLookup = this.makeMap(turnovers);
|
|
60
|
+
this.offset = 0;
|
|
61
|
+
if (turnovers === '') this.fixed = true;
|
|
62
|
+
this.turnovers = this.turnoverLookup.reduce(function(turnovers, turnover) {
|
|
63
|
+
turnovers[turnover] = true;
|
|
64
|
+
return turnovers;
|
|
65
|
+
}.bind(this), {});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Call this method to select the initial rotation of the rotor. This is a
|
|
70
|
+
* letter offset from the logical 0 connector. The initial rotation will
|
|
71
|
+
* also take into account the ring setting
|
|
72
|
+
*
|
|
73
|
+
* @param {String} connector This is a letter value that corresponds to what
|
|
74
|
+
* would appear in the rotation window. This value will be adjusted for the
|
|
75
|
+
* ring setting.
|
|
76
|
+
*/
|
|
77
|
+
setStartPosition(connector) {
|
|
78
|
+
var pos = this.alphabet.indexOf(connector);
|
|
79
|
+
this.offset = this.normalize(pos - this.ringOffset);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Call this method to map an input connector to an output connector when
|
|
84
|
+
* the signal is moving in the given direction. The input connector
|
|
85
|
+
* represents a physical location in real space. To get the logical
|
|
86
|
+
* connector for the rotor's zero point we need to adjust the connector
|
|
87
|
+
* number for the current rotation.
|
|
88
|
+
*
|
|
89
|
+
* @param {String} direction either right for moving towards the reflector or
|
|
90
|
+
* left if moving back
|
|
91
|
+
* @param {Number} input the connector in physical space
|
|
92
|
+
*
|
|
93
|
+
* @returns {Number} the output connector in physical space.
|
|
94
|
+
*/
|
|
95
|
+
encode(direction, input) {
|
|
96
|
+
var map = direction === 'right' ? this.rightMap : this.leftMap;
|
|
97
|
+
var evName = direction === 'right' ? 'encode-right' : 'encode-left';
|
|
98
|
+
|
|
99
|
+
//find the logical position of the input connector in the wiring map
|
|
100
|
+
var index = this.normalize(input + this.offset);
|
|
101
|
+
|
|
102
|
+
// get the logical output connector and convert that to the physical
|
|
103
|
+
// output connector
|
|
104
|
+
var output = map[index];
|
|
105
|
+
var result = this.normalize(output - this.offset);
|
|
106
|
+
|
|
107
|
+
this.fire(evName, this.name,
|
|
108
|
+
`${evName} ${this.name}, input: ${input} ouput: ${result} relative input: ${index}, relative output: ${output} rotation: ${this.offset}`,
|
|
109
|
+
{
|
|
110
|
+
input : input,
|
|
111
|
+
output: result,
|
|
112
|
+
logicalInput: index,
|
|
113
|
+
logicalOutput: output,
|
|
114
|
+
rotation: this.offset,
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Call this method to step the rotor
|
|
123
|
+
*
|
|
124
|
+
* @returns {Boolean} true true if the next rotor should be stepped
|
|
125
|
+
*/
|
|
126
|
+
step() {
|
|
127
|
+
// Because the turnover notch is attached to the outer ring, and the
|
|
128
|
+
// logical coordinates is based on the wiring, the logical position of
|
|
129
|
+
// the notch for turnover needs to be adjusted to remove the ring offset.
|
|
130
|
+
this.offset = this.normalize(this.offset + 1);
|
|
131
|
+
var turnoverOffset = this.normalize(this.offset + this.ringOffset);
|
|
132
|
+
|
|
133
|
+
// turnover happens when we step past the turnover point
|
|
134
|
+
var turnover = this.turnovers[this.normalize(turnoverOffset - 1)];
|
|
135
|
+
|
|
136
|
+
this.fire('step', this.name,
|
|
137
|
+
`step ${this.name}, ${this.offset} ${this.normalize(this.offset + this.ringOffset)} ${turnoverOffset} ${Boolean(turnover)}`,
|
|
138
|
+
{
|
|
139
|
+
rotation: this.offset,
|
|
140
|
+
ringSetting: this.ringOffset,
|
|
141
|
+
turnover: Boolean(turnover),
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return turnover;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Call this method to see if the next step on this rotor will lead to
|
|
150
|
+
* turnover. The Enigma class will call this on the middle rotor to handle
|
|
151
|
+
* double stepping.
|
|
152
|
+
*
|
|
153
|
+
* @returns true if this rotor will turnover on the next step
|
|
154
|
+
*/
|
|
155
|
+
willTurnover() {
|
|
156
|
+
var turnoverOffset = this.normalize(this.offset + this.ringOffset);
|
|
157
|
+
|
|
158
|
+
// double stepping happens when we step to the turnover point
|
|
159
|
+
return this.turnovers[turnoverOffset];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Call this method to find whether this is a fixed rotor. This is used for
|
|
164
|
+
* the non stepping rotors--beta and gamma--that are used in the M4
|
|
165
|
+
* @returns
|
|
166
|
+
*/
|
|
167
|
+
isFixed() {
|
|
168
|
+
return this.fixed;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export var STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export {default as Enigma} from './Enigma.js';
|
|
2
|
+
export {default as EntryDisc} from './EntryDisk.js';
|
|
3
|
+
export {default as inventory} from './Inventory.js';
|
|
4
|
+
export {default as PlugBoard} from './PlugBoard.js';
|
|
5
|
+
export {default as Reflector} from './Rotor.js';
|
|
6
|
+
|
|
7
|
+
import './standardInventory.js'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import inventory from'./Inventory.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This module adds all the standard inventory components to simulate models
|
|
5
|
+
* I, M3 and M4
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export var entryDiscDefintions = {
|
|
9
|
+
default: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export var rotorDefinitions = {
|
|
13
|
+
I: {
|
|
14
|
+
map: 'EKMFLGDQVZNTOWYHXUSPAIBRCJ', //p->q
|
|
15
|
+
turnovers: 'Q'
|
|
16
|
+
},
|
|
17
|
+
II: {
|
|
18
|
+
map: 'AJDKSIRUXBLHWTMCQGZNPYFVOE', //d->e
|
|
19
|
+
turnovers: 'E'
|
|
20
|
+
},
|
|
21
|
+
III: {
|
|
22
|
+
map: 'BDFHJLCPRTXVZNYEIWGAKMUSQO', // u->v
|
|
23
|
+
turnovers: 'V'
|
|
24
|
+
},
|
|
25
|
+
IV: {
|
|
26
|
+
map: 'ESOVPZJAYQUIRHXLNFTGKDCMWB', // i-> j
|
|
27
|
+
turnovers: 'J'
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
V: {
|
|
31
|
+
map: 'VZBRGITYUPSDNHLXAWMJQOFECK', //y->z
|
|
32
|
+
turnovers: 'Z'
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
VI: {
|
|
36
|
+
map: 'JPGVOUMFYQBENHZRDKASXLICTW', // l->m, y-z
|
|
37
|
+
turnovers: 'ZM'
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
VII: {
|
|
41
|
+
map: 'NZJHGRCXMYSWBOUFAIVLPEKQDT', //l-m
|
|
42
|
+
turnovers: 'ZM'
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
VIII: {
|
|
46
|
+
map: 'FKQHTLXOCBJSPDZRAMEWNIUYGV',
|
|
47
|
+
turnovers: 'ZM'
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
Beta: {
|
|
51
|
+
map: 'LEYJVCNIXWPBQMDRTAKZGFUHOS',
|
|
52
|
+
turnovers: ''
|
|
53
|
+
},
|
|
54
|
+
Gamma: {
|
|
55
|
+
map: 'FSOKANUERHMBTIYCWLQPZXVGJD',
|
|
56
|
+
turnovers: ''
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export var reflectorDefinitions = {
|
|
61
|
+
A: 'EJMZALYXVBWFCRQUONTSPIKHGD',
|
|
62
|
+
B: 'YRUHQSLDPXNGOKMIEBFZCWVJAT',
|
|
63
|
+
C: 'FVPJIAOYEDRZXWGCTKUQSBNMHL',
|
|
64
|
+
'Thin-B': 'ENKQAUYWJICOPBLMDXZVFTHRGS',
|
|
65
|
+
'Thin-C': 'RDOBJNTKVEHMLFCWZAXGYIPSUQ'
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
function addRotor(name) {
|
|
70
|
+
inventory.addRotor(name, rotorDefinitions[name].map, rotorDefinitions[name].turnovers);;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function addReflector(name) {
|
|
74
|
+
inventory.addReflector(name, reflectorDefinitions[name]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
inventory.addEntryDisc('default', entryDiscDefintions.default);
|
|
78
|
+
|
|
79
|
+
addRotor('I');
|
|
80
|
+
addRotor('II');
|
|
81
|
+
addRotor('III');
|
|
82
|
+
addRotor('IV');
|
|
83
|
+
addRotor('V');
|
|
84
|
+
addRotor('VI');
|
|
85
|
+
addRotor('VII');
|
|
86
|
+
addRotor('VIII');
|
|
87
|
+
|
|
88
|
+
addRotor('Beta');
|
|
89
|
+
addRotor('Gamma');
|
|
90
|
+
|
|
91
|
+
addReflector('A');
|
|
92
|
+
addReflector('B');
|
|
93
|
+
addReflector('C');
|
|
94
|
+
addReflector('Thin-B');
|
|
95
|
+
addReflector('Thin-C');
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
export const enigmaData = {
|
|
2
|
+
sampleFieldMessages: [
|
|
3
|
+
{
|
|
4
|
+
source: 'http://wiki.franklinheath.co.uk/index.php/Enigma/Sample_Decrypts#Operation_Barbarossa.2C_1941',
|
|
5
|
+
model: 'I/M3',
|
|
6
|
+
setup: {
|
|
7
|
+
reflector: 'B',
|
|
8
|
+
rotors: ['II', 'IV', 'V'],
|
|
9
|
+
ringSettings: [2, 21, 12],
|
|
10
|
+
plugs: 'AV BS CG DL FU HZ IN KM OW RX'
|
|
11
|
+
},
|
|
12
|
+
message: {
|
|
13
|
+
key: 'BLA',
|
|
14
|
+
encoded: 'EDPUDNRGYSZRCXNUYTPOMRMBOFKTBZREZKMLXLVEFGUEYSIOZVEQMIKUBPMMYLKLTTDEISMDICAGYKUACTCDOMOHWXMUUIAUBSTSLRNBZSZWNRFXWFYSSXJZVIJHIDISHPRKLKAYUPADTXQSPINQMATLPIFSVKDASCTACDPBOPVHJK',
|
|
15
|
+
decoded: 'AUFKLXABTEILUNGXVONXKURTINOWAXKURTINOWAXNORDWESTLXSEBEZXSEBEZXUAFFLIEGERSTRASZERIQTUNGXDUBROWKIXDUBROWKIXOPOTSCHKAXOPOTSCHKAXUMXEINSAQTDREINULLXUHRANGETRETENXANGRIFFXINFXRGTX',
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
source: 'http://wiki.franklinheath.co.uk/index.php/Enigma/Sample_Messages#U-264_.28Kapit.C3.A4nleutnant_Hartwig_Looks.29.2C_1942',
|
|
20
|
+
model: 'M4',
|
|
21
|
+
setup: {
|
|
22
|
+
reflector: 'Thin-B',
|
|
23
|
+
rotors: ['Beta', 'II', 'IV', 'I'],
|
|
24
|
+
ringSettings: [1, 1, 1, 22],
|
|
25
|
+
plugs: 'AT BL DF GJ HM NW OP QY RZ VX',
|
|
26
|
+
},
|
|
27
|
+
message: {
|
|
28
|
+
key: 'VJNA',
|
|
29
|
+
encoded: 'NCZWVUSXPNYMINHZXMQXSFWXWLKJAHSHNMCOCCAKUQPMKCSMHKSEINJUSBLKIOSXCKUBHMLLXCSJUSRRDVKOHULXWCCBGVLIYXEOAHXRHKKFVDREWEZLXOBAFGYUJQUKGRTVUKAMEURBVEKSUHHVOYHABCJWMAKLFKLMYFVNRIZRVVRTKOFDANJMOLBGFFLEOPRGTFLVRHOWOPBEKVWMUQFMPWPARMFHAGKXIIBG',
|
|
30
|
+
decoded: 'VONVONJLOOKSJHFFTTTEINSEINSDREIZWOYYQNNSNEUNINHALTXXBEIANGRIFFUNTERWASSERGEDRUECKTYWABOSXLETZTERGEGNERSTANDNULACHTDREINULUHRMARQUANTONJOTANEUNACHTSEYHSDREIYZWOZWONULGRADYACHTSMYSTOSSENACHXEKNSVIERMBFAELLTYNNNNNNOOOVIERYSICHTEINSNULL',
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
source: 'http://wiki.franklinheath.co.uk/index.php/Enigma/Sample_Messages#Scharnhorst_.28Konteradmiral_Erich_Bey.29.2C_1943',
|
|
35
|
+
model: 'M3',
|
|
36
|
+
setup: {
|
|
37
|
+
reflector: 'B',
|
|
38
|
+
rotors: ['III', 'VI', 'VIII'],
|
|
39
|
+
ringSettings: [1, 8, 13],
|
|
40
|
+
plugs: 'AN EZ HK IJ LR MQ OT PV SW UX',
|
|
41
|
+
},
|
|
42
|
+
message: {
|
|
43
|
+
key: 'UZV',
|
|
44
|
+
encoded: 'YKAENZAPMSCHZBFOCUVMRMDPYCOFHADZIZMEFXTHFLOLPZLFGGBOTGOXGRETDWTJIQHLMXVJWKZUASTR',
|
|
45
|
+
decoded: 'STEUEREJTANAFJORDJANSTANDORTQUAAACCCVIERNEUNNEUNZWOFAHRTZWONULSMXXSCHARNHORSTHCO',
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
source: 'https://enigma.hoerenberg.com/index.php?cat=The%20U534%20messages&page=P1030662',
|
|
50
|
+
model: 'M4',
|
|
51
|
+
setup: {
|
|
52
|
+
reflector: 'Thin-C',
|
|
53
|
+
rotors: ['Beta', 'V', 'VI', 'VIII'],
|
|
54
|
+
ringSettings: 'AAEL',
|
|
55
|
+
plugs: 'AE BF CM DQ HU JN LX PR SZ VW',
|
|
56
|
+
},
|
|
57
|
+
message: {
|
|
58
|
+
key: 'WIIJ',
|
|
59
|
+
encoded: 'LIRZMLWRCDMSNKLKBEBHRMFQFEQAZWXBGBIEXJPYFCQAAWSEKDEACOHDZKCZTOVSYHFNSCMAIMIMMAVJNLFXEWNPUIRINOZNCRVDHCGKCYRVUJQPVKEUIVVXGLQMKRJMDMLXLLRLYBKJWRXBQRZWGCCNDOPMGCKJ',
|
|
60
|
+
decoded: 'UUUVIRSIBENNULEINSYNACHRXUUUSTUETZPUNKTLUEBECKVVVCHEFVIERXUUUFLOTTXXMITUUUVIERSIBENNULZWOUNDUUUVIERSIBENNULDREIZURFLENDERWERFTLUEBECKGEHENXFONDORTFOLGTWEITERESX',
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
source: 'https://enigma.hoerenberg.com/index.php?cat=Norrk%C3%B6ping%20messages&page=PAGE_47_DOEP',
|
|
65
|
+
model: 'M3',
|
|
66
|
+
setup: {
|
|
67
|
+
reflector: 'B',
|
|
68
|
+
rotors: ['VII', 'IV', 'VI'],
|
|
69
|
+
ringSettings: 'AGW',
|
|
70
|
+
plugs: 'BM DX EW GP JO KV NZ RT',
|
|
71
|
+
},
|
|
72
|
+
message: {
|
|
73
|
+
key: 'NSH',
|
|
74
|
+
encoded: 'YEWZANTGDXWUVDSSYQELAMUOAMBVFZAJWFATABRMMBWXWTLFIOYBTEXXFFAOHADDXWWGBEROYDWLEUTP',
|
|
75
|
+
decoded: 'KOFFERGRAMOPHONKEINSINNKTZCMYQWPJDZXQLXGJXKWXLQDHYKCMIRBYKFHCMQWHVXLRHGDXMQWCHYK',
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
source: 'https://enigma.hoerenberg.com/index.php?cat=Norrk%C3%B6ping%20messages&page=PAGE_49_KRLR',
|
|
80
|
+
model: 'M3',
|
|
81
|
+
setup: {
|
|
82
|
+
reflector: 'B',
|
|
83
|
+
rotors: ['VII', 'IV', 'VI'],
|
|
84
|
+
ringSettings: 'AGW',
|
|
85
|
+
plugs: 'BM DX EW GP JO KV NZ RT',
|
|
86
|
+
},
|
|
87
|
+
message: {
|
|
88
|
+
key: 'RPR',
|
|
89
|
+
encoded: 'PBQOMEWLLMJFBXKPZNBHRGLUGVHJBXYPSEACOXOTBQRWVTPVVYHLLDOCQQKIWVAMJNFADSUNAVMJGJIBMUGBWWRKJBZNHVELOGZHTISLTUWS',
|
|
90
|
+
decoded: 'VERWALTUNGSAMTREICHSGERICHTARBEITSLAGERNICHTAUFSCHLAGENVCNSTDMQWZVGNVMBCPDGKPFQZWTRLDMFNGHSRDTFZGLDKCVLMNHGF',
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
sampleVerifiedMessages: [
|
|
97
|
+
{
|
|
98
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', ],
|
|
99
|
+
model: 'I',
|
|
100
|
+
setup: {
|
|
101
|
+
rotors: ['II', 'IV', 'III'],
|
|
102
|
+
plugs: 'IU ZM NK YH SJ DB TX RF OG QE',
|
|
103
|
+
ringSettings: [22, 26, 26],
|
|
104
|
+
reflector: 'A'
|
|
105
|
+
},
|
|
106
|
+
message: {
|
|
107
|
+
key: 'ZAE',
|
|
108
|
+
encoded: 'EICSSVSACTFYCNWRBCXMELAKMTBDSSPAIHMUKGFDPVEUHUDWRKOTITUTNWCEJTGUUGVVBOSVOYIDXHBRFYNOAJWISOZDIOPIXJDJJXBUYJWQRMMACUDGZCVHIFPRPCUDDWTXLCEUEASBRVOUFRTQDDOWWGCIEQSAYEXYHLCJHKWRMNQUHERAEGUXBQQDWIZYQHFJZRBZBNGWPUGLUYMUCXFKPPBWXAPRRXZLWBOFPWQHKVXWKHTNJXQCZZROWMYPLSMJINVBUCIPVYZGZXMONPMEIFWALQFEKJRRJYXVRNUFWNMPMIYBCQCJNSLNHIHOESEVNFMBKPPQTWQXQMCKIMQNPGOOTKNJDBKKHTWDUSJJGTSFJJLQ',
|
|
109
|
+
decoded: 'HOWOTHERWISETHEKINGSIRHATHLAIDTHATINADOZENPASSESBETWEENYOURSELFANDHIMHESHALLNOTEXCEEDYOUTHREEHITSHEHATHLAIDONTWELVEFORNINEANDITWOULDCOMETOIMMEDIATETRIALIFYOURLORDSHIPWOULDVOUCHSAFETHEANSWERBYTHELORDHORATIOTHESETHREEYEARSIHAVETAKENANOTEOFITTHEAGEISGROWNSOPICKEDTHATTHETOEOFTHEPEASANTCOMESSONEARTHEHEELOFTHECOURTIERHEGAFFSHISKIBETHYSTATEISTHEMOREGRACIOUSFORTISAVICETOKNOWHIM'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'http://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-u_v26_en.html'],
|
|
115
|
+
model: 'I',
|
|
116
|
+
setup: {
|
|
117
|
+
rotors: ['III', 'IV', 'I'],
|
|
118
|
+
plugs: 'EL ID YR QP FX HO KJ NS BV WU',
|
|
119
|
+
ringSettings: [10, 22, 8],
|
|
120
|
+
reflector: 'B'
|
|
121
|
+
},
|
|
122
|
+
message: {
|
|
123
|
+
key: 'OAK',
|
|
124
|
+
encoded: 'RXDVNGAFUCJOOJHUZZKLZLCIVQZQUJYGRAQSUQWSWFCGJXGDGWWQMTYTLLLGTLWVCSQQITTKGOEDMERMCMPERRYAZKEDRFSDKWVQUSMGFXLJFOWKIUMTJXKQDBHGPJUKQRTSBTZ',
|
|
125
|
+
decoded: 'GOODEVENSIRMAYONEBEPARDONDANDRETAINTHEOFFENCEHOWLONGHATHSHEBEENTHUSTHOUGHTANDAFFLICTIONPASSIONHELLITSELFSHETURNSTOFAVOURANDTOPRETTINESS'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'http://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-u_v26_en.html'],
|
|
131
|
+
model: 'I',
|
|
132
|
+
setup: {
|
|
133
|
+
rotors: ['III', 'I', 'II'],
|
|
134
|
+
plugs: 'EJ ZX TU KV MI DB NW GQ LR FO',
|
|
135
|
+
ringSettings: [4, 5, 7],
|
|
136
|
+
reflector: 'B'
|
|
137
|
+
},
|
|
138
|
+
message: {
|
|
139
|
+
start: 'OEC',
|
|
140
|
+
encoded: 'WCOJLTOXJZSYRDRGPLWJJOXWFCCISIRBMEHOKYWDFNJFWXCWATBJJTJLTMEQMWBIFXVIBJJOPAYKSUZIFATKZLMMVHNIYOPHBYKBAAHCYPKELSEWGKBHAHROMIGPBRYQXFCRJGUXWHFKBGTECDZDZLWVIHXKOFBXWJKETDLBLWKDTPPFUFKPKMROJHZMDDBHTKXSWMPNRYOVWEEUIMKYBGVSQJCFNBKKLVYZGKQQRTMMPTWJAMJQPGDCGHLUNQKSVWOAALTXS',
|
|
141
|
+
decoded: 'HOWDANGEROUSISITTHATTHISMANGOESLOOSEYETMUSTNOTWEPUTTHESTRONGLAWONHIMHESLOVEDOFTHEDISTRACTEDMULTITUDEWHOLIKENOTINTHEIRJUDGMENTBUTTHEIREYESANDWHERETISSOTHEOFFENDERSSCOURGEISWEIGHDBUTNEVERTHEOFFENCEAMINOTITHERIGHTOLDJEPHTHAHAHMYGOODLORDWHATHAVEISEENTONIGHTWHATGERTRUDE'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'http://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-u_v26_en.html'],
|
|
147
|
+
model: 'M3',
|
|
148
|
+
setup: {
|
|
149
|
+
rotors: ['IV', 'VII', 'I'],
|
|
150
|
+
plugs: 'NI RM AH VQ UL FD GS WY JE TX',
|
|
151
|
+
ringSettings: [23, 13, 3],
|
|
152
|
+
reflector: 'B'
|
|
153
|
+
},
|
|
154
|
+
message: {
|
|
155
|
+
start: 'HMP',
|
|
156
|
+
encoded: 'PDWBCHJGYPCHHUXNDVUGEZSYIUATENCFMYUIQPWIBBIELWXUNAWZNPVNKTBOJTHEDWDJJKQEOEPSMJBRGTWJGNURUXXEFYGJKUSHYPFZIDEWODCRKXFFLQKVLRLXNIACDAZEIFJXTLZXUSJRLQIDBJTIDUAFRJAYTSNFBJBXKVLASUZQMPCJJLCOZDMDIRPEJANYQCIUPLAPSMQMXXXHSMMNEDCSDDILZOWGPXVWKWKHKYLXEOYFHMNBPURLKA',
|
|
157
|
+
decoded: 'COMEHITHERGENTLEMENANDLAYYOURHANDSAGAINUPONMYSWORDNEVERTOSPEAKOFTHISTHATYOUHAVEHEARDSWEARBYMYSWORDAREYOUFAIRNOMOREBEDONEWESHOULDPROFANETHESERVICEOFTHEDEADTOSINGAREQUIEMANDSUCHRESTTOHERASTOPEACEPARTEDSOULSNIGGARDOFQUESTIONBUTOFOURDEMANDSMOSTFREEINHISREPLY'
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'http://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-u_v26_en.html'],
|
|
162
|
+
model: 'M3',
|
|
163
|
+
setup: {
|
|
164
|
+
rotors: ['VI', 'IV', 'II'],
|
|
165
|
+
plugs: 'QZ DK LA MX ET BG VO SH YP WJ',
|
|
166
|
+
ringSettings: [4, 15, 21],
|
|
167
|
+
reflector: 'B'
|
|
168
|
+
},
|
|
169
|
+
message: {
|
|
170
|
+
key: 'ASA',
|
|
171
|
+
encoded: 'URVTSTZATDUFQLGIUJKVRRVJZHSHDOGEKHREOYECTDTBUYZACWNGMHTUGWNUIUJMKHQQAPAKQIQYDHXWBRILLAURPOYTGNYUDANCLJCKSIPCJWLCLBHVXKNNRXPHHJUQJMRBZZASHQLCLQMMTYOA',
|
|
172
|
+
decoded: 'EXCHANGEFORGIVENESSWITHMENOBLEHAMLETMINEANDMYFATHERSDEATHCOMENOTUPONTHEENORTHINEONMEHORATIOIAMDEADTHOULIVESTREPORTMEANDMYCAUSEARIGHTTOTHEUNSATISFIED'
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'http://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-u_v26_en.html'],
|
|
177
|
+
model: 'M3',
|
|
178
|
+
setup: {
|
|
179
|
+
rotors: ['II', 'I', 'VII'],
|
|
180
|
+
plugs: 'BL SK WD FN HZ IX QU EJ PC OM',
|
|
181
|
+
ringSettings: [19, 20, 14],
|
|
182
|
+
reflector: 'B'
|
|
183
|
+
},
|
|
184
|
+
message: {
|
|
185
|
+
key: 'HRO',
|
|
186
|
+
encoded: 'LJVBCURJWLPLFSYKZNBEXTUHTPCWRQTOKBRBCDMAMQCDMIXIOMVCVKBVDXMXIJVELDNRBXPWCCICPEHAXTVZYV',
|
|
187
|
+
decoded: 'WHYONEFAIRDAUGHTERANDNOMORETHEWHICHHELOVEDPASSINGWELLYOUTOLDUSOFSOMESUITWHATISTLAERTES'
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'https://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-m4_v16_en.html'],
|
|
192
|
+
model: 'M4',
|
|
193
|
+
setup: {
|
|
194
|
+
rotors: ['Beta', 'II', 'VII', 'V'],
|
|
195
|
+
plugs: 'YL SF VO AI BX WC KD MT JN PH',
|
|
196
|
+
ringSettings: [9, 18, 12, 8],
|
|
197
|
+
reflector: 'Thin-B'
|
|
198
|
+
},
|
|
199
|
+
message: {
|
|
200
|
+
key: 'MKDM',
|
|
201
|
+
encoded: 'KEKZCUAFFTWOHZURZPCTWWCICFOGAXBTCTPHMAIFXEEBAYFCQOOAGFKWAHOFEUYWBUEZIWADYATCTLEOUGRPSQDTBZCHIGQGBCXBRYDSGFDBEURKHUKFUYHALYROROKIZWFTPEJJUIRGBIQFGPLGWXJPMNPSEHYSGITZCGSMZXVVJCRXZQWGMOKNBTYVRMYHPWMEXGPUSHYQEWIDZTITRUFXLPYAQUCZVDCAVCMWGGQMXEQDFPTBLOJDEGTGKXXWFIGASTKMLWHFOPPXNBVDYUHDNOJNATOTMWLJGLSTJXDDHGKOBZCPNTPLPKINICEWVJUUNVKDYWGVFFJE',
|
|
202
|
+
decoded: 'YOUNGFORTINBRASWITHCONQUESTCOMEFROMPOLANDTOTHEAMBASSADORSOFENGLANDGIVESTHISWARLIKEVOLLEYNOUPSWORDANDKNOWTHOUAMOREHORRIDHENTWHENHEISDRUNKASLEEPORINHISRAGEORINTHEINCESTUOUSPLEASUREOFHISBEDATGAMINGSWEARINGORABOUTSOMEACTTHATHASNORELISHOFSALVATIONINTTHENTRIPHIMTHATHISHEELSMAYKICKATHEAVENANDTHATHISSOULMAYBEASDAMNDANDBLACKASHELLWHERETOITGOES'
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'https://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-m4_v16_en.html'],
|
|
207
|
+
model: 'M4',
|
|
208
|
+
setup: {
|
|
209
|
+
rotors: ['Gamma', 'VIII', 'VI', 'V'],
|
|
210
|
+
plugs: 'RX VA HE ST QL UK PG DB JZ OC',
|
|
211
|
+
ringSettings: [7, 14, 19, 26],
|
|
212
|
+
reflector: 'Thin-C'
|
|
213
|
+
},
|
|
214
|
+
message: {
|
|
215
|
+
key: 'IBFP',
|
|
216
|
+
encoded: 'PUEDVIRAVXYTNFBCUGMSVLVZYRREYMERJCSCKSCACKBZNZNNLYFAAUIKFRMIIUPWBFSFWVKRYLMKHXKXGJXRFQJNRSLSFGFBBKEVQPWCLKUBMNUUKWIYSUQXAXOGZKSBVCRXMIMNJKOXCUBFXSGYQVUXSAWOFQVDQIYTULXKZXEKCQGXQXFVADTSSVZZGQMHBBMJQBYLUWAJELOIYICWTDZQAQYPBCAGKHTICILGFKMNRRXGRUTADCIOCQBXHODIRWXARKENICHVMVHWRRIVWWDCG',
|
|
217
|
+
decoded: 'IFYOUDOMEETHORATIOANDMARCELLUSTHERIVALSOFMYWATCHBIDTHEMMAKEHASTETHEGREATMANDOWNYOUMARKHISFAVOURITEFLIESTHEPOORADVANCEDMAKESFRIENDSOFENEMIESOWONDERFULGOODMYLORDTELLITFORGIVEMETHISMYVIRTUEFORINTHEFATNESSOFTHESEPURSYTIMESVIRTUEITSELFOFVICEMUSTPARDONBEGYEACURBANDWOOFORLEAVETODOHIMGOOD'
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
verified: ['https://cryptii.com/pipes/enigma-machine', 'https://people.physik.hu-berlin.de/~palloks/js/enigma/enigma-m4_v16_en.html'],
|
|
222
|
+
model: 'M4',
|
|
223
|
+
setup: {
|
|
224
|
+
rotors: ['Gamma', 'II', 'VIII', 'VII'],
|
|
225
|
+
plugs: 'SA LM XY CV ER GB KT PJ WH DU',
|
|
226
|
+
ringSettings: [25, 21, 11, 2],
|
|
227
|
+
reflector: 'Thin-B'
|
|
228
|
+
},
|
|
229
|
+
message: {
|
|
230
|
+
key: 'TQXV',
|
|
231
|
+
encoded: 'NWWKWEOXQKVGAFEMANRTAEGIZIROXKLPEFTWZJEHQVXDIDDXYIDASSXZJUSZTDHEUKPUVNTOONBYOZJAGCMTEFYHCSJCHOHGMXWHLZAVCBRVNGAMTFUNZNVWPAWBXUICZCYKDBSKJZBQPOMBJCJVKSIGJXAXBDZLYAGPSVATCLXXEQVIWUKBOP',
|
|
232
|
+
decoded: 'COMEHITHERGENTLEMENANDLAYYOURHANDSAGAINUPONMYSWORDNEVERTOSPEAKOFTHISTHATYOUHAVEHEARDSWEARBYMYSWORDSEEITSTALKSAWAYSTAYSPEAKSPEAKICHARGETHEESPEAKTISGONEANDWILLNOTANSWERISHALLOBEYMYLORD'
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
|
|
2
|
+
import '../standardInventory.js';
|
|
3
|
+
import Enigma from "../Enigma.js";
|
|
4
|
+
|
|
5
|
+
import { enigmaData } from './EnigmaData.js';
|
|
6
|
+
|
|
7
|
+
describe('Enigma Test Cases', function() {
|
|
8
|
+
var enigma;
|
|
9
|
+
/*
|
|
10
|
+
describe('Configuration', function() {
|
|
11
|
+
|
|
12
|
+
})
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
function messageLoop(messages, which, cb) {
|
|
16
|
+
messages.find(function(message) {
|
|
17
|
+
var enigma = new Enigma({
|
|
18
|
+
reflector: message.setup.reflector
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
enigma.configure({
|
|
22
|
+
plugs: message.setup.plugs,
|
|
23
|
+
rotors: message.setup.rotors,
|
|
24
|
+
ringSettings: message.setup.ringSettings
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
var toEncode = message.message[which]
|
|
28
|
+
var encoded = enigma.encode(message.message.key,toEncode);
|
|
29
|
+
return cb(message, encoded);
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('Stepping', function() {
|
|
34
|
+
var steps = {}
|
|
35
|
+
beforeEach(function() {
|
|
36
|
+
enigma = new Enigma({
|
|
37
|
+
reflector: 'B',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
enigma.configure({rotors: ['I', 'II', 'III']});
|
|
41
|
+
|
|
42
|
+
enigma.listen(function(event, name, message, info) {
|
|
43
|
+
if (event === 'step') {
|
|
44
|
+
steps[name] = steps[name] || [];
|
|
45
|
+
steps[name].push(info);
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it ('should step only the right-most rotor when not at turnover', function() {
|
|
52
|
+
steps = {};
|
|
53
|
+
enigma.encode('AAA', 'A');
|
|
54
|
+
var stepped = Object.keys(steps);
|
|
55
|
+
expect(stepped.length).toBe(1);
|
|
56
|
+
expect(stepped[0]).toBe('rotor-III');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it ('should step the next rotor when the previous turns over', function() {
|
|
60
|
+
steps = {};
|
|
61
|
+
enigma.encode('AAV', 'A');
|
|
62
|
+
expect(steps['rotor-II'].length).toBe(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it ('should double step when reaching the turn over', function() {
|
|
66
|
+
steps = {};
|
|
67
|
+
enigma.encode('ADV', 'AA');
|
|
68
|
+
expect(steps['rotor-II'].length).toBe(2);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it ('should double step on first step', function() {
|
|
72
|
+
steps = {};
|
|
73
|
+
|
|
74
|
+
enigma.configure({rotors: ['III', 'VI', 'VIII'], ringSettings: [1, 8, 13]});
|
|
75
|
+
|
|
76
|
+
enigma.encode('UZV', 'AA');
|
|
77
|
+
expect(steps['rotor-VI'].length).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('Encoding', function() {
|
|
82
|
+
it('Should encode sample field messages', function() {
|
|
83
|
+
var messages = enigmaData.sampleFieldMessages;
|
|
84
|
+
|
|
85
|
+
var fail = messageLoop(messages, 'decoded', function(message, decoded) {
|
|
86
|
+
return decoded !== message.encoded;
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(fail).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('Should decode sample field messages', function() {
|
|
93
|
+
var messages = enigmaData.sampleFieldMessages;
|
|
94
|
+
|
|
95
|
+
var fail = messageLoop(messages, 'encoded', function(message, decoded) {
|
|
96
|
+
return decoded !== message.decoded;
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(fail).toBeUndefined();
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('Should encode sample verified messages', function() {
|
|
103
|
+
var messages = enigmaData.sampleVerifiedMessages;
|
|
104
|
+
|
|
105
|
+
var fail = messageLoop(messages, 'decoded', function(message, decoded) {
|
|
106
|
+
return decoded !== message.encoded;
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expect(fail).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('Should decode sample verified messages', function() {
|
|
113
|
+
var messages = enigmaData.sampleVerifiedMessages;
|
|
114
|
+
|
|
115
|
+
var fail = messageLoop(messages, 'encoded', function(message, decoded) {
|
|
116
|
+
return decoded !== message.decoded;
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
expect(fail).toBeUndefined();
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
})
|
|
123
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import PlugBoard from "../PlugBoard.js";
|
|
2
|
+
import { plugBoardData } from './PlugBoardData.js';
|
|
3
|
+
|
|
4
|
+
describe('PlugBoard Test Cases', function() {
|
|
5
|
+
var plugBoard;
|
|
6
|
+
|
|
7
|
+
describe('Encode', function() {
|
|
8
|
+
beforeEach(function() {
|
|
9
|
+
plugBoard = new PlugBoard('test-plugboard', {
|
|
10
|
+
alphabet: plugBoardData.alphabet,
|
|
11
|
+
});
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('passes through if no plugs defined', function() {
|
|
15
|
+
plugBoard.configure()
|
|
16
|
+
var alphabet = [...plugBoardData.alphabet];
|
|
17
|
+
alphabet.forEach(function(letter) {
|
|
18
|
+
var input = plugBoardData.alphabet.indexOf(letter);
|
|
19
|
+
var output = plugBoard.encode('right', input);
|
|
20
|
+
expect(output).toBe(input);
|
|
21
|
+
var output = plugBoard.encode('left', input);
|
|
22
|
+
expect(output).toBe(input);
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should link cabled pairs', function() {
|
|
27
|
+
plugBoard.configure({plugs: plugBoardData.plugSettings});
|
|
28
|
+
var pairs = plugBoardData.plugSettings.split(' ');
|
|
29
|
+
var alphabet = plugBoardData.alphabet;
|
|
30
|
+
pairs.forEach(function(pair) {
|
|
31
|
+
var left = alphabet.indexOf(pair[0]);
|
|
32
|
+
var right = alphabet.indexOf(pair[1]);
|
|
33
|
+
|
|
34
|
+
var output = plugBoard.encode('right', left);
|
|
35
|
+
expect(output).toBe(right);
|
|
36
|
+
var output = plugBoard.encode('right', right);
|
|
37
|
+
expect(output).toBe(left);
|
|
38
|
+
|
|
39
|
+
var output = plugBoard.encode('left', right);
|
|
40
|
+
expect(output).toBe(left);
|
|
41
|
+
var output = plugBoard.encode('left', left);
|
|
42
|
+
expect(output).toBe(right);
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
})
|