@ondoher/enigma 1.0.2 → 1.0.3
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/README.md +3 -2
- package/jsconfig.json +7 -1
- package/lib/enigma/Encoder.js +120 -12
- package/lib/enigma/Enigma.js +38 -53
- package/lib/enigma/EnigmaTypes.d.ts +109 -17
- package/lib/enigma/EntryDisc.js +2 -8
- package/lib/enigma/PlugBoard.js +3 -8
- package/lib/enigma/Reflector.js +4 -8
- package/lib/enigma/Rotor.js +41 -35
- package/lib/enigma/tests/EnigmaSpec.js +16 -10
- package/lib/enigma/tests/RotorData.js +1 -1
- package/lib/enigma/tests/RotorSpec.js +3 -1
- package/lib/generator/Generator.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Table of Contents
|
|
6
6
|
[Getting Started](#getting-started)
|
|
7
|
+
|
|
7
8
|
[Simulation](#simulation)
|
|
8
9
|
- [Events](#events)
|
|
9
10
|
- [Plugboard](#plugboard)
|
|
@@ -37,7 +38,7 @@ You can then import this into your code like this:
|
|
|
37
38
|
```JavaScript
|
|
38
39
|
import {Enigma} from '@ondoher/enigma';
|
|
39
40
|
|
|
40
|
-
var enigma = new Enigma({reflector: 'B'});
|
|
41
|
+
var enigma = new Enigma("I", {reflector: 'B'});
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
In addition to this api, there is also [documentation](https://github.com/Ondoher/enigma/blob/main/docs/enigma.md)
|
|
@@ -419,7 +420,7 @@ An array of the installed rotors
|
|
|
419
420
|
```javascript
|
|
420
421
|
import {Enigma} from '@ondoher/enigma';
|
|
421
422
|
|
|
422
|
-
var enigma = new Enigma({reflector: 'B'});
|
|
423
|
+
var enigma = new Enigma("I", {reflector: 'B'});
|
|
423
424
|
|
|
424
425
|
enigma.configure({
|
|
425
426
|
rotors: ['III', 'VI', 'VIII'],
|
package/jsconfig.json
CHANGED
package/lib/enigma/Encoder.js
CHANGED
|
@@ -10,14 +10,21 @@ export default class Encoder {
|
|
|
10
10
|
* Constructor for the base encoder
|
|
11
11
|
*
|
|
12
12
|
* @param {string} name
|
|
13
|
+
* @param {ComponentType} type
|
|
13
14
|
* @param {EncoderSetup} settings
|
|
14
15
|
*/
|
|
15
|
-
constructor(name, settings) {
|
|
16
|
+
constructor(name, type, settings) {
|
|
16
17
|
let {cb, alphabet = STANDARD_ALPHABET} = settings;
|
|
17
18
|
this.name = name;
|
|
19
|
+
this.type = type;
|
|
18
20
|
this.alphabet = alphabet;
|
|
19
21
|
this.contactCount = alphabet.length;
|
|
20
|
-
|
|
22
|
+
/** @type {{[name: string]: Listener}} */
|
|
23
|
+
this.listeners = {}
|
|
24
|
+
|
|
25
|
+
if (cb) {
|
|
26
|
+
this.listeners["constructor"] = cb;
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
|
@@ -31,6 +38,52 @@ export default class Encoder {
|
|
|
31
38
|
return (connector + this.contactCount * 2) % this.contactCount;
|
|
32
39
|
}
|
|
33
40
|
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param {string} letter
|
|
44
|
+
*
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
verifyLetter(letter) {
|
|
48
|
+
if (letter.length !== 1 || this.alphabet.indexOf(letter) === -1) {
|
|
49
|
+
if (letter !== ' ') {
|
|
50
|
+
console.warn(`Unexpected character ${letter}`);
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Call this method to convert a letter to a connector value
|
|
60
|
+
*
|
|
61
|
+
* @param {string} letter
|
|
62
|
+
* @returns {number | undefined}
|
|
63
|
+
*/
|
|
64
|
+
letterToConnector(letter) {
|
|
65
|
+
if (!this.verifyLetter(letter)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this.alphabet.indexOf(letter);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Call this method to turn a connector to a letter value
|
|
74
|
+
*
|
|
75
|
+
* @param {number} connector
|
|
76
|
+
* @returns {string | undefined}
|
|
77
|
+
*/
|
|
78
|
+
connectorToLetter(connector) {
|
|
79
|
+
if (connector >= this.alphabet.length) {
|
|
80
|
+
console.warn(`Unexpected connector ${connector}`);
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return this.alphabet[connector];
|
|
85
|
+
}
|
|
86
|
+
|
|
34
87
|
/**
|
|
35
88
|
* Given an alphabetic connection map, convert that into an array of
|
|
36
89
|
* numbers. The index into the array or string is the input connector, and
|
|
@@ -79,23 +132,78 @@ export default class Encoder {
|
|
|
79
132
|
}
|
|
80
133
|
|
|
81
134
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
135
|
+
*
|
|
136
|
+
* @param {number | string} start
|
|
137
|
+
* @param {number | string} stop
|
|
138
|
+
* @param {Direction} direction
|
|
139
|
+
*/
|
|
140
|
+
fireEncodeSet(start, stop, direction) {
|
|
141
|
+
/** @type {EventData} */
|
|
142
|
+
let eventData = {
|
|
143
|
+
name: this.name,
|
|
144
|
+
type: this.type,
|
|
145
|
+
description: `input: ${start}`,
|
|
146
|
+
event: 'input',
|
|
147
|
+
input: start
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.fire('input', this.name, eventData);
|
|
84
151
|
|
|
85
|
-
|
|
152
|
+
if (typeof start === 'number') start = this.connectorToLetter(start)
|
|
153
|
+
if (typeof stop === 'number') stop = this.connectorToLetter(stop)
|
|
154
|
+
eventData = {
|
|
155
|
+
name: this.name,
|
|
156
|
+
type: this.type,
|
|
157
|
+
description: `${this.type}: ${this.name} translate ${start} to ${stop}`,
|
|
158
|
+
event: 'translate',
|
|
159
|
+
direction,
|
|
160
|
+
start,
|
|
161
|
+
stop
|
|
162
|
+
}
|
|
163
|
+
this.fire('translate', this.name, eventData);
|
|
164
|
+
|
|
165
|
+
eventData = {
|
|
166
|
+
name: this.name,
|
|
167
|
+
type: this.type,
|
|
168
|
+
description: `output: ${stop}`,
|
|
169
|
+
event: 'output',
|
|
170
|
+
output: stop
|
|
171
|
+
}
|
|
172
|
+
this.fire('output', this.name, eventData);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Call this method to add a function to be called when important events
|
|
177
|
+
* happen to a component. The name can be used to later remove the listener
|
|
178
|
+
*
|
|
179
|
+
* @param {string} name - the name of the listener
|
|
180
|
+
* @param {Listener} cb - the function to be called.
|
|
181
|
+
*/
|
|
182
|
+
listen(name, cb) {
|
|
183
|
+
this.listeners[name] = cb;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Call this method to remove a listener
|
|
188
|
+
*
|
|
189
|
+
* @param {string} name - the name of the listener
|
|
86
190
|
*/
|
|
87
|
-
|
|
88
|
-
this.
|
|
191
|
+
unlisten(name) {
|
|
192
|
+
delete this.listeners[name];
|
|
89
193
|
}
|
|
90
194
|
|
|
91
195
|
/**
|
|
92
|
-
* Call this method to call
|
|
196
|
+
* Call this method to call any event listeners
|
|
93
197
|
*
|
|
94
|
-
* @param {
|
|
95
|
-
* @param
|
|
198
|
+
* @param {EventName} event - the event being fired
|
|
199
|
+
* @param {String} name - the name of the component firing the event
|
|
200
|
+
* @param {EventData} data - the event data
|
|
96
201
|
*/
|
|
97
|
-
fire(name,
|
|
98
|
-
|
|
202
|
+
fire(event, name, data) {
|
|
203
|
+
let listeners = Object.values(this.listeners);
|
|
204
|
+
for (let cb of listeners) {
|
|
205
|
+
cb(event, name, data)
|
|
206
|
+
}
|
|
99
207
|
}
|
|
100
208
|
|
|
101
209
|
}
|
package/lib/enigma/Enigma.js
CHANGED
|
@@ -11,49 +11,35 @@ import Encoder from "./Encoder.js";
|
|
|
11
11
|
* parameters to the constructor and the config method reference the names of
|
|
12
12
|
* standard Enigma parts. These are retrieved from the Inventory instance
|
|
13
13
|
*/
|
|
14
|
-
export default class Enigma {
|
|
14
|
+
export default class Enigma extends Encoder {
|
|
15
15
|
/**
|
|
16
16
|
* The constructor for the Enigma. This represents the unconfigurable
|
|
17
17
|
* settings of the device.
|
|
18
18
|
*
|
|
19
|
+
* @param {string} name
|
|
19
20
|
* @param {EnigmaSetup} settings
|
|
20
21
|
*/
|
|
21
|
-
constructor(settings) {
|
|
22
|
-
|
|
22
|
+
constructor(name, settings) {
|
|
23
|
+
super(name, "Enigma", settings)
|
|
24
|
+
let {entryDisc = 'default', reflector, alphabet = STANDARD_ALPHABET} = settings;
|
|
23
25
|
let entryDiscSettings = inventory.getEntryDisc(entryDisc)
|
|
24
26
|
|
|
25
27
|
let reflectorSettings = inventory.getReflector(reflector)
|
|
26
28
|
this.alphabet = alphabet;
|
|
27
|
-
this.plugboard = new PlugBoard('
|
|
28
|
-
this.entryDisc = new EntryDisc('
|
|
29
|
-
this.reflector = new Reflector(
|
|
29
|
+
this.plugboard = new PlugBoard('default', {});
|
|
30
|
+
this.entryDisc = new EntryDisc('default', entryDiscSettings);
|
|
31
|
+
this.reflector = new Reflector(reflector, reflectorSettings)
|
|
30
32
|
this.length = alphabet.length;
|
|
31
33
|
/** @type {Rotor[]} */
|
|
32
34
|
this._rotors = [];
|
|
33
35
|
/** @type {{[rotor: number]: boolean}} */
|
|
34
36
|
this.pending = [];
|
|
35
|
-
this.name = model;
|
|
36
37
|
/** @type {Encoder[]} */
|
|
37
38
|
this.encoders = [];
|
|
38
39
|
/**@type {SimplifiedConfiguration & {reflector: string}} */
|
|
39
40
|
this._configuration = {reflector}
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
/**
|
|
43
|
-
* Call this method to normalize a connector number to be within the
|
|
44
|
-
* the length of the current alphabet
|
|
45
|
-
*
|
|
46
|
-
* @param {Number} connector the number to be normalized
|
|
47
|
-
*
|
|
48
|
-
* @returns {Number} the normalized connector number
|
|
49
|
-
*/
|
|
50
|
-
normalize(connector) {
|
|
51
|
-
connector += this.length;
|
|
52
|
-
connector = connector % this.length
|
|
53
|
-
|
|
54
|
-
return connector;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
43
|
/**
|
|
58
44
|
* the configured rotors
|
|
59
45
|
*
|
|
@@ -94,7 +80,7 @@ export default class Enigma {
|
|
|
94
80
|
/** @type {number[]} */
|
|
95
81
|
let ringOffsets = []
|
|
96
82
|
|
|
97
|
-
// because the rotors are specified in the reverse
|
|
83
|
+
// because the rotors are specified in the reverse order they are used,
|
|
98
84
|
// we have to do the same for the ringSettings.
|
|
99
85
|
if (Array.isArray(ringSettings)) {
|
|
100
86
|
ringSettings = ringSettings.reverse();
|
|
@@ -114,7 +100,7 @@ export default class Enigma {
|
|
|
114
100
|
}
|
|
115
101
|
|
|
116
102
|
this._rotors = useRotors.map(function(name, idx) {
|
|
117
|
-
return new Rotor(
|
|
103
|
+
return new Rotor(`${name}`, {...inventory.getRotor(name), alphabet: this.alphabet, ringSetting: ringOffsets[idx], cb: this.cb});
|
|
118
104
|
}, this);
|
|
119
105
|
|
|
120
106
|
this.encoders = [this.plugboard, this.entryDisc, ...this.rotors];
|
|
@@ -131,19 +117,28 @@ export default class Enigma {
|
|
|
131
117
|
* stepping between all rotors
|
|
132
118
|
*/
|
|
133
119
|
step() {
|
|
134
|
-
this._rotors.forEach(
|
|
120
|
+
this._rotors.forEach((rotor, idx) => {
|
|
135
121
|
if (rotor.isFixed()) return;
|
|
136
122
|
|
|
137
123
|
// This is the double stepping. Only do this for the middle rotor
|
|
138
124
|
if (rotor.willTurnover() && idx === 1) {
|
|
139
125
|
this.pending[idx] = true
|
|
126
|
+
/** @type {EventData} */
|
|
127
|
+
let eventData = {
|
|
128
|
+
name: rotor.name,
|
|
129
|
+
event: "double-step",
|
|
130
|
+
offset: rotor.offset,
|
|
131
|
+
description: `rotor ${rotor.name} double stepping from ${rotor.offset}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.fire('double-step', rotor.name, eventData);
|
|
140
135
|
};
|
|
141
136
|
|
|
142
137
|
if (this.pending[idx]) {
|
|
143
138
|
this.pending[idx] = false;
|
|
144
139
|
if (rotor.step()) this.pending[idx + 1] = true;
|
|
145
140
|
}
|
|
146
|
-
}
|
|
141
|
+
});
|
|
147
142
|
|
|
148
143
|
// The first rotor is always stepping
|
|
149
144
|
this.pending[0] = true;
|
|
@@ -190,23 +185,22 @@ export default class Enigma {
|
|
|
190
185
|
*/
|
|
191
186
|
keyPress(letter) {
|
|
192
187
|
letter = letter.toUpperCase();
|
|
193
|
-
if (
|
|
194
|
-
if (letter !== ' ')
|
|
195
|
-
console.warn(`Unexpected character ${letter}`);
|
|
188
|
+
if (!this.verifyLetter(letter)) {
|
|
196
189
|
return;
|
|
197
190
|
}
|
|
198
191
|
|
|
199
|
-
this.
|
|
192
|
+
let connector = this.letterToConnector(letter);
|
|
193
|
+
|
|
200
194
|
this.step();
|
|
201
195
|
|
|
202
196
|
// encode to the right
|
|
203
|
-
|
|
197
|
+
connector = this.encoders.reduce(function(connector, encoder, idx) {
|
|
204
198
|
connector = encoder.encode('right', connector);
|
|
205
199
|
return connector;
|
|
206
200
|
}.bind(this), this.alphabet.indexOf(letter));
|
|
207
201
|
|
|
208
202
|
// reflector
|
|
209
|
-
connector = this.reflector.encode('
|
|
203
|
+
connector = this.reflector.encode('turn-around', connector);
|
|
210
204
|
|
|
211
205
|
// encode to the left
|
|
212
206
|
connector = this.encoders.reduceRight(function(connector, encoder) {
|
|
@@ -214,9 +208,10 @@ export default class Enigma {
|
|
|
214
208
|
return connector;
|
|
215
209
|
}.bind(this), connector)
|
|
216
210
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
211
|
+
const outputLetter = this.connectorToLetter(connector);
|
|
212
|
+
|
|
213
|
+
this.fireEncodeSet(letter, outputLetter, "end-to-end")
|
|
214
|
+
return outputLetter;
|
|
220
215
|
}
|
|
221
216
|
|
|
222
217
|
/**
|
|
@@ -238,27 +233,17 @@ export default class Enigma {
|
|
|
238
233
|
}
|
|
239
234
|
|
|
240
235
|
/**
|
|
241
|
-
* Call this method to call the event listener
|
|
242
236
|
*
|
|
243
|
-
* @param {
|
|
244
|
-
* @param
|
|
237
|
+
* @param {string} name
|
|
238
|
+
* @param {Listener} cb
|
|
245
239
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Call this method to set a function to be called when important events
|
|
252
|
-
* happen to a component.
|
|
240
|
+
listen(name, cb) {
|
|
241
|
+
super.listen(name, cb);
|
|
253
242
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.cb = cb;
|
|
258
|
-
this.encoders.forEach(function(encoder) {
|
|
259
|
-
encoder.listen(cb);
|
|
260
|
-
});
|
|
243
|
+
for (let encoder of this.encoders) {
|
|
244
|
+
encoder.listen(name, cb)
|
|
245
|
+
}
|
|
261
246
|
|
|
262
|
-
this.reflector.listen(cb);
|
|
247
|
+
this.reflector.listen(name, cb);
|
|
263
248
|
}
|
|
264
249
|
}
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
// type Listener = (
|
|
2
|
-
// /** name of the event being fired*/
|
|
3
|
-
// event: string,
|
|
4
1
|
|
|
5
|
-
// /** the name of th component firing the event */
|
|
6
|
-
// name: string,
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
// message: string,
|
|
10
|
-
|
|
11
|
-
// /** event specific data */
|
|
12
|
-
// info: object
|
|
13
|
-
|
|
14
|
-
// ) => void;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type Listener = (...params: unknown[]) => void;
|
|
3
|
+
type ComponentType = ("Entry Wheel" | "Plugboard" | "Reflector" | "Rotor" | "Enigma")
|
|
19
4
|
|
|
20
5
|
interface EncoderSetup {
|
|
21
6
|
/** if specified defines an alternate alphabet for the enigma */
|
|
@@ -98,4 +83,111 @@ interface RotorSetup extends EncoderSetup {
|
|
|
98
83
|
turnovers?: string;
|
|
99
84
|
}
|
|
100
85
|
|
|
101
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Defines the possible directions for a translation.
|
|
88
|
+
*
|
|
89
|
+
* - **right** - the signal is moving from the right to the left
|
|
90
|
+
* - **left** - the signal is moving from left to right
|
|
91
|
+
* - **turn-around** - the signal is transitioning between directions, this is
|
|
92
|
+
* sent from the reflector
|
|
93
|
+
*/
|
|
94
|
+
type Direction = ("right" | "left" | "turn-around" | "end-to-end");
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Defines the possible events sent from the enigma components.
|
|
98
|
+
*
|
|
99
|
+
* **translate** - fired when the input signal is changed from one value to another
|
|
100
|
+
* **input** - fired when a signal first enters the component
|
|
101
|
+
* **output** - fired when the signal exits the component
|
|
102
|
+
*
|
|
103
|
+
*/
|
|
104
|
+
type EventName = ("translate" | "input" | "output" | "step" | "double-step");
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Defines the common data for an event
|
|
108
|
+
*/
|
|
109
|
+
type EventBase = {
|
|
110
|
+
/** the name of the component sending the event */
|
|
111
|
+
name: string;
|
|
112
|
+
|
|
113
|
+
type: ComponentType;
|
|
114
|
+
|
|
115
|
+
/** a human readable description of the event */
|
|
116
|
+
description: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Defines the data sent for a translate event
|
|
121
|
+
*/
|
|
122
|
+
type TranslateData = {
|
|
123
|
+
/** the event name */
|
|
124
|
+
event: EventName = "translate";
|
|
125
|
+
|
|
126
|
+
/** the direction the signal was sent */
|
|
127
|
+
direction: Direction;
|
|
128
|
+
|
|
129
|
+
/** the starting value */
|
|
130
|
+
start: number | string;
|
|
131
|
+
|
|
132
|
+
/** the translated value */
|
|
133
|
+
stop: number | string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* defines the data sent for a rotor step
|
|
138
|
+
*/
|
|
139
|
+
type StepData = {
|
|
140
|
+
/** the event name */
|
|
141
|
+
event: EventName = "step";
|
|
142
|
+
|
|
143
|
+
/** the starting rotor position */
|
|
144
|
+
start: number | string;
|
|
145
|
+
|
|
146
|
+
/** the ending rotor position */
|
|
147
|
+
stop: number | string
|
|
148
|
+
|
|
149
|
+
/** the locations of the turnover points for this rotor */
|
|
150
|
+
turnover: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type DoubleStepData = {
|
|
154
|
+
eventName: EventName = "double-step",
|
|
155
|
+
rotor: string;
|
|
156
|
+
offset: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Defines the data sent with an input event
|
|
161
|
+
*/
|
|
162
|
+
type InputData = {
|
|
163
|
+
/** the event name */
|
|
164
|
+
event: EventName = "input";
|
|
165
|
+
|
|
166
|
+
/** the input connection */
|
|
167
|
+
input: number | string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Defines the data sent with an output event
|
|
172
|
+
*/
|
|
173
|
+
type OutputData = {
|
|
174
|
+
/** the event name */
|
|
175
|
+
event: EventName = "output";
|
|
176
|
+
|
|
177
|
+
/** the output connection */
|
|
178
|
+
output: number | string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Defines the discriminated union for all the event data */
|
|
182
|
+
type EventData = EventBase & (TranslateData | StepData | InputData | OutputData);
|
|
183
|
+
|
|
184
|
+
type Listener = (
|
|
185
|
+
/** name of the event being fired*/
|
|
186
|
+
event: string,
|
|
187
|
+
|
|
188
|
+
/** the name of th component firing the event */
|
|
189
|
+
name: string,
|
|
190
|
+
|
|
191
|
+
/** event specific data */
|
|
192
|
+
data: EventData
|
|
193
|
+
) => void;
|
package/lib/enigma/EntryDisc.js
CHANGED
|
@@ -15,7 +15,7 @@ export default class EntryDisc extends Encoder {
|
|
|
15
15
|
* between input and output contacts
|
|
16
16
|
*/
|
|
17
17
|
constructor(name, settings) {
|
|
18
|
-
super(name, settings);
|
|
18
|
+
super(name, "Entry Wheel", settings);
|
|
19
19
|
var {map = STANDARD_ALPHABET} = settings;
|
|
20
20
|
this.rightMap = this.makeMap(map);
|
|
21
21
|
this.leftMap = this.makeReverseMap(this.rightMap);
|
|
@@ -31,14 +31,8 @@ export default class EntryDisc extends Encoder {
|
|
|
31
31
|
*/
|
|
32
32
|
encode(direction, input) {
|
|
33
33
|
var result = direction === 'right' ? this.rightMap[input]: this.leftMap[input];
|
|
34
|
-
var evName = direction === 'right' ? 'encode-right' : 'encode-left';
|
|
35
34
|
|
|
36
|
-
this.
|
|
37
|
-
`${evName} ${this.name}, input: ${input}, output: ${result}`, {
|
|
38
|
-
input: input,
|
|
39
|
-
output: result,
|
|
40
|
-
}
|
|
41
|
-
);
|
|
35
|
+
this.fireEncodeSet(input, result, direction);
|
|
42
36
|
return result;
|
|
43
37
|
}
|
|
44
38
|
}
|
package/lib/enigma/PlugBoard.js
CHANGED
|
@@ -14,7 +14,7 @@ export default class PlugBoard extends Encoder {
|
|
|
14
14
|
* using an alternate alphabet
|
|
15
15
|
*/
|
|
16
16
|
constructor(name = 'plugboard', settings = {}) {
|
|
17
|
-
super(name, settings);
|
|
17
|
+
super(name, "Plugboard", settings);
|
|
18
18
|
|
|
19
19
|
var {alphabet = STANDARD_ALPHABET, map} = settings;
|
|
20
20
|
this.alphabet = alphabet;
|
|
@@ -64,13 +64,8 @@ export default class PlugBoard extends Encoder {
|
|
|
64
64
|
*/
|
|
65
65
|
encode(direction, input) {
|
|
66
66
|
var result = direction === 'right' ? this.rightMap[input]: this.leftMap[input];
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
`${evName} ${this.name}, input: ${input}, output: ${result}`, {
|
|
70
|
-
input: input,
|
|
71
|
-
output: result,
|
|
72
|
-
}
|
|
73
|
-
);
|
|
67
|
+
|
|
68
|
+
this.fireEncodeSet(input, result, direction);
|
|
74
69
|
|
|
75
70
|
return result;
|
|
76
71
|
}
|
package/lib/enigma/Reflector.js
CHANGED
|
@@ -13,7 +13,7 @@ export default class Reflector extends Encoder {
|
|
|
13
13
|
* @param {EncoderSetup} settings The definition of the reflector
|
|
14
14
|
*/
|
|
15
15
|
constructor(name, settings) {
|
|
16
|
-
super(name, settings);
|
|
16
|
+
super(name, "Reflector", settings);
|
|
17
17
|
var {map = STANDARD_ALPHABET} = settings;
|
|
18
18
|
|
|
19
19
|
this.map = this.makeMap(map);
|
|
@@ -30,14 +30,10 @@ export default class Reflector extends Encoder {
|
|
|
30
30
|
*
|
|
31
31
|
* @returns {Number} the mapped output connector
|
|
32
32
|
*/
|
|
33
|
-
encode(
|
|
33
|
+
encode(direction, input) {
|
|
34
34
|
var result = this.map[input];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
input: input,
|
|
39
|
-
output: result,
|
|
40
|
-
});
|
|
35
|
+
|
|
36
|
+
this.fireEncodeSet(input, result, direction)
|
|
41
37
|
|
|
42
38
|
return result;
|
|
43
39
|
}
|
package/lib/enigma/Rotor.js
CHANGED
|
@@ -17,8 +17,8 @@ export default class Rotor extends Encoder {
|
|
|
17
17
|
* define the the rotor and how it is configured.
|
|
18
18
|
*/
|
|
19
19
|
constructor(name, settings) {
|
|
20
|
-
super(name, settings);
|
|
21
|
-
|
|
20
|
+
super(name, "Rotor", settings);
|
|
21
|
+
let { map = STANDARD_ALPHABET, turnovers = '', ringSetting = 0} = settings
|
|
22
22
|
|
|
23
23
|
this.map = [...map];
|
|
24
24
|
this.rightMap = this.makeMap(map);
|
|
@@ -29,10 +29,13 @@ export default class Rotor extends Encoder {
|
|
|
29
29
|
this.turnoverLookup = this.makeMap(turnovers);
|
|
30
30
|
this.offset = 0;
|
|
31
31
|
if (turnovers === '') this.fixed = true;
|
|
32
|
-
this.turnovers =
|
|
33
|
-
|
|
32
|
+
this.turnovers = turnovers;
|
|
33
|
+
/** @type {Set<number>} */
|
|
34
|
+
let turnoverSet = new Set();
|
|
35
|
+
this._turnovers = this.turnoverLookup.reduce((turnovers, turnover) => {
|
|
36
|
+
turnovers.add(turnover)
|
|
34
37
|
return turnovers;
|
|
35
|
-
}
|
|
38
|
+
}, turnoverSet);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -45,7 +48,7 @@ export default class Rotor extends Encoder {
|
|
|
45
48
|
* ring setting.
|
|
46
49
|
*/
|
|
47
50
|
setStartPosition(connector) {
|
|
48
|
-
|
|
51
|
+
let pos = this.alphabet.indexOf(connector);
|
|
49
52
|
this.offset = this.normalize(pos - this.ringOffset);
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -63,54 +66,49 @@ export default class Rotor extends Encoder {
|
|
|
63
66
|
* @returns {Number} the output connector in physical space.
|
|
64
67
|
*/
|
|
65
68
|
encode(direction, input) {
|
|
66
|
-
|
|
67
|
-
var evName = direction === 'right' ? 'encode-right' : 'encode-left';
|
|
69
|
+
let map = direction === 'right' ? this.rightMap : this.leftMap;
|
|
68
70
|
|
|
69
71
|
//find the logical position of the input connector in the wiring map
|
|
70
|
-
|
|
72
|
+
let index = this.normalize(input + this.offset);
|
|
71
73
|
|
|
72
74
|
// get the logical output connector and convert that to the physical
|
|
73
75
|
// output connector
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.fire(evName, this.name,
|
|
78
|
-
`${evName} ${this.name}, input: ${input} output: ${result} relative input: ${index}, relative output: ${output} rotation: ${this.offset}`,
|
|
79
|
-
{
|
|
80
|
-
input : input,
|
|
81
|
-
output: result,
|
|
82
|
-
logicalInput: index,
|
|
83
|
-
logicalOutput: output,
|
|
84
|
-
rotation: this.offset,
|
|
85
|
-
}
|
|
86
|
-
);
|
|
76
|
+
let output = map[index];
|
|
77
|
+
let result = this.normalize(output - this.offset);
|
|
87
78
|
|
|
79
|
+
this.fireEncodeSet(input, result, direction);
|
|
88
80
|
return result;
|
|
89
81
|
}
|
|
90
82
|
|
|
91
83
|
/**
|
|
92
84
|
* Call this method to step the rotor
|
|
93
85
|
*
|
|
94
|
-
* @returns {Boolean} true
|
|
86
|
+
* @returns {Boolean} true if the next rotor should be stepped
|
|
95
87
|
*/
|
|
96
88
|
step() {
|
|
97
89
|
// Because the turnover notch is attached to the outer ring, and the
|
|
98
90
|
// logical coordinates are based on the wiring, the logical position of
|
|
99
91
|
// the notch for turnover needs to be adjusted to remove the ring offset.
|
|
92
|
+
let start = this.offset;
|
|
100
93
|
this.offset = this.normalize(this.offset + 1);
|
|
101
|
-
|
|
94
|
+
let stop = this.offset;
|
|
95
|
+
let turnoverOffset = this.normalize(this.offset + this.ringOffset);
|
|
96
|
+
|
|
102
97
|
|
|
103
98
|
// turnover happens when we step past the turnover point
|
|
104
|
-
|
|
99
|
+
let turnover = this._turnovers.has(this.normalize(turnoverOffset - 1));
|
|
100
|
+
|
|
101
|
+
/** @type {EventData} */
|
|
102
|
+
let eventData = {
|
|
103
|
+
name: this.name,
|
|
104
|
+
type: this.type,
|
|
105
|
+
description: `step ${this.name}, ${this.offset} ${this.normalize(this.offset + this.ringOffset)} ${turnoverOffset} ${Boolean(turnover)}`,
|
|
106
|
+
event: 'step',
|
|
107
|
+
turnover, start, stop
|
|
108
|
+
}
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{
|
|
109
|
-
rotation: this.offset,
|
|
110
|
-
ringSetting: this.ringOffset,
|
|
111
|
-
turnover: Boolean(turnover),
|
|
112
|
-
}
|
|
113
|
-
);
|
|
110
|
+
// console.log("firing", eventData);
|
|
111
|
+
this.fire('step', this.name, eventData);
|
|
114
112
|
|
|
115
113
|
return turnover;
|
|
116
114
|
}
|
|
@@ -123,10 +121,18 @@ export default class Rotor extends Encoder {
|
|
|
123
121
|
* @returns true if this rotor will turnover on the next step
|
|
124
122
|
*/
|
|
125
123
|
willTurnover() {
|
|
126
|
-
|
|
124
|
+
let turnoverOffset = this.normalize(this.offset + this.ringOffset);
|
|
127
125
|
|
|
128
126
|
// double stepping happens when we step to the turnover point
|
|
129
|
-
return this.
|
|
127
|
+
return this._turnovers.has(turnoverOffset);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
shouldTurnover() {
|
|
131
|
+
let turnoverOffset = this.normalize(this.offset + 1 + this.ringOffset);
|
|
132
|
+
|
|
133
|
+
// double stepping happens when we step to the turnover point
|
|
134
|
+
return this._turnovers.has(turnoverOffset);
|
|
135
|
+
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
/**
|
|
@@ -5,11 +5,12 @@ import Enigma from "../Enigma.js";
|
|
|
5
5
|
import { enigmaData } from './EnigmaData.js';
|
|
6
6
|
|
|
7
7
|
describe('Enigma Test Cases', function() {
|
|
8
|
+
/** @type {Enigma} */
|
|
8
9
|
var enigma;
|
|
9
10
|
|
|
10
11
|
function messageLoop(messages, which, cb) {
|
|
11
12
|
return messages.find(function(message) {
|
|
12
|
-
var enigma = new Enigma({
|
|
13
|
+
var enigma = new Enigma("Test", {
|
|
13
14
|
reflector: message.setup.reflector
|
|
14
15
|
});
|
|
15
16
|
|
|
@@ -29,39 +30,42 @@ describe('Enigma Test Cases', function() {
|
|
|
29
30
|
describe('Stepping', function() {
|
|
30
31
|
var steps = {}
|
|
31
32
|
beforeEach(function() {
|
|
32
|
-
enigma = new Enigma({
|
|
33
|
+
enigma = new Enigma("I", {
|
|
33
34
|
reflector: 'B',
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
enigma.configure({rotors: ['I', 'II', 'III']});
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
listen()
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function listen() {
|
|
43
|
+
enigma.listen('test', (event, name, data) => {
|
|
39
44
|
if (event === 'step') {
|
|
40
45
|
steps[name] = steps[name] || [];
|
|
41
|
-
steps[name].push(
|
|
46
|
+
steps[name].push(data);
|
|
42
47
|
}
|
|
43
48
|
})
|
|
44
|
-
|
|
45
|
-
});
|
|
49
|
+
}
|
|
46
50
|
|
|
47
51
|
it ('should step only the right-most rotor when not at turnover', function() {
|
|
48
52
|
steps = {};
|
|
49
53
|
enigma.encode('AAA', 'A');
|
|
50
54
|
var stepped = Object.keys(steps);
|
|
51
55
|
expect(stepped.length).toBe(1);
|
|
52
|
-
expect(stepped[0]).toBe('
|
|
56
|
+
expect(stepped[0]).toBe('III');
|
|
53
57
|
});
|
|
54
58
|
|
|
55
59
|
it ('should step the next rotor when the previous turns over', function() {
|
|
56
60
|
steps = {};
|
|
57
61
|
enigma.encode('AAV', 'A');
|
|
58
|
-
expect(steps['
|
|
62
|
+
expect(steps['II'].length).toBe(1);
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
it ('should double step when reaching the turn over', function() {
|
|
62
66
|
steps = {};
|
|
63
67
|
enigma.encode('ADV', 'AA');
|
|
64
|
-
expect(steps['
|
|
68
|
+
expect(steps['II'].length).toBe(2);
|
|
65
69
|
});
|
|
66
70
|
|
|
67
71
|
it ('should double step on first step', function() {
|
|
@@ -69,8 +73,10 @@ describe('Enigma Test Cases', function() {
|
|
|
69
73
|
|
|
70
74
|
enigma.configure({rotors: ['III', 'VI', 'VIII'], ringSettings: [1, 8, 13]});
|
|
71
75
|
|
|
76
|
+
listen();
|
|
77
|
+
|
|
72
78
|
enigma.encode('UZV', 'AA');
|
|
73
|
-
expect(steps['
|
|
79
|
+
expect(steps['VI'].length).toBe(1);
|
|
74
80
|
});
|
|
75
81
|
})
|
|
76
82
|
|
|
@@ -29,7 +29,9 @@ describe('Rotor Test Cases', function() {
|
|
|
29
29
|
turnovers: rotorData.createData.turnovers,
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
let turnovers = [...rotor._turnovers];
|
|
33
|
+
|
|
34
|
+
expect(turnovers).toEqual(rotorData.createData.turnoversMap);
|
|
33
35
|
})
|
|
34
36
|
})
|
|
35
37
|
|
|
@@ -136,7 +136,7 @@ export default class Generator {
|
|
|
136
136
|
*/
|
|
137
137
|
createRandomEnigma(model = "Enigma", reflectors = ['A', 'B', 'C']) {
|
|
138
138
|
let reflector = Random.pickOne(reflectors);
|
|
139
|
-
return new Enigma({model, reflector});
|
|
139
|
+
return new Enigma(model, {model, reflector});
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|