@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,452 @@
|
|
|
1
|
+
|
|
2
|
+
import Enigma from '../enigma/Enigma.js';
|
|
3
|
+
import '../enigma/standardInventory.js';
|
|
4
|
+
import sentences from './hamlet.js'
|
|
5
|
+
import inventory from '../enigma/Inventory.js';
|
|
6
|
+
import { STANDARD_ALPHABET } from '../enigma/consts.js';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} EnigmaConfiguration specifies a configuration for the
|
|
11
|
+
* Enigma model M3. This assumes an Enigma with standard reflector B
|
|
12
|
+
* @property {Array.<String>} rotors an array of three rotor names
|
|
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
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export default class Generator {
|
|
20
|
+
constructor(embellish) {
|
|
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
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Call this method to chose a random item from a list. The item is not
|
|
55
|
+
* removed.
|
|
56
|
+
*
|
|
57
|
+
* @param {Array} list the list of items to choose from
|
|
58
|
+
*
|
|
59
|
+
* @returns {*} the chosen item
|
|
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
|
|
93
|
+
*/
|
|
94
|
+
choose(count, list){
|
|
95
|
+
var result = [];
|
|
96
|
+
for(var idx = 0; idx < count; idx++) {
|
|
97
|
+
result.push(this.chooseOne(list));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Call this method to randmply pick a given number of item pairs. The items
|
|
105
|
+
* will be removed from the list.
|
|
106
|
+
*
|
|
107
|
+
* @param {Number} count the number of pairs to pick
|
|
108
|
+
* @param {Array} list the list of items to choose from
|
|
109
|
+
*
|
|
110
|
+
* @returns {Array.<Array>} the item pairs chosen. Each pair is an array of
|
|
111
|
+
* two items from the list
|
|
112
|
+
*/
|
|
113
|
+
pickPairs(count, list) {
|
|
114
|
+
var result = [];
|
|
115
|
+
|
|
116
|
+
for(var idx = 0; idx < count; idx++) {
|
|
117
|
+
result.push(this.pickPair(list));
|
|
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
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Call this method to generate the given number of sentences. The sentences
|
|
145
|
+
* are pulled from the text of Hamlet.
|
|
146
|
+
*
|
|
147
|
+
* @param {Number} count the number of sentences
|
|
148
|
+
* @returns {String} the sentences separated by a ' ';
|
|
149
|
+
*/
|
|
150
|
+
generateSentences(count) {
|
|
151
|
+
var start = Math.floor(Math.random() * sentences.length) + count;
|
|
152
|
+
|
|
153
|
+
return sentences.slice(start, start + count).join(' ');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Call this method to create a three letter string as an identifier for a
|
|
158
|
+
* a day in a key sheet
|
|
159
|
+
*
|
|
160
|
+
* @returns {String} the string
|
|
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]);
|
|
169
|
+
|
|
170
|
+
this.indicators[indicator] = indicator;
|
|
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
|
|
191
|
+
*/
|
|
192
|
+
generateEnigmaSetup(settings = {}) {
|
|
193
|
+
var rotorInventory = settings.rotors || inventory.getRotorNames();
|
|
194
|
+
var connectors = [...Array(26).keys()];
|
|
195
|
+
var letters = [...STANDARD_ALPHABET];
|
|
196
|
+
|
|
197
|
+
var rotors = this.pick(3, rotorInventory);
|
|
198
|
+
var ringSettings = this.choose(3, connectors);
|
|
199
|
+
var plugsA = this.pickStringPairs(10, letters);
|
|
200
|
+
ringSettings[0]++;
|
|
201
|
+
ringSettings[1]++;
|
|
202
|
+
ringSettings[2]++;
|
|
203
|
+
|
|
204
|
+
var plugs = plugsA.join(' ');
|
|
205
|
+
|
|
206
|
+
if (settings.fixed) {
|
|
207
|
+
rotors.unshift(this.pickOne(settings.fixed));
|
|
208
|
+
ringSettings.push(this.chooseOne(connectors));
|
|
209
|
+
ringSettings[3]++;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {rotors, plugs, ringSettings};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
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
|
+
*
|
|
247
|
+
* @param {Number} days the number of days on the key sheet
|
|
248
|
+
* @returns {Array.<Object>} the array of day objects
|
|
249
|
+
*/
|
|
250
|
+
generateKeySheet(days) {
|
|
251
|
+
var sheet = [];
|
|
252
|
+
this.indicators = {};
|
|
253
|
+
|
|
254
|
+
for(var idx = 0; idx < days; idx++) {
|
|
255
|
+
sheet.push(this.generateDay(idx))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return sheet;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Call this method to break a string into groups of five letters with a
|
|
263
|
+
* space between them.
|
|
264
|
+
*
|
|
265
|
+
* @param {String} text the original text
|
|
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
|
|
285
|
+
*
|
|
286
|
+
* @param {String} text the original text
|
|
287
|
+
* @returns {String} the text that has been normalized to what the Enigma
|
|
288
|
+
* can process
|
|
289
|
+
*/
|
|
290
|
+
cleanMessage(text) {
|
|
291
|
+
text = text.toUpperCase();
|
|
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('');
|
|
444
|
+
|
|
445
|
+
enigma.configure(setup);
|
|
446
|
+
|
|
447
|
+
text = text.replace(/ /g, '');
|
|
448
|
+
var encoded = enigma.encode(start, text);
|
|
449
|
+
|
|
450
|
+
return {setup, message: { key: start, encoded, decoded: text}};
|
|
451
|
+
}
|
|
452
|
+
}
|