@ondoher/enigma 0.1.6 → 1.0.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/README.md +455 -94
- package/bin/enigma.js +0 -0
- package/jsconfig.json +3 -0
- package/lib/enigma/Encoder.js +12 -13
- package/lib/enigma/Enigma.js +68 -50
- package/lib/enigma/EnigmaTypes.d.ts +101 -0
- package/lib/enigma/EntryDisc.js +13 -5
- package/lib/enigma/Inventory.js +26 -6
- package/lib/enigma/PlugBoard.js +20 -17
- package/lib/enigma/Reflector.js +4 -11
- package/lib/enigma/Rotor.js +5 -35
- package/lib/enigma/standardInventory.js +2 -2
- package/lib/enigma/tests/EnigmaSpec.js +0 -5
- package/lib/enigma/tests/PlugBoardSpec.js +15 -14
- package/lib/enigma/tests/RotorSpec.js +1 -1
- package/lib/generator/CodeBook.js +180 -0
- package/lib/generator/Generator.js +94 -386
- package/lib/generator/GeneratorTypes.d.ts +100 -0
- package/lib/generator/index.js +2 -0
- package/lib/utils/Random.js +236 -0
- package/package.json +10 -3
|
@@ -4,140 +4,50 @@ import '../enigma/standardInventory.js';
|
|
|
4
4
|
import sentences from './hamlet.js'
|
|
5
5
|
import inventory from '../enigma/Inventory.js';
|
|
6
6
|
import { STANDARD_ALPHABET } from '../enigma/consts.js';
|
|
7
|
+
import Random from '../utils/Random.js';
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @property {Array.<Number>} ringSettings an array of offsets for the ring
|
|
14
|
-
* settings
|
|
15
|
-
* @property {Array.<Array>} plugs 10 pairs of letters that will be used as
|
|
16
|
-
* connections on the plug board
|
|
11
|
+
* Use this class to generate random enigma configurations and messages. The
|
|
12
|
+
* methods in this class all the use the `Random` object, which can be seeded to
|
|
13
|
+
* produce a reproducible output
|
|
17
14
|
*/
|
|
18
|
-
|
|
19
15
|
export default class Generator {
|
|
20
|
-
constructor(
|
|
21
|
-
this.embellish = embellish;
|
|
22
|
-
this.enigma = new Enigma({reflector: 'B'});
|
|
23
|
-
this.indicators = {};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* call this method to pick a random number from an array and remove it
|
|
28
|
-
* @param {Array} list the array of itens to chooise from
|
|
29
|
-
*
|
|
30
|
-
* @returns {*} the chosen item
|
|
31
|
-
*/
|
|
32
|
-
pickOne(list) {
|
|
33
|
-
var pos = Math.floor(Math.random() * list.length);
|
|
34
|
-
var choice = list.splice(pos, 1);
|
|
35
|
-
return choice[0];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Call this method to pick two items from a given list. The items are
|
|
40
|
-
* removed from the array
|
|
41
|
-
*
|
|
42
|
-
* @param {Array} list the array of items to choose
|
|
43
|
-
* @returns {Array} the two chosen items
|
|
44
|
-
*/
|
|
45
|
-
pickPair(list) {
|
|
46
|
-
var result = [];
|
|
47
|
-
result.push(this.pickOne(list))
|
|
48
|
-
result.push(this.pickOne(list))
|
|
49
|
-
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
16
|
+
constructor() {}
|
|
52
17
|
|
|
53
18
|
/**
|
|
54
|
-
* Call this method to
|
|
55
|
-
*
|
|
19
|
+
* Call this method to turn a string into valid characters for encryption,
|
|
20
|
+
* which is just the letters A-Z.
|
|
56
21
|
*
|
|
57
|
-
* @param {
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*/
|
|
61
|
-
chooseOne(list) {
|
|
62
|
-
var pos = Math.floor(Math.random() * list.length);
|
|
63
|
-
return list[pos];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Call this method to choose a given number of items from a list. The items
|
|
68
|
-
* are removed.
|
|
69
|
-
*
|
|
70
|
-
* @param {Number} count te number of items to pick
|
|
71
|
-
* @param {*} list the list of items to choose from
|
|
72
|
-
*
|
|
73
|
-
* @returns {Array} the chosen items
|
|
74
|
-
*/
|
|
75
|
-
pick(count, list) {
|
|
76
|
-
|
|
77
|
-
var result = [];
|
|
78
|
-
for(var idx = 0; idx < count; idx++) {
|
|
79
|
-
result.push(this.pickOne(list));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Call this method to randomly pick a number of items from a list. The
|
|
87
|
-
* items are not removed.
|
|
88
|
-
*
|
|
89
|
-
* @param {Number} count the number of items to choose
|
|
90
|
-
* @param {Array} list the list of items to choose from
|
|
91
|
-
*
|
|
92
|
-
* @returns {Array} the list of items chosen
|
|
22
|
+
* @param {String} text the original text
|
|
23
|
+
* @returns {String} the text that has been normalized to what the Enigma
|
|
24
|
+
* can process
|
|
93
25
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
result.push(this.chooseOne(list));
|
|
98
|
-
}
|
|
26
|
+
cleanMessage(text) {
|
|
27
|
+
text = text.toUpperCase();
|
|
28
|
+
text = text.replace(/[^A-Z]/g, '');
|
|
99
29
|
|
|
100
|
-
return
|
|
30
|
+
return text
|
|
101
31
|
}
|
|
102
32
|
|
|
103
33
|
/**
|
|
104
|
-
* Call this method to
|
|
105
|
-
*
|
|
34
|
+
* Call this method to break a string into groups of five letters with a
|
|
35
|
+
* space between them.
|
|
106
36
|
*
|
|
107
|
-
* @param {
|
|
108
|
-
* @param {
|
|
37
|
+
* @param {string} text - the original text
|
|
38
|
+
* @param {number} [size] - the size of the text groups, defaults to 5
|
|
109
39
|
*
|
|
110
|
-
* @returns {
|
|
111
|
-
* two items from the list
|
|
40
|
+
* @returns {string} the segmented string
|
|
112
41
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Call this method to pick pairs of strings from an array of strings. This
|
|
125
|
-
* returns an array of strings where each chosen pair has been concatenated.
|
|
126
|
-
* The strings are removed from the list.
|
|
127
|
-
*
|
|
128
|
-
* @param {Number} the number of pairs to pick
|
|
129
|
-
* @param {Array.<String>} list the strings to pick from
|
|
130
|
-
* @returns {Array.<String>} the array of string pairs
|
|
131
|
-
*/
|
|
132
|
-
pickStringPairs(count, list) {
|
|
133
|
-
var result = [];
|
|
134
|
-
|
|
135
|
-
for(var idx = 0; idx < count; idx++) {
|
|
136
|
-
result.push(this.pickPair(list).join(''));
|
|
137
|
-
}
|
|
42
|
+
groupText(text, size = 5) {
|
|
43
|
+
let textArray = [...text];
|
|
44
|
+
let result = textArray.reduce((text, letter, idx) => {
|
|
45
|
+
text = text + letter;
|
|
46
|
+
if (Math.floor(idx % size) === size - 1) text = text + ' ';
|
|
47
|
+
return text;
|
|
48
|
+
}, '');
|
|
138
49
|
|
|
139
50
|
return result;
|
|
140
|
-
|
|
141
51
|
}
|
|
142
52
|
|
|
143
53
|
/**
|
|
@@ -148,64 +58,71 @@ export default class Generator {
|
|
|
148
58
|
* @returns {String} the sentences separated by a ' ';
|
|
149
59
|
*/
|
|
150
60
|
generateSentences(count) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
61
|
+
return Random.chooseRange(count, sentences);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Call this method to get the possible setup and configuration options for
|
|
66
|
+
* the given model
|
|
67
|
+
*
|
|
68
|
+
* @param {Model} model
|
|
69
|
+
*
|
|
70
|
+
* @returns {ModelOptions}
|
|
71
|
+
*/
|
|
72
|
+
getModelOptions(model) {
|
|
73
|
+
switch (model) {
|
|
74
|
+
case 'I':
|
|
75
|
+
return {
|
|
76
|
+
rotors: ['I', 'II', 'III', 'IV', 'V'],
|
|
77
|
+
reflectors: ['A', 'B', 'C'],
|
|
78
|
+
fixed: false
|
|
79
|
+
};
|
|
80
|
+
case "M3":
|
|
81
|
+
return {
|
|
82
|
+
rotors: ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII'],
|
|
83
|
+
reflectors: ['A', 'B', 'C'],
|
|
84
|
+
fixed: false
|
|
85
|
+
}
|
|
86
|
+
case "M4":
|
|
87
|
+
return {
|
|
88
|
+
reflectors: ['Thin-B', 'Thin-C'],
|
|
89
|
+
rotors: ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII'],
|
|
90
|
+
fixed: ['Beta', 'Gamma'],
|
|
91
|
+
}
|
|
92
|
+
}
|
|
154
93
|
}
|
|
155
94
|
|
|
156
95
|
/**
|
|
157
|
-
* Call this method to
|
|
158
|
-
* a day in a key sheet
|
|
96
|
+
* Call this method to generate a random Enigma configuration
|
|
159
97
|
*
|
|
160
|
-
* @
|
|
161
|
-
*/
|
|
162
|
-
makeIndicator() {
|
|
163
|
-
var letters = [...'abcdefghijklmnopqrstuvwxyzx'];
|
|
164
|
-
var indicator;
|
|
165
|
-
|
|
166
|
-
do {
|
|
167
|
-
indicator = this.choose(3, letters).join('');
|
|
168
|
-
} while (this.indicators[indicator]);
|
|
98
|
+
* @param {GeneratorSetup} [setup] options for settings
|
|
169
99
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return indicator;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Call this method to generate a random Enigma setup
|
|
177
|
-
*
|
|
178
|
-
* @param {Object} [settings] options for settings
|
|
179
|
-
* @property {Array.<String>} [rotors] alternate list of rotors to choose
|
|
180
|
-
* from
|
|
181
|
-
* @property {Array.<String>} [fixed] an array of fixed rotors to pick one
|
|
182
|
-
* of
|
|
183
|
-
*
|
|
184
|
-
* @returns {Object} the Enigma settings.
|
|
185
|
-
* @property {Array.<String>} rotors an array of three rotor names, four if
|
|
186
|
-
* fixed was given
|
|
187
|
-
* @property {Array.<Number>} ringSettings an array of offsets for the ring
|
|
188
|
-
* settings
|
|
189
|
-
* @property {Array.<Array>} plugs 10 pairs of letters that will be used as
|
|
190
|
-
* connections on the plug board
|
|
100
|
+
* @returns {SimplifiedConfiguration} the Enigma settings.
|
|
191
101
|
*/
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
102
|
+
generateEnigmaConfiguration(setup = {}) {
|
|
103
|
+
/** @type {number []} */
|
|
104
|
+
let connectors = [...Array(26).keys()];
|
|
105
|
+
/** @type {string[]} */
|
|
106
|
+
let letters = [...STANDARD_ALPHABET];
|
|
107
|
+
let unfixed = inventory.getRotorNames(false);
|
|
108
|
+
let fixed = inventory.getRotorNames(true);
|
|
109
|
+
let useRotors = structuredClone(setup.rotors || unfixed);
|
|
110
|
+
let rotors = Random.pick(3, useRotors);
|
|
111
|
+
let ringSettings = Random.choose(3, connectors);
|
|
112
|
+
let plugsList = Random.pickPairs(10, letters);
|
|
113
|
+
|
|
114
|
+
plugsList = plugsList.map((pair) => pair.join(""));
|
|
196
115
|
|
|
197
|
-
var rotors = this.pick(3, rotorInventory);
|
|
198
|
-
var ringSettings = this.choose(3, connectors);
|
|
199
|
-
var plugsA = this.pickStringPairs(10, letters);
|
|
200
116
|
ringSettings[0]++;
|
|
201
117
|
ringSettings[1]++;
|
|
202
118
|
ringSettings[2]++;
|
|
203
119
|
|
|
204
|
-
|
|
120
|
+
let plugs = plugsList.join(' ');
|
|
205
121
|
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
122
|
+
if (setup.fixed) {
|
|
123
|
+
let useRotors = structuredClone(Array.isArray(setup.fixed) ? setup.fixed : fixed);
|
|
124
|
+
rotors.push(Random.pickOne(useRotors));
|
|
125
|
+
ringSettings.push(Random.chooseOne(connectors));
|
|
209
126
|
ringSettings[3]++;
|
|
210
127
|
}
|
|
211
128
|
|
|
@@ -213,240 +130,31 @@ export default class Generator {
|
|
|
213
130
|
}
|
|
214
131
|
|
|
215
132
|
/**
|
|
216
|
-
* Call this method to create a single days configuration for a key sheet.
|
|
217
|
-
* This an Enigma configuration plus the other metadata.
|
|
218
|
-
*
|
|
219
|
-
* @param {idx} idx the day of the month
|
|
220
|
-
*
|
|
221
|
-
* @returns {Object} the single day of the key sheet
|
|
222
|
-
* @property {Number} day the day of the month
|
|
223
|
-
* @property {Array.<String>} rotors an array of three rotor names
|
|
224
|
-
* @property {Array.<Number>} ringSettings an array of offsets for the ring
|
|
225
|
-
* settings
|
|
226
|
-
* @property {Array.<String>} plugs 10 pairs of letters that will be used as
|
|
227
|
-
* connections on the plug board
|
|
228
|
-
* @propery {Array.<String>} indicators and array of four three letter
|
|
229
|
-
* strings. This will be unique across a key sheet
|
|
230
|
-
*/
|
|
231
|
-
generateDay(idx) {
|
|
232
|
-
var settings = this.generateEnigmaSetup();
|
|
233
|
-
|
|
234
|
-
var indicators = [];
|
|
235
|
-
|
|
236
|
-
indicators.push(this.makeIndicator());
|
|
237
|
-
indicators.push(this.makeIndicator());
|
|
238
|
-
indicators.push(this.makeIndicator());
|
|
239
|
-
indicators.push(this.makeIndicator());
|
|
240
|
-
|
|
241
|
-
return {...settings, indicators}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Call this method to construct a key sheet for the given number of days
|
|
246
133
|
*
|
|
247
|
-
* @param {
|
|
248
|
-
*
|
|
134
|
+
* @param {string[]} [reflectors] if given, specifies and alternate list of
|
|
135
|
+
* reflectors. Defaults to ['A', 'B', 'C'];
|
|
249
136
|
*/
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
for(var idx = 0; idx < days; idx++) {
|
|
255
|
-
sheet.push(this.generateDay(idx))
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return sheet;
|
|
137
|
+
createRandomEnigma(model = "Enigma", reflectors = ['A', 'B', 'C']) {
|
|
138
|
+
let reflector = Random.pickOne(reflectors);
|
|
139
|
+
return new Enigma({model, reflector});
|
|
259
140
|
}
|
|
260
141
|
|
|
261
142
|
/**
|
|
262
|
-
* Call this method to
|
|
263
|
-
*
|
|
143
|
+
* Call this method to generate a random message text encoded with the given
|
|
144
|
+
* Enigma. The random text will be a few sentences from Hamlet.
|
|
264
145
|
*
|
|
265
|
-
* @param {
|
|
266
|
-
* @returns {String} the segmented string
|
|
267
|
-
*/
|
|
268
|
-
segment(text) {
|
|
269
|
-
var textArray = [...text];
|
|
270
|
-
var text = textArray.reduce(function(text, letter, idx) {
|
|
271
|
-
text = text + letter;
|
|
272
|
-
if (Math.floor(idx % 5) === 4) text = text + ' ';
|
|
273
|
-
return text;
|
|
274
|
-
}, '');
|
|
275
|
-
|
|
276
|
-
return text
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Call this method to turn a string into valid characters for encyption,
|
|
281
|
-
* which is just the letters A-Z. If the generator was created with the
|
|
282
|
-
* embellish option then certain characters in the original text will be
|
|
283
|
-
* relaced by uncommon letter triplets. This allows for better presentation
|
|
284
|
-
* when the message as been decoded
|
|
146
|
+
* @param {Enigma} enigma
|
|
285
147
|
*
|
|
286
|
-
* @
|
|
287
|
-
* @returns {String} the text that has been normalized to what the Enigma
|
|
288
|
-
* can process
|
|
148
|
+
* @returns {GeneratedMessage} details of the generated text
|
|
289
149
|
*/
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (this.embellish) {
|
|
294
|
-
text = text.replace(/,/g, 'ZIZ');
|
|
295
|
-
text = text.replace(/\./g, 'YIY');
|
|
296
|
-
text = text.replace(/\?/g, 'XIX');
|
|
297
|
-
text = text.replace(/ +/g, 'WIW');
|
|
298
|
-
text = text.replace(/'/g, 'VIV');
|
|
299
|
-
text = text.replace(/'/g, 'UIU');
|
|
300
|
-
text = text.replace(/0/g, 'NULL');
|
|
301
|
-
text = text.replace(/1/g, 'ONE');
|
|
302
|
-
text = text.replace(/2/g, 'TWO');
|
|
303
|
-
text = text.replace(/3/g, 'THREE');
|
|
304
|
-
text = text.replace(/4/g, 'FOUR');
|
|
305
|
-
text = text.replace(/5/g, 'FIVE');
|
|
306
|
-
text = text.replace(/6/g, 'SIX');
|
|
307
|
-
text = text.replace(/7/g, 'SEVEN');
|
|
308
|
-
text = text.replace(/8/g, 'EIGHT');
|
|
309
|
-
text = text.replace(/9/g, 'NINE');
|
|
310
|
-
}
|
|
311
|
-
text = text.replace(/[^A-Z]/g, '');
|
|
312
|
-
|
|
313
|
-
return this.segment(text);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Call this method to process one submessage from a longer message. This is
|
|
318
|
-
* part of code to generate a message as the Enigma would have been used.
|
|
319
|
-
*
|
|
320
|
-
* @param {string} indicator the three letter code that can be sent to
|
|
321
|
-
* reference the configuration of the Enigma. These will be used to cross
|
|
322
|
-
* reference the message to the machine configuration on the key sheet
|
|
323
|
-
* @param {String} text the cleaned up string to be encoded
|
|
324
|
-
*
|
|
325
|
-
* @returns {Object} the generated message part
|
|
326
|
-
* @property {String} key a randomly chosen key, this would be transmitted
|
|
327
|
-
* with the message
|
|
328
|
-
* @property {String} enc the encoded start position for the message. This
|
|
329
|
-
* was encoded using the randomly chosen key. This was sent with the
|
|
330
|
-
* message
|
|
331
|
-
* @property {String} text the message text encoded using the unencoded
|
|
332
|
-
* start position. The first five letters of this text string included the
|
|
333
|
-
* unencrypted key identifier.
|
|
334
|
-
* @property {String} start the the unencoded start position. This was not
|
|
335
|
-
* sent with the message but s included here to verify an implementation of
|
|
336
|
-
* this method.
|
|
337
|
-
* @property {String} clear the message before being encoded. Given to
|
|
338
|
-
* verify the value if data is being used to test the Enigma system
|
|
339
|
-
* implementation
|
|
340
|
-
*/
|
|
341
|
-
encodeOnePart(indicator, text) {
|
|
342
|
-
var key = this.choose(3, STANDARD_ALPHABET).join('');
|
|
343
|
-
var start = this.choose(3, STANDARD_ALPHABET).join('');
|
|
344
|
-
var enc = this.enigma.encode(key, start);
|
|
345
|
-
var clear = text;
|
|
346
|
-
|
|
347
|
-
text = text.replace(/ /g, '');
|
|
348
|
-
text = this.enigma.encode(start, text);
|
|
349
|
-
text = this.segment(text);
|
|
350
|
-
text = indicator + ' ' + text
|
|
351
|
-
|
|
352
|
-
return {key, enc, start, text, clear};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Call this method to generate a full message based on the data in a key
|
|
357
|
-
* sheet. The message constructed reflects an actual message as the Enigma
|
|
358
|
-
* was really used. An individual message cannot be more than 250
|
|
359
|
-
* characters, so longer messages are broken into multiple parts. each one
|
|
360
|
-
* encoded with a unique key
|
|
361
|
-
*
|
|
362
|
-
* @param {Object} sheet a key sheet generated using the generateKeySheet
|
|
363
|
-
* method
|
|
364
|
-
* @returns {Array} a list of encoded sub messages
|
|
365
|
-
*/
|
|
366
|
-
generateMessage(sheet) {
|
|
367
|
-
var dayIdx = Math.floor(Math.random() * 30);
|
|
368
|
-
var day = sheet[dayIdx]
|
|
369
|
-
var indicator = this.chooseOne(day.indicators);
|
|
370
|
-
var count = Math.floor(Math.random() * 4) + 1;
|
|
371
|
-
|
|
372
|
-
this.enigma.configure({plugs: day.plugs, rotors: day.rotors, ringSettings: day.ringSettings})
|
|
373
|
-
var text = this.cleanMessage(this.generateSentences(count));
|
|
374
|
-
|
|
375
|
-
var indicator = this.chooseOne(day.indicators);
|
|
376
|
-
indicator = (this.choose(2, [...STANDARD_ALPHABET]).join('') + indicator).toUpperCase();
|
|
377
|
-
|
|
378
|
-
var messageParts = [];
|
|
379
|
-
while (text.length) {
|
|
380
|
-
var segment = text.slice(0, 245);
|
|
381
|
-
if (text.length)
|
|
382
|
-
text = text.slice(246);
|
|
383
|
-
|
|
384
|
-
messageParts.push(segment);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
var messages = messageParts.map(function(part) {
|
|
388
|
-
return this.encodeOnePart(indicator, part)
|
|
389
|
-
}, this);
|
|
390
|
-
|
|
391
|
-
return messages;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Call this method to generate a given number of messages based on a
|
|
396
|
-
* generated key sheet
|
|
397
|
-
*
|
|
398
|
-
* @param {Object} sheet the generated key sheet
|
|
399
|
-
* @param {Number} count the number of messages to generate
|
|
400
|
-
*
|
|
401
|
-
* @returns {Array} the list of generated messages
|
|
402
|
-
*/
|
|
403
|
-
generateMessages(sheet, count) {
|
|
404
|
-
var result = [];
|
|
405
|
-
for (var idx = 0; idx < count; idx++) {
|
|
406
|
-
result.push(this.generateMessage(sheet));
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return result;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Call this method to generate some random text encoded with a random
|
|
414
|
-
* Enigma configuration. The random text will be a few sentences from
|
|
415
|
-
* Hamlet.
|
|
416
|
-
*
|
|
417
|
-
* @param {Object} settings alternate details to define the Enigma
|
|
418
|
-
* configuration
|
|
419
|
-
* @property {Array.<String>} [rotors] alternate list of rotors to choose
|
|
420
|
-
* from
|
|
421
|
-
* @property {Array.<String>} [fixed] an array of fixed rotors to pick from
|
|
422
|
-
* @property {Array.<String>} [reflectors] an array of reflectors to choose
|
|
423
|
-
* from
|
|
424
|
-
*
|
|
425
|
-
* @returns {Object} Details of the generated text
|
|
426
|
-
* @property {EnigmaConfiguration} setup how the Enigma was configured to
|
|
427
|
-
* encode the message
|
|
428
|
-
* @property {String} start three letter string with the starting rotor
|
|
429
|
-
* offsets used to encode the string.
|
|
430
|
-
* @property {String} message the encoded text
|
|
431
|
-
* @property {String} clear the unencoded text
|
|
432
|
-
*/
|
|
433
|
-
generateEncodedText(settings) {
|
|
434
|
-
var reflectors = settings.reflectors || ['A', 'B', 'C'];
|
|
435
|
-
var reflector = this.pickOne(reflectors);
|
|
436
|
-
var enigma = new Enigma({reflector: reflector});
|
|
437
|
-
var setup = this.generateEnigmaSetup(settings);
|
|
438
|
-
var count = Math.floor(Math.random() * 3) + 2;
|
|
439
|
-
|
|
440
|
-
setup.reflector = reflector;
|
|
441
|
-
|
|
442
|
-
var text = this.cleanMessage(this.generateSentences(count));
|
|
443
|
-
var start = this.choose(settings.fixed ? 4 : 3, STANDARD_ALPHABET).join('');
|
|
150
|
+
generateMessage(enigma) {
|
|
151
|
+
let count = Random.random(3) + 2;
|
|
444
152
|
|
|
445
|
-
|
|
153
|
+
let decoded = this.cleanMessage(this.generateSentences(count));
|
|
154
|
+
let start = Random.choose(enigma.rotors.length, [...STANDARD_ALPHABET]).join('');
|
|
446
155
|
|
|
447
|
-
|
|
448
|
-
var encoded = enigma.encode(start, text);
|
|
156
|
+
let encoded = enigma.encode(start, decoded);
|
|
449
157
|
|
|
450
|
-
return {
|
|
158
|
+
return {start, encoded, decoded, configuration: enigma.configuration};
|
|
451
159
|
}
|
|
452
160
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Defines the options for generating a random Enigma configuration
|
|
4
|
+
*/
|
|
5
|
+
type GeneratorSetup = {
|
|
6
|
+
/** if specified, limits the rotors to select from, defaults to the unfixed
|
|
7
|
+
* rotors in the inventory */
|
|
8
|
+
rotors?: string[];
|
|
9
|
+
/** if true a random fixed router will be chosen. If it is an array then one
|
|
10
|
+
* of the specified rotors will be chosen */
|
|
11
|
+
fixed?: boolean|string[];
|
|
12
|
+
/** */
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type RouterInventorySpec = {
|
|
16
|
+
/* a string specifying the connector mapping. The index
|
|
17
|
+
* of the string is the logical coordinate of the connector, the character
|
|
18
|
+
* at that index is the output connector. To be exact, it would be the
|
|
19
|
+
* position of that character in the given alphabet. So, in the map '
|
|
20
|
+
* EKMFLGDQVZNTOWYHXUSPAIBRCJ', input connector 0 would map to output
|
|
21
|
+
* connector 4 and input connector 1 would map to output connector 10.
|
|
22
|
+
* Remember that the connectors are numbered starting at 0.*/
|
|
23
|
+
map: string;
|
|
24
|
+
/* a string of characters representing the
|
|
25
|
+
* turnover locations on the disk. These letter would be the value shown in
|
|
26
|
+
* the window to during turnover. In Rotor I this is 'Q' in rotors VI, VII,
|
|
27
|
+
* and VIII there are two turnover locations, 'M' and 'Z'. Use a empty
|
|
28
|
+
* string if this is a fixed rotor */
|
|
29
|
+
turnovers: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* This is a simplified version of an Enigma configuration.
|
|
34
|
+
*/
|
|
35
|
+
type SimplifiedConfiguration = {
|
|
36
|
+
rotors: string[];
|
|
37
|
+
/** an array of zero based offsets for the ring setting for each rotor */
|
|
38
|
+
ringSettings: number[];
|
|
39
|
+
/** ten pairs of plug setups */
|
|
40
|
+
plugs: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One day of a monthly key sheet
|
|
45
|
+
*/
|
|
46
|
+
type KeySheetLine = {
|
|
47
|
+
/** the day of the month */
|
|
48
|
+
day: number;
|
|
49
|
+
/** the rotors for the day */
|
|
50
|
+
rotors: string[];
|
|
51
|
+
/** an array of zero based offsets for the ring setting for each rotor */
|
|
52
|
+
ringSettings: number[];
|
|
53
|
+
/** ten pairs of plug setups */
|
|
54
|
+
plugs: string;
|
|
55
|
+
/** an array of four three letter strings. This will be unique across a key sheet */
|
|
56
|
+
indicators: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Defines one part of a message. Long messages may be broken into smaller parts.
|
|
61
|
+
*/
|
|
62
|
+
type MessagePart = {
|
|
63
|
+
/** a random key for this message chosen by the operator */
|
|
64
|
+
key: string;
|
|
65
|
+
|
|
66
|
+
/** the starting position for the rotors, encoded using the chosen key */
|
|
67
|
+
enc: string;
|
|
68
|
+
|
|
69
|
+
/** the message text encoded using the unencoded start position. The first
|
|
70
|
+
* five letters of this text string included the unencrypted key identifier*/
|
|
71
|
+
text: string;
|
|
72
|
+
|
|
73
|
+
/** the unencoded start position */
|
|
74
|
+
start: string;
|
|
75
|
+
|
|
76
|
+
/** The clear text */
|
|
77
|
+
clear: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type KeyBookMessage = {
|
|
81
|
+
configuration: SimplifiedConfiguration;
|
|
82
|
+
parts: MessagePart[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type GeneratedMessage = {
|
|
86
|
+
/** three letter start position for the message */
|
|
87
|
+
start: string;
|
|
88
|
+
/** the encoded string */
|
|
89
|
+
encoded: string;
|
|
90
|
+
/** the decoded string */
|
|
91
|
+
decoded: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type Model = ("I" | "M3" | "M4");
|
|
95
|
+
|
|
96
|
+
type ModelOptions = {
|
|
97
|
+
rotors: string[];
|
|
98
|
+
fixed: string[];
|
|
99
|
+
reflectors: string[];
|
|
100
|
+
}
|
package/lib/generator/index.js
CHANGED