@ondoher/enigma 0.1.7 → 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.
@@ -1,3 +1,4 @@
1
+ import { STANDARD_ALPHABET } from "./consts.js";
1
2
  import Encoder from "./Encoder.js";
2
3
 
3
4
  /**
@@ -6,49 +7,18 @@ import Encoder from "./Encoder.js";
6
7
  * values here are specified in physical space.
7
8
  */
8
9
  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
10
  /**
26
11
  * This is the constructor for the rotor.
27
12
  *
28
13
  * @param {String} name the name of the rotor; under normal circumstances
29
14
  * this will be the string 'rotor-' plus the standard name for the rotor,
30
15
  * for example 'rotor-IV'
31
- * @param {Object} settings an object that contains the various options that
16
+ * @param {RotorSetup} settings an object that contains the various options that
32
17
  * 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
18
  */
49
19
  constructor(name, settings) {
50
20
  super(name, settings);
51
- var { map, turnovers = '', ringSetting = 0} = settings
21
+ var { map = STANDARD_ALPHABET, turnovers = '', ringSetting = 0} = settings
52
22
 
53
23
  this.map = [...map];
54
24
  this.rightMap = this.makeMap(map);
@@ -105,7 +75,7 @@ export default class Rotor extends Encoder {
105
75
  var result = this.normalize(output - this.offset);
106
76
 
107
77
  this.fire(evName, this.name,
108
- `${evName} ${this.name}, input: ${input} ouput: ${result} relative input: ${index}, relative output: ${output} rotation: ${this.offset}`,
78
+ `${evName} ${this.name}, input: ${input} output: ${result} relative input: ${index}, relative output: ${output} rotation: ${this.offset}`,
109
79
  {
110
80
  input : input,
111
81
  output: result,
@@ -125,7 +95,7 @@ export default class Rotor extends Encoder {
125
95
  */
126
96
  step() {
127
97
  // Because the turnover notch is attached to the outer ring, and the
128
- // logical coordinates is based on the wiring, the logical position of
98
+ // logical coordinates are based on the wiring, the logical position of
129
99
  // the notch for turnover needs to be adjusted to remove the ring offset.
130
100
  this.offset = this.normalize(this.offset + 1);
131
101
  var turnoverOffset = this.normalize(this.offset + this.ringOffset);
@@ -5,7 +5,7 @@ import inventory from'./Inventory.js';
5
5
  * I, M3 and M4
6
6
  */
7
7
 
8
- export var entryDiscDefintions = {
8
+ export var entryDiscDefinitions = {
9
9
  default: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
10
10
  };
11
11
 
@@ -74,7 +74,7 @@ function addReflector(name) {
74
74
  inventory.addReflector(name, reflectorDefinitions[name]);
75
75
  }
76
76
 
77
- inventory.addEntryDisc('default', entryDiscDefintions.default);
77
+ inventory.addEntryDisc('default', entryDiscDefinitions.default);
78
78
 
79
79
  addRotor('I');
80
80
  addRotor('II');
@@ -6,11 +6,6 @@ import { enigmaData } from './EnigmaData.js';
6
6
 
7
7
  describe('Enigma Test Cases', function() {
8
8
  var enigma;
9
- /*
10
- describe('Configuration', function() {
11
-
12
- })
13
- */
14
9
 
15
10
  function messageLoop(messages, which, cb) {
16
11
  return messages.find(function(message) {
@@ -2,7 +2,8 @@ import PlugBoard from "../PlugBoard.js";
2
2
  import { plugBoardData } from './PlugBoardData.js';
3
3
 
4
4
  describe('PlugBoard Test Cases', function() {
5
- var plugBoard;
5
+ /** @type {PlugBoard} */
6
+ let plugBoard;
6
7
 
7
8
  describe('Encode', function() {
8
9
  beforeEach(function() {
@@ -13,32 +14,32 @@ describe('PlugBoard Test Cases', function() {
13
14
 
14
15
  it('passes through if no plugs defined', function() {
15
16
  plugBoard.configure()
16
- var alphabet = [...plugBoardData.alphabet];
17
+ let alphabet = [...plugBoardData.alphabet];
17
18
  alphabet.forEach(function(letter) {
18
- var input = plugBoardData.alphabet.indexOf(letter);
19
- var output = plugBoard.encode('right', input);
19
+ let input = plugBoardData.alphabet.indexOf(letter);
20
+ let output = plugBoard.encode('right', input);
20
21
  expect(output).toBe(input);
21
- var output = plugBoard.encode('left', input);
22
+ output = plugBoard.encode('left', input);
22
23
  expect(output).toBe(input);
23
24
  })
24
25
  });
25
26
 
26
27
  it('should link cabled pairs', function() {
27
- plugBoard.configure({plugs: plugBoardData.plugSettings});
28
- var pairs = plugBoardData.plugSettings.split(' ');
29
- var alphabet = plugBoardData.alphabet;
28
+ plugBoard.configure(plugBoardData.plugSettings);
29
+ let pairs = plugBoardData.plugSettings.split(' ');
30
+ let alphabet = plugBoardData.alphabet;
30
31
  pairs.forEach(function(pair) {
31
- var left = alphabet.indexOf(pair[0]);
32
- var right = alphabet.indexOf(pair[1]);
32
+ let left = alphabet.indexOf(pair[0]);
33
+ let right = alphabet.indexOf(pair[1]);
33
34
 
34
- var output = plugBoard.encode('right', left);
35
+ let output = plugBoard.encode('right', left);
35
36
  expect(output).toBe(right);
36
- var output = plugBoard.encode('right', right);
37
+ output = plugBoard.encode('right', right);
37
38
  expect(output).toBe(left);
38
39
 
39
- var output = plugBoard.encode('left', right);
40
+ output = plugBoard.encode('left', right);
40
41
  expect(output).toBe(left);
41
- var output = plugBoard.encode('left', left);
42
+ output = plugBoard.encode('left', left);
42
43
  expect(output).toBe(right);
43
44
  })
44
45
  })
@@ -41,7 +41,7 @@ describe('Rotor Test Cases', function() {
41
41
  });
42
42
  });
43
43
 
44
- describe('startPositon', function() {
44
+ describe('startPosition', function() {
45
45
  it('no Rotation', function() {
46
46
  rotor.setStartPosition('A');
47
47
  expect(rotor.offset).toBe(0)
@@ -0,0 +1,180 @@
1
+
2
+ import Enigma from '../enigma/Enigma.js';
3
+ import '../enigma/standardInventory.js';
4
+ import sentences from './hamlet.js'
5
+ import { STANDARD_ALPHABET } from '../enigma/consts.js';
6
+ import Random from '../utils/Random.js';
7
+ import Generator from './Generator.js';
8
+
9
+ /**
10
+ * Use this class to generate Enigma key sheets and messages using it. The
11
+ * procedures used were derived from the information at
12
+ * [Enigma Message Procedures](https://www.ciphermachinesandcryptology.com/en/enigmaproc.htm)
13
+ */
14
+ export default class CodeBook {
15
+ /**
16
+ * Constructor for the `CodeBook` class.
17
+ * @param {Enigma} enigma - all encryption will be done using this configured Enigma
18
+ */
19
+ constructor(enigma) {
20
+ this.enigma = enigma
21
+ this.indicators = {};
22
+ this.generator = new Generator();
23
+ }
24
+
25
+ /**
26
+ * Call this method to provide a new configuration to the enigma
27
+ *
28
+ * @param {SimplifiedConfiguration} config
29
+ */
30
+ configure(config) {
31
+ this.enigma.configure(config);
32
+ }
33
+
34
+ reset() {
35
+ this.indicators = {}
36
+ }
37
+
38
+ /**
39
+ * Call this method to create a three letter string as an identifier for a
40
+ * a day in a key sheet
41
+ *
42
+ * @returns {String} the string
43
+ */
44
+ makeIndicator() {
45
+ let letters = [...'abcdefghijklmnopqrstuvwxyzx'];
46
+ let indicator;
47
+
48
+ do {
49
+ indicator = Random.choose(3, letters).join('');
50
+ } while (this.indicators[indicator]);
51
+
52
+ this.indicators[indicator] = indicator;
53
+
54
+ return indicator;
55
+ }
56
+
57
+ /**
58
+ * Call this method to create a single days configuration for a key sheet.
59
+ * This an Enigma configuration plus the other metadata.
60
+ *
61
+ * @param {number} day the day of the month
62
+ *
63
+ * @returns {KeySheetLine} One line of a key sheet
64
+ */
65
+ generateDay(day) {
66
+ let settings = this.generator.generateEnigmaConfiguration();
67
+ let indicators = [];
68
+
69
+ indicators.push(this.makeIndicator());
70
+ indicators.push(this.makeIndicator());
71
+ indicators.push(this.makeIndicator());
72
+ indicators.push(this.makeIndicator());
73
+
74
+ return {day, ...settings, indicators}
75
+ }
76
+
77
+ /**
78
+ * Call this method to construct a key sheet for the given number of days
79
+ *
80
+ * @param {Number} days the number of days on the key sheet
81
+ * @returns {KeySheetLine[]} the array of day objects
82
+ */
83
+ generateKeySheet(days) {
84
+ let sheet = [];
85
+ this.indicators = {};
86
+
87
+ for(let idx = 0; idx < days; idx++) {
88
+ sheet.push(this.generateDay(idx))
89
+ }
90
+
91
+ return sheet;
92
+ }
93
+
94
+ /**
95
+ * Call this method to process one sub-message from a longer message. This is
96
+ * part of code to generate a message as the Enigma would have been used.
97
+ *
98
+ * @param {string[]} indicators the three letter code that can be sent to
99
+ * reference the configuration of the Enigma. These will be used to cross
100
+ * reference the message to the machine configuration on the key sheet
101
+ * @param {String} text the cleaned up string to be encoded
102
+ *
103
+ * @returns {MessagePart} the encoded message segment
104
+ */
105
+ encodeOnePart(indicators, text) {
106
+ let indicator = Random.chooseOne(indicators);
107
+ let paddedIndicator = (Random.choose(2, [...STANDARD_ALPHABET]).join('') + indicator).toUpperCase();
108
+
109
+ let key = Random.choose(3, [...STANDARD_ALPHABET]).join('');
110
+ let start = Random.choose(3, [...STANDARD_ALPHABET]).join('');
111
+ let enc = this.enigma.encode(key, start);
112
+ let clear = text;
113
+
114
+ text = text.replace(/ /g, '');
115
+ text = this.enigma.encode(start, text);
116
+ text = this.generator.groupText(text);
117
+ text = paddedIndicator + ' ' + text
118
+
119
+ return {key, enc, start, text, clear};
120
+ }
121
+
122
+ /**
123
+ * Call this method to generate a full message based on the data in a key
124
+ * sheet. The message constructed reflects an actual message as the Enigma
125
+ * was really used. An individual message cannot be more than 250
126
+ * characters, so longer messages are broken into multiple parts, each one
127
+ * encoded with a unique key
128
+ *
129
+ * @param {KeySheetLine[]} sheet a key sheet generated using generateKeySheet
130
+ * @param {number} [dayIdx] - if provided, specifies the day of the month for the message
131
+ * @param {string} [text] - if provided, this is the text of the message to generate
132
+ * @returns {KeyBookMessage} a list of encoded sub messages
133
+ */
134
+ generateMessage(sheet, dayIdx, text) {
135
+ dayIdx = dayIdx === undefined ? Random.random(sheet.length) : dayIdx;
136
+ let day = sheet[dayIdx]
137
+ let count = Random.random(3) + 2;
138
+
139
+ this.enigma.configure({plugs: day.plugs, rotors: day.rotors, ringSettings: day.ringSettings})
140
+
141
+ text = text ?? this.generator.generateSentences(count);
142
+ text = this.generator.cleanMessage(text);
143
+ let messageParts = [];
144
+
145
+ while (text.length) {
146
+ let segment = text.slice(0, 245);
147
+ if (text.length)
148
+ text = text.slice(246);
149
+
150
+ messageParts.push(segment);
151
+ }
152
+
153
+ let messages = messageParts.map((part) => {
154
+ return this.encodeOnePart(day.indicators, part)
155
+ });
156
+
157
+ return {
158
+ options: this.enigma.configuration,
159
+ parts: messages
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Call this method to generate a given number of messages based on a
165
+ * generated key sheet
166
+ *
167
+ * @param {Object} sheet the generated key sheet
168
+ * @param {Number} count - the number of messages to generate
169
+ *
170
+ * @returns {Array} the list of generated messages
171
+ */
172
+ generateMessages(sheet, count) {
173
+ let result = [];
174
+ for (let idx = 0; idx < count; idx++) {
175
+ result.push(this.generateMessage(sheet));
176
+ }
177
+
178
+ return result;
179
+ }
180
+ }