1 // Written in the D programming language.
2 
3 /**
4  * A library for simulating the Enigma machines.
5  *
6  * Copyright: Copyright Kazuya Takahashi 2016.
7  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8  * Authors:   Kazuya Takahashi
9  */
10 module enigma;
11 
12 private enum size_t N = 26;
13 
14 private template isSomeStringOrDcharRange(T)
15 {
16     import std.range.primitives : isInfinite, isInputRange, ElementType;
17     import std.traits : isSomeString;
18 
19     enum isSomeStringOrDcharRange = isSomeString!T ||
20         (isInputRange!T && !isInfinite!T && is(ElementType!T : dchar));
21 }
22 
23 
24 ///
25 struct Rotor
26 {
27     import boolean_matrix : BSM;
28 
29     immutable BSM!N perm;
30     private immutable bool hasNotch = false;
31     private immutable size_t[] turnovers;
32 
33     /++
34      + Constructs a rotor with no turnover notches.
35      + If ringOffset is `2`, it corresponds to "C-03".
36      +/
37     this()(in auto ref BSM!N perm, size_t ringOffset) pure
38     in
39     {
40         import boolean_matrix : isBijective;
41 
42         assert(perm.isBijective, "Rotor must be bijective.");
43     }
44     body
45     {
46         import boolean_matrix : lowerRotator, upperRotator;
47 
48         this.perm = lowerRotator!N(ringOffset) * perm * upperRotator!N(ringOffset);
49     }
50 
51     /++
52      + Constructs a rotor with one turnover notch.
53      + If turnover is `1`, the next rotor steps when this rotor steps from B to C.
54      + If ringOffset is `2`, it corresponds to "C-03".
55      +/
56     this()(in auto ref BSM!N perm, size_t turnover, size_t ringOffset) pure
57     {
58         this(perm, ringOffset);
59         this.turnovers = [turnover % N];
60         hasNotch = true;
61     }
62 
63     /++
64      + Constructs a rotor with two turnover notches.
65      + If turnover1 is `1` and turnover2 is `25`, the next rotor steps
66      + when this rotor steps from B to C and from Z to A.
67      + If ringOffset is `2`, it corresponds to "C-03".
68      +/
69     this()(in auto ref BSM!N perm, size_t turnover1, size_t turnover2, size_t ringOffset) pure
70     {
71         import std.algorithm.sorting : sort;
72         import std.array : array;
73 
74         this(perm, ringOffset);
75         this.turnovers = [turnover1 % N, turnover2 % N].sort().array;
76         hasNotch = true;
77     }
78 
79     /++
80      + Constructs a rotor with five turnover notches.
81      + If turnover1 is `1` and turnover2 is `25`, the next rotor steps
82      + when this rotor steps from B to C and from Z to A.
83      + If ringOffset is `2`, it corresponds to "C-03".
84      +/
85     this()(in auto ref BSM!N perm, size_t turnover1, size_t turnover2, size_t turnover3, size_t turnover4, size_t turnover5, size_t ringOffset) pure
86     {
87         import std.algorithm.sorting : sort;
88         import std.array : array;
89 
90         this(perm, ringOffset);
91         this.turnovers = [turnover1 % N, turnover2 % N, turnover3 % N, turnover4 % N, turnover5 % N].sort().array;
92         hasNotch = true;
93     }
94 }
95 
96 /++
97  + A convenience function to make a rotor with no turnover notches
98  + from a forward substitution.
99  + If ringSetting is `'C'`, it corresponds to "C-03".
100  +/
101 auto rotor(S)(in S forwardSubstitution, dchar ringSetting) pure if (isSomeStringOrDcharRange!S)
102 in
103 {
104     import std.algorithm.comparison : isPermutation;
105     import std.algorithm.iteration : map;
106     import std.ascii : isAlpha, toUpper;
107     import std.range : iota, walkLength;
108 
109     assert(forwardSubstitution.walkLength == N, "Bad length.");
110     assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
111     assert(ringSetting.isAlpha, "Bad ring setting.");
112 }
113 body
114 {
115     import std.algorithm.iteration : map;
116     import std.array : array;
117     import std.ascii : toUpper;
118     import boolean_matrix : permutation;
119 
120     return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N,
121         ringSetting.toUpper - 'A');
122 }
123 
124 /++
125  + A convenience function to make a rotor with one turnover notch
126  + from a forward substitution.
127  + If turnover is `'B'`, the next rotor steps when this rotor steps from B to C.
128  + If ringSetting is `'C'`, it corresponds to "C-03".
129  +/
130 auto rotor(S)(in S forwardSubstitution, dchar turnover, dchar ringSetting) pure if (isSomeStringOrDcharRange!S)
131 in
132 {
133     import std.algorithm.comparison : isPermutation;
134     import std.algorithm.iteration : map;
135     import std.ascii : isAlpha, toUpper;
136     import std.range : iota, walkLength;
137 
138     assert(forwardSubstitution.walkLength == N, "Bad length.");
139     assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
140     assert(turnover.isAlpha, "Bad turnover setting.");
141     assert(ringSetting.isAlpha, "Bad ring setting.");
142 }
143 body
144 {
145     import std.algorithm.iteration : map;
146     import std.array : array;
147     import std.ascii : toUpper;
148     import boolean_matrix : permutation;
149 
150     return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N,
151         turnover.toUpper - 'A', ringSetting.toUpper - 'A');
152 }
153 
154 /++
155  + A convenience function to make a rotor with two turnover notches
156  + from a forward substitution.
157  + If turnover1 is `'B'` and turnover2 is `'Z'`, the next rotor steps
158  + when this rotor steps from B to C and from Z to A.
159 + If ringSetting is `'C'`, it corresponds to "C-03".
160  +/
161 auto rotor(S)(in S forwardSubstitution, dchar turnover1, dchar turnover2, dchar ringSetting) pure if (isSomeStringOrDcharRange!S)
162 in
163 {
164     import std.algorithm.comparison : isPermutation;
165     import std.algorithm.iteration : map;
166     import std.ascii : isAlpha, toUpper;
167     import std.range : iota, walkLength;
168 
169     assert(forwardSubstitution.walkLength == N, "Bad length.");
170     assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
171     assert(turnover1.isAlpha, "Bad turnover setting.");
172     assert(turnover2.isAlpha, "Bad turnover setting.");
173     assert(ringSetting.isAlpha, "Bad ring setting.");
174 }
175 body
176 {
177     import std.algorithm.iteration : map;
178     import std.array : array;
179     import std.ascii : toUpper;
180     import boolean_matrix : permutation;
181 
182     return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N,
183         turnover1.toUpper - 'A', turnover2.toUpper - 'A', ringSetting.toUpper - 'A');
184 }
185 
186 /++
187  + A convenience function to make a rotor with five turnover notches
188  + from a forward substitution.
189  + If turnover1 is `'B'` and turnover2 is `'Z'`, the next rotor steps
190  + when this rotor steps from B to C and from Z to A.
191  + If ringSetting is `'C'`, it corresponds to "C-03".
192  +/
193 auto rotor(S)(in S forwardSubstitution, dchar turnover1, dchar turnover2, dchar turnover3, dchar turnover4, dchar turnover5, dchar ringSetting) pure if (isSomeStringOrDcharRange!S)
194 in
195 {
196     import std.algorithm.comparison : isPermutation;
197     import std.algorithm.iteration : map;
198     import std.ascii : isAlpha, toUpper;
199     import std.range : iota, walkLength;
200 
201     assert(forwardSubstitution.walkLength == N, "Bad length.");
202     assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
203     assert(turnover1.isAlpha, "Bad turnover setting.");
204     assert(turnover2.isAlpha, "Bad turnover setting.");
205     assert(turnover3.isAlpha, "Bad turnover setting.");
206     assert(turnover4.isAlpha, "Bad turnover setting.");
207     assert(turnover5.isAlpha, "Bad turnover setting.");
208     assert(ringSetting.isAlpha, "Bad ring setting.");
209 }
210 body
211 {
212     import std.algorithm.iteration : map;
213     import std.array : array;
214     import std.ascii : toUpper;
215     import boolean_matrix : permutation;
216 
217     return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N,
218         turnover1.toUpper - 'A', turnover2.toUpper - 'A', turnover3.toUpper - 'A',
219         turnover4.toUpper - 'A', turnover5.toUpper - 'A', ringSetting.toUpper - 'A');
220 }
221 
222 ///
223 struct EntryWheel
224 {
225     import boolean_matrix : BSM;
226 
227     immutable BSM!N perm;
228     ///
229     this()(in auto ref BSM!N perm) pure
230     in
231     {
232         import boolean_matrix : isBijective;
233 
234         assert(perm.isBijective, "Entry wheel must be bijective.");
235     }
236     body
237     {
238         this.perm = cast(immutable) perm;
239     }
240 
241     alias perm this;
242 }
243 
244 /++
245  + A convenience function to make an entry wheel from a substitution.
246  +/
247 auto entryWheel(S)(in S backwardSubstitution) pure if (isSomeStringOrDcharRange!S)
248 in
249 {
250     import std.algorithm.comparison : isPermutation;
251     import std.algorithm.iteration : map;
252     import std.ascii : toUpper;
253     import std.range : iota, walkLength;
254 
255     assert(backwardSubstitution.walkLength == N, "Bad length.");
256     assert(N.iota.isPermutation(backwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
257 }
258 body
259 {
260     import std.algorithm.iteration : map;
261     import std.array : array;
262     import std.ascii : toUpper;
263     import boolean_matrix : permutation, transpose;
264 
265     return EntryWheel(backwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N.transpose);
266 }
267 
268 ///
269 struct Plugboard
270 {
271     import boolean_matrix : BSM;
272 
273     immutable BSM!N perm;
274     ///
275     this()(in auto ref BSM!N perm) pure
276     in
277     {
278         import boolean_matrix : isBijective, isSymmetric;
279 
280         assert(perm.isBijective, "Plugboard must be bijective.");
281         assert(perm.isSymmetric, "Plugboard must be symmetric.");
282     }
283     body
284     {
285         this.perm = cast(immutable) perm;
286     }
287 
288     alias perm this;
289 }
290 
291 /++
292  + A convenience function to make a plugboard from a substitution.
293  +/
294 auto plugboard(S)(in S substitution) pure if (isSomeStringOrDcharRange!S)
295 in
296 {
297     import std.algorithm.comparison : isPermutation;
298     import std.algorithm.iteration : map;
299     import std.ascii : toUpper;
300     import std.range : iota, walkLength;
301 
302     assert(substitution.walkLength == N, "Bad length.");
303     assert(N.iota.isPermutation(substitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
304 }
305 body
306 {
307     import std.algorithm.iteration : map;
308     import std.array : array;
309     import std.ascii : toUpper;
310     import boolean_matrix : permutation;
311 
312     return Plugboard(substitution.map!toUpper.map!"a-'A'".array.permutation!N);
313 }
314 
315 ///
316 struct Reflector
317 {
318     import boolean_matrix : BSM;
319 
320     immutable BSM!N perm;
321     ///
322     this()(in auto ref BSM!N perm, size_t ringOffset) pure
323     in
324     {
325         import boolean_matrix : isBijective, isIrreflexive, isSymmetric;
326 
327         assert(perm.isBijective, "Reflector must be bijective.");
328         assert(perm.isIrreflexive, "Reflector must be irreflexive.");
329         assert(perm.isSymmetric, "Reflector must be bijective.");
330     }
331     body
332     {
333         import boolean_matrix : lowerRotator, upperRotator;
334 
335         this.perm = lowerRotator!N(ringOffset) * perm * upperRotator!N(ringOffset);
336     }
337 
338     alias perm this;
339 }
340 
341 /++
342  + A convenience function to make a reflector from a substitution.
343  +/
344 auto reflector(S)(in S substitution, dchar ringSetting = 'A') pure if (isSomeStringOrDcharRange!S)
345 in
346 {
347     import std.algorithm.comparison : isPermutation;
348     import std.algorithm.iteration : map;
349     import std.algorithm.searching : canFind;
350     import std.ascii : isAlpha, toUpper;
351     import std.range : iota, walkLength, zip;
352 
353     assert(substitution.walkLength == N, "Bad length.");
354     assert(!N.iota.zip(substitution.map!toUpper.map!"a-'A'").canFind!"a[0]==a[1]", "Self-loop found.");
355     assert(N.iota.isPermutation(substitution.map!toUpper.map!"a-'A'"), "Bad permutation.");
356     assert(ringSetting.isAlpha, "Bad ring setting.");
357 }
358 body
359 {
360     import std.algorithm.iteration : map;
361     import std.array : array;
362     import std.ascii : toUpper;
363     import boolean_matrix : permutation;
364 
365     return Reflector(substitution.map!toUpper.map!"a-'A'".array.permutation!N,
366         ringSetting.toUpper - 'A');
367 }
368 
369 /// Currently machines with the double-stepping mechanism are available.
370 struct Enigma(size_t rotorN, bool fixedFinalRotor = false, bool hasPlugboard = true, bool settableReflectorPos = false)
371 {
372     import boolean_matrix : BSM;
373     private immutable BSM!N composedInputPerm;
374     private immutable Rotor[rotorN] rotors;
375     private immutable BSM!N reflector;
376     private size_t[rotorN] rotationStates;
377 
378     import meta_workaround : Repeat;
379 
380     ///
381     this(in EntryWheel entryWheel, in Repeat!(rotorN, Rotor) rotors,
382         in Reflector reflector, in dchar[rotorN] rotorStartPos)
383     in
384     {
385         foreach (dchar c; rotorStartPos)
386         {
387             import std.ascii : isAlpha;
388 
389             assert(c.isAlpha, "Bad start position.");
390         }
391     }
392     body
393     {
394         foreach (i, ref e; rotationStates)
395         {
396             import std.ascii : toUpper;
397 
398             e = rotorStartPos[i].toUpper - 'A';
399         }
400 
401         this.composedInputPerm = cast(immutable) entryWheel.perm;
402         this.rotors[] = cast(immutable)[rotors][];
403         this.reflector = cast(immutable) reflector.perm;
404     }
405 
406     ///
407     static if (settableReflectorPos)
408     {
409         this(in EntryWheel entryWheel, in Repeat!(rotorN, Rotor) rotors,
410             in Reflector reflector, in dchar[rotorN] rotorStartPos,
411             dchar reflectorPos)
412         in
413         {
414             import std.ascii : isAlpha;
415 
416             assert(reflectorPos.isAlpha, "Bad reflector position.");
417         }
418         body
419         {
420             this(entryWheel, rotors, reflector, rotorStartPos);
421 
422             import std.ascii : toUpper;
423             import boolean_matrix : lowerRotator, upperRotator;
424 
425             immutable refOffset = reflectorPos.toUpper - 'A';
426             this.reflector = upperRotator!N(refOffset) * reflector * lowerRotator!N(refOffset);
427         }
428     }
429 
430     ///
431     static if (hasPlugboard)
432     {
433         this(in Plugboard plugboard, in EntryWheel entryWheel,
434             in Repeat!(rotorN, Rotor) rotors,
435             in Reflector reflector, in dchar[rotorN] rotorStartPos)
436         {
437             this(entryWheel, rotors, reflector, rotorStartPos);
438             this.composedInputPerm = cast(immutable) (entryWheel * plugboard);
439         }
440 
441         ///
442         static if (settableReflectorPos)
443         {
444             this(in Plugboard plugboard, in EntryWheel entryWheel,
445                 in Repeat!(rotorN, Rotor) rotors,
446                 in Reflector reflector, in dchar[rotorN] rotorStartPos,
447                 dchar reflectorPos)
448             in
449             {
450                 import std.ascii : isAlpha;
451 
452                 assert(reflectorPos.isAlpha, "Bad reflector position.");
453             }
454             body
455             {
456                 this(plugboard, entryWheel, rotors, reflector, rotorStartPos);
457 
458                 import std.ascii : toUpper;
459                 import boolean_matrix : lowerRotator, upperRotator;
460 
461                 immutable refOffset = reflectorPos.toUpper - 'A';
462                 this.reflector = upperRotator!N(refOffset) * reflector * lowerRotator!N(refOffset);
463             }
464         }
465     }
466 
467     private void step()
468     {
469         enum movableRotorN = fixedFinalRotor ? rotorN - 1 : rotorN;
470         bool[movableRotorN] stepFlag;
471 
472         stepFlag[0] = true;
473 
474         // Handles double stepping
475         foreach (rotorID; 0 .. movableRotorN - 1)
476         {
477             import std.algorithm.searching : canFind;
478 
479             if (rotors[rotorID].turnovers.canFind(rotationStates[rotorID]))
480             {
481                 stepFlag[rotorID] = true;
482                 stepFlag[rotorID + 1] = true;
483             }
484         }
485 
486         foreach (rotorID, e; stepFlag)
487         {
488             if (e)
489             {
490                 rotationStates[rotorID] = (rotationStates[rotorID] + 1) % N;
491             }
492         }
493     }
494 
495     import boolean_matrix : BCV;
496 
497     /+
498      + fwdPerm = (Um*Rm*Lm)*(Um-1*Rm-1*Lm-1)*...*(U0*R0*L0)*P
499      +         = Um*(Rm*Lm*Um-1)*(Rm-1*Lm-1*Um-2)*...*(R0*L0)*P
500      +         = Um*(Rm*RELm)*(Rm-1*RELm-1)*...*(R0*L0)*P
501      +/
502     private BCV!N composeForward(in ref BCV!N inputVec, size_t rotorID)
503     {
504         import boolean_matrix : lowerRotator, upperRotator;
505 
506         immutable ptrdiff_t x = rotorID == 0 ? rotationStates[0] : rotationStates[rotorID] - rotationStates[rotorID - 1];
507         immutable relRotator = x > 0 ? lowerRotator!N(x) : upperRotator!N(-x);
508         immutable composedVec = rotors[rotorID].perm * (relRotator * inputVec);
509         return rotorID == rotorN - 1 ? upperRotator!N(rotationStates[rotorID]) * composedVec
510             : composeForward(composedVec, rotorID + 1);
511     }
512 
513     /+
514      + bwdPerm = [(Um*Rm*Lm)*(Um-1*Rm-1*Lm-1)*...*(U0*R0*L0)*P]^-1
515      +         = [Um*(Rm*RELm)*(Rm-1*RELm-1)*...*(R0*L0)*P]^-1
516      +         = P^-1*(R0*L0)^-1*...(Rm-1*RELm-1)^-1*(Rm*RELm)^-1*Um^-1
517      +         = P^-1*(U0*R0^-1)*...(RELm-1^-1*Rm-1^-1)*(RELm^-1*Rm^-1)*Lm
518      +/
519     private BCV!N composeBackward(in ref BCV!N inputVec, size_t rotorID)
520     {
521         import boolean_matrix : lowerRotator, transpose, upperRotator;
522 
523         immutable ptrdiff_t x = rotorID == 0 ? rotationStates[0] : rotationStates[rotorID] - rotationStates[rotorID - 1];
524         immutable relRotatorInv = x > 0 ? upperRotator!N(x) : lowerRotator!N(-x);
525         immutable iv = rotorID == rotorN - 1 ? lowerRotator!N(rotationStates[rotorN - 1]) * inputVec : inputVec;
526         immutable composedVec =  relRotatorInv * (rotors[rotorID].perm.transpose * iv);
527         return rotorID == 0 ? composedVec : composeBackward(composedVec, rotorID - 1);
528    }
529     
530     private auto process(size_t keyInputID)
531     out (r)
532     {
533         assert(r >= 0);
534     }
535     body
536     {
537         step();
538 
539         import boolean_matrix : transpose, BCV;
540 
541         immutable composedInput = composedInputPerm * BCV!N.e(keyInputID);
542         immutable reflectorOutput = reflector * composeForward(composedInput, 0);
543         immutable composedOutput = composedInputPerm.transpose * composeBackward(reflectorOutput,rotorN - 1) ;
544         import std.algorithm.searching : countUntil;
545 
546         immutable r = composedOutput[].countUntil!"a";
547         return r;
548     }
549 
550     /// Enciphers only an alphabetical character through the current Enigma machine.
551     dchar opCall(dchar keyInput)
552     {
553         import std.ascii : isAlpha, toUpper;
554 
555         return keyInput.isAlpha ? process(keyInput.toUpper - 'A') + 'A' : keyInput;
556     }
557 }
558 
559 /// Enigma I 'Wehrmacht', which has three rotor slots.
560 alias EnigmaI = Enigma!3;
561 
562 /// Enigma M3, which has three rotor slots.
563 alias EnigmaM3 = EnigmaI;
564 
565 /// Enigma M4, which has four rotor slots. The fourth rotor never rotates.
566 alias EnigmaM4 = Enigma!(4, true);
567 
568 /// Norway Enigma, which has three rotor slots.
569 alias Norway = EnigmaI;
570 
571 /// Enigma D, which has three rotor slots and no plugboard. The reflector can be set to any positions.
572 alias EnigmaD = Enigma!(3, false, false, true);
573 
574 /// Swiss K, which has three rotor slots and no plugboard. The reflector can be set to any positions.
575 alias SwissK = EnigmaD;
576 
577 /// Enigma R, which has three rotor slots and no plugboard. The reflector can be set to any positions.
578 alias EnigmaR = EnigmaD;
579 
580 /// Enigma T 'Tirpitz', which has three rotor slots and no plugboard. The reflector can be set to any positions.
581 alias EnigmaT = EnigmaD;
582 
583 /// Predefined existent rotors.
584 auto rotorI(dchar ringSetting = 'A') pure
585 {
586     return rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 'Q', ringSetting);
587 }
588 
589 /// ditto
590 auto rotorII(dchar ringSetting = 'A') pure
591 {
592     return rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 'E', ringSetting);
593 }
594 
595 /// ditto
596 auto rotorIII(dchar ringSetting = 'A') pure
597 {
598     return rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 'V', ringSetting);
599 }
600 
601 /// ditto
602 auto rotorIV(dchar ringSetting = 'A') pure
603 {
604     return rotor("ESOVPZJAYQUIRHXLNFTGKDCMWB", 'J', ringSetting);
605 }
606 
607 /// ditto
608 auto rotorV(dchar ringSetting = 'A') pure
609 {
610     return rotor("VZBRGITYUPSDNHLXAWMJQOFECK", 'Z', ringSetting);
611 }
612 
613 /// ditto
614 auto rotorVI(dchar ringSetting = 'A') pure
615 {
616     return rotor("JPGVOUMFYQBENHZRDKASXLICTW", 'Z', 'M', ringSetting);
617 }
618 
619 /// ditto
620 auto rotorVII(dchar ringSetting = 'A') pure
621 {
622     return rotor("NZJHGRCXMYSWBOUFAIVLPEKQDT", 'Z', 'M', ringSetting);
623 }
624 
625 /// ditto
626 auto rotorVIII(dchar ringSetting = 'A') pure
627 {
628     return rotor("FKQHTLXOCBJSPDZRAMEWNIUYGV", 'Z', 'M', ringSetting);
629 }
630 
631 /// ditto
632 auto rotorINor(dchar ringSetting = 'A') pure
633 {
634     return rotor("WTOKASUYVRBXJHQCPZEFMDINLG", 'Q', ringSetting);
635 }
636 
637 /// ditto
638 auto rotorIINor(dchar ringSetting = 'A') pure
639 {
640     return rotor("GJLPUBSWEMCTQVHXAOFZDRKYNI", 'E', ringSetting);
641 }
642 
643 /// ditto
644 auto rotorIIINor(dchar ringSetting = 'A') pure
645 {
646     return rotor("JWFMHNBPUSDYTIXVZGRQLAOEKC", 'V', ringSetting);
647 }
648 
649 /// ditto
650 alias rotorIVNor = rotorIV;
651 
652 /// ditto
653 auto rotorVNor(dchar ringSetting = 'A') pure
654 {
655     return rotor("HEJXQOTZBVFDASCILWPGYNMURK", 'Z', ringSetting);
656 }
657 
658 /// ditto
659 auto rotorID(dchar ringSetting = 'A') pure
660 {
661     return rotor("LPGSZMHAEOQKVXRFYBUTNICJDW", 'Y', ringSetting);
662 }
663 
664 /// ditto
665 auto rotorIID(dchar ringSetting = 'A') pure
666 {
667     return rotor("SLVGBTFXJQOHEWIRZYAMKPCNDU", 'E', ringSetting);
668 }
669 
670 /// ditto
671 auto rotorIIID(dchar ringSetting = 'A') pure
672 {
673     return rotor("CJGDPSHKTURAWZXFMYNQOBVLIE", 'N', ringSetting);
674 }
675 
676 /// ditto
677 auto rotorIK(dchar ringSetting = 'A') pure
678 {
679     return rotor("PEZUOHXSCVFMTBGLRINQJWAYDK", 'Y', ringSetting);
680 }
681 
682 /// ditto
683 auto rotorIIK(dchar ringSetting = 'A') pure
684 {
685     return rotor("ZOUESYDKFWPCIQXHMVBLGNJRAT", 'E', ringSetting);
686 }
687 
688 /// ditto
689 auto rotorIIIK(dchar ringSetting = 'A') pure
690 {
691     return rotor("EHRVXGAOBQUSIMZFLYNWKTPDJC", 'N', ringSetting);
692 }
693 
694 /// ditto
695 auto rotorIR(dchar ringSetting = 'A') pure
696 {
697     return rotor("JGDQOXUSCAMIFRVTPNEWKBLZYH", 'N', ringSetting);
698 }
699 
700 /// ditto
701 auto rotorIIR(dchar ringSetting = 'A') pure
702 {
703     return rotor("NTZPSFBOKMWRCJDIVLAEYUXHGQ", 'E', ringSetting);
704 }
705 
706 /// ditto
707 auto rotorIIIR(dchar ringSetting = 'A') pure
708 {
709     return rotor("JVIUBHTCDYAKEQZPOSGXNRMWFL", 'Y', ringSetting);
710 }
711 
712 /// ditto
713 auto rotorIT(dchar ringSetting = 'A') pure
714 {
715     return rotor("KPTYUELOCVGRFQDANJMBSWHZXI", 'W', 'Z', 'E', 'K', 'Q', ringSetting);
716 }
717 
718 auto rotorIIT(dchar ringSetting = 'A') pure
719 {
720     return rotor("UPHZLWEQMTDJXCAKSOIGVBYFNR", 'W', 'Z', 'F', 'L', 'R', ringSetting);
721 }
722 
723 auto rotorIIIT(dchar ringSetting = 'A') pure
724 {
725     return rotor("QUDLYRFEKONVZAXWHMGPJBSICT", 'W', 'Z', 'E', 'K', 'Q', ringSetting);
726 }
727 
728 auto rotorIVT(dchar ringSetting = 'A') pure
729 {
730     return rotor("CIWTBKXNRESPFLYDAGVHQUOJZM", 'W', 'Z', 'F', 'L', 'R', ringSetting);
731 }
732 
733 auto rotorVT(dchar ringSetting = 'A') pure
734 {
735     return rotor("UAXGISNJBVERDYLFZWTPCKOHMQ", 'Y', 'C', 'F', 'K', 'R', ringSetting);
736 }
737 
738 auto rotorVIT(dchar ringSetting = 'A') pure
739 {
740     return rotor("XFUZGALVHCNYSEWQTDMRBKPIOJ", 'X', 'E', 'I', 'M', 'Q', ringSetting);
741 }
742 
743 auto rotorVIIT(dchar ringSetting = 'A') pure
744 {
745     return rotor("BJVFTXPLNAYOZIKWGDQERUCHSM", 'Y', 'C', 'F', 'K', 'R', ringSetting);
746 }
747 
748 auto rotorVIIIT(dchar ringSetting = 'A') pure
749 {
750     return rotor("YMTPNZHWKODAJXELUQVGCBISFR", 'X', 'E', 'I', 'M', 'Q', ringSetting);
751 }
752 
753 
754 /++
755 + Predefined existent rotors. Because these rotors have no turnover notches, they are generally set
756 + side by side with a reflector.
757 +/
758 auto rotorBeta(dchar ringSetting = 'A') pure
759 {
760     return rotor("LEYJVCNIXWPBQMDRTAKZGFUHOS", ringSetting);
761 }
762 
763 /// ditto
764 auto rotorGamma(dchar ringSetting = 'A') pure
765 {
766     return rotor("FSOKANUERHMBTIYCWLQPZXVGJD", ringSetting);
767 }
768 
769 /// Predefined the simplest entry wheel which does not substitute.
770 auto entryWheelABC() pure
771 {
772     return entryWheel("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
773 }
774 
775 /// Predefined entry wheel: QWE... -> ABC...
776 auto entryWheelQWE() pure
777 {
778     return entryWheel("QWERTZUIOASDFGHJKPYXCVBNML");
779 }
780 
781 /// Predefined entry wheel: KZR... -> ABC...
782 auto entryWheelKZR() pure
783 {
784     return entryWheel("KZROUQHYAIGBLWVSTDXFPNMCJE");
785 }
786 
787 /// Predefined the simplest plugboard which does not substitute.
788 auto plugboardDoNothing() pure
789 {
790     return plugboard("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
791 }
792 
793 /// Predefined existent reflectors.
794 auto reflectorA() pure
795 {
796     return reflector("EJMZALYXVBWFCRQUONTSPIKHGD");
797 }
798 
799 auto reflectorB() pure
800 {
801     return reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT");
802 }
803 
804 /// ditto
805 auto reflectorC() pure
806 {
807     return reflector("FVPJIAOYEDRZXWGCTKUQSBNMHL");
808 }
809 
810 /// ditto
811 auto reflectorBThin() pure
812 {
813     return reflector("ENKQAUYWJICOPBLMDXZVFTHRGS");
814 }
815 
816 /// ditto
817 auto reflectorCThin() pure
818 {
819     return reflector("RDOBJNTKVEHMLFCWZAXGYIPSUQ");
820 }
821 
822 /// ditto
823 auto reflectorNor() pure
824 {
825     return reflector("MOWJYPUXNDSRAIBFVLKZGQCHET");
826 }
827 
828 /// ditto
829 auto reflectorD(dchar ringSetting = 'A') pure
830 {
831     return reflector("IMETCGFRAYSQBZXWLHKDVUPOJN", ringSetting);
832 }
833 
834 /// ditto
835 alias reflectorK = reflectorD;
836 
837 /// ditto
838 auto reflectorR(dchar ringSetting = 'A') pure
839 {
840     return reflector("QYHOGNECVPUZTFDJAXWMKISRBL", ringSetting);
841 }
842 
843 /// ditto
844 auto reflectorT(dchar ringSetting = 'A') pure
845 {
846     return reflector("GEKPBTAUMOCNILJDXZYFHWVQSR", ringSetting);
847 }
848 
849 // Double stepping test (http://www.cryptomuseum.com/crypto/enigma/working.htm)
850 unittest
851 {
852     auto m3 = EnigmaM3(plugboardDoNothing, entryWheelABC, rotorI, rotorII, rotorIII, reflectorB, "ODA");
853 
854     assert(m3.rotationStates == [14, 3, 0]);
855 
856     assert(m3('A') == 'H');
857     assert(m3.rotationStates == [15, 3, 0]);
858 
859     assert(m3('A') == 'D');
860     assert(m3.rotationStates == [16, 3, 0]);
861 
862     assert(m3('A') == 'Z');
863     assert(m3.rotationStates == [17, 4, 0]);
864 
865     assert(m3('A') == 'G');
866     assert(m3.rotationStates == [18, 5, 1]);
867 
868     assert(m3('A') == 'O');
869     assert(m3.rotationStates == [19, 5, 1]);
870 
871     assert(m3('A') == 'V');
872     assert(m3.rotationStates == [20, 5, 1]);
873 }
874 
875 // The noches are fixed to the ring.
876 unittest
877 {
878     auto ed = EnigmaD(entryWheelQWE, rotorID, rotorIID, rotorIIID, reflectorD, "UDN" /*!*/ , 'B');
879 
880     assert(ed.rotationStates == [20, 3, 13]);
881 
882     assert(ed('A') == 'Z');
883     assert(ed.rotationStates == [21, 3, 13]);
884 
885     assert(ed('A') == 'D');
886     assert(ed.rotationStates == [22, 3, 13]);
887 
888     assert(ed('A') == 'V');
889     assert(ed.rotationStates == [23, 3, 13]);
890 
891     assert(ed('A') == 'I');
892     assert(ed.rotationStates == [24, 3, 13]);
893 
894     assert(ed('A') == 'C');
895     assert(ed.rotationStates == [25, 4, 13]);
896 
897     assert(ed('A') == 'Z');
898     assert(ed.rotationStates == [0, 5, 14]);
899 
900 
901     // The K's rotor positions are same as the D's.
902     auto sk = SwissK(entryWheelQWE, rotorIK('Z'), rotorIIK('Y'), rotorIIIK, reflectorK('E'), "UDN" /*!*/ , 'X');
903 
904     assert(sk.rotationStates == [20, 3, 13]);
905 
906     assert(sk('A') == 'Y');
907     assert(sk.rotationStates == [21, 3, 13]);
908 
909     assert(sk('A') == 'H');
910     assert(sk.rotationStates == [22, 3, 13]);
911 
912     assert(sk('A') == 'U');
913     assert(sk.rotationStates == [23, 3, 13]);
914 
915     assert(sk('A') == 'M');
916     assert(sk.rotationStates == [24, 3, 13]);
917 
918     assert(sk('A') == 'V');
919     assert(sk.rotationStates == [25, 4, 13]);
920 
921     assert(sk('A') == 'Q');
922     assert(sk.rotationStates == [0, 5, 14]);
923 }
924 
925 unittest
926 {
927     auto nor1 = Norway(entryWheelABC, rotorINor, rotorIINor, rotorIIINor, reflectorNor, "PDL");
928 
929     assert(nor1('A') == 'I');
930     assert(nor1('A') == 'F');
931     assert(nor1('A') == 'L');
932     assert(nor1('A') == 'F');
933     assert(nor1('A') == 'F');
934     assert(nor1('A') == 'H');
935 
936 
937     auto nor2 = Norway(plugboard("ABCDEGFHIJKLMNOPQRSTUVWXYZ"), entryWheelABC, rotorINor, rotorIVNor, rotorVNor, reflectorNor, "PDL");
938 
939     assert(nor2('A') == 'P');
940     assert(nor2('A') == 'G');
941     assert(nor2('A') == 'J');
942     assert(nor2('A') == 'N');
943     assert(nor2('A') == 'U');
944     assert(nor2('A') == 'Z');
945 }
946 
947 unittest
948 {
949     auto er = EnigmaR(entryWheelQWE, rotorIR, rotorIIR, rotorIIIR, reflectorR('E'), "KBA", 'C');
950 
951     assert(er('A') == 'Y');
952     assert(er('A') == 'P');
953     assert(er('A') == 'I');
954     assert(er('A') == 'R');
955     assert(er('A') == 'Z');
956     assert(er('A') == 'S');
957 }
958 
959 unittest
960 {
961     auto et1 = EnigmaT(entryWheelKZR, rotorIT, rotorIIT, rotorIIIT, reflectorT, "AFR", 'H');
962 
963     assert(et1('A') == 'E');
964     assert(et1('A') == 'T');
965     assert(et1('A') == 'U');
966     assert(et1('A') == 'I');
967     assert(et1('A') == 'H');
968     assert(et1('A') == 'M');
969 
970 
971     auto et2 = EnigmaT(entryWheelKZR, rotorVIT, rotorVT, rotorIVT, reflectorT, "AFR", 'A');
972 
973     assert(et2('A') == 'X');
974     assert(et2('A') == 'F');
975     assert(et2('A') == 'M');
976     assert(et2('A') == 'R');
977     assert(et2('A') == 'C');
978     assert(et2('A') == 'B');
979 
980 
981     auto et3 = EnigmaT(entryWheelKZR, rotorVIIT, rotorVIIIT('Z'), rotorIVT, reflectorT('C'), "AFR", 'V');
982 
983     assert(et3('A') == 'D');
984     assert(et3('A') == 'S');
985     assert(et3('A') == 'F');
986     assert(et3('A') == 'N');
987     assert(et3('A') == 'D');
988     assert(et3('A') == 'T');
989 }
990 
991 /// Step-by-step enciphering.
992 unittest
993 {
994     immutable pbCI = plugboard("ABIDEFGHCJKLMNOPQRSTUVWXYZ"); // C <-> I
995     immutable enWh = entryWheel("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
996     immutable refB = reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT");
997     immutable rot1 = rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 'Q', 'A');
998     immutable rot2 = rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 'E', 'B');
999     immutable rot3 = rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 'V', 'A');
1000 
1001     auto e3 = Enigma!3(pbCI, enWh, rot1, rot2, rot3, refB, ['X', 'Q', 'E']);
1002     assert(e3('A') == 'K');
1003     assert(e3('a') == 'T'); // A lowercase is automatically converted to an uppercase.
1004     assert(e3('5') == '5'); // A non-alphabetical character does not changes
1005     assert(e3('Ü') == 'Ü'); // the machine state and will be output as it is.
1006     assert(e3('A') == 'Q');
1007 }
1008 
1009 /// Encipherment is decipherment.
1010 unittest
1011 {
1012     // These have the same settings.
1013     auto encipherer = Enigma!2(entryWheelABC, rotorVI, rotorVII, reflectorC, "PY");
1014     auto decipherer = Enigma!2(entryWheelABC, rotorVI, rotorVII, reflectorC, "PY");
1015 
1016     foreach (dchar c; "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
1017     {
1018         auto enciphered = encipherer(c);
1019         auto deciphered = decipherer(enciphered);
1020     }
1021 }
1022 
1023 /++
1024  + A certain equivalence of the M3 and the M4.
1025  +/
1026 unittest
1027 {
1028     // These have the equivalent settings.
1029     auto m3 = EnigmaM3(entryWheelABC, rotorIII, rotorII, rotorI, reflectorB /*!*/ ,
1030         "FOO");
1031     auto m4 = EnigmaM4(entryWheelABC, rotorIII, rotorII, rotorI,
1032         rotorBeta('A') /*!*/ , reflectorBThin /*!*/ , "FOOA" /*!*/ ); // FOO*A*
1033 
1034     // If each machine has just one movable rotor...
1035     auto e1 = Enigma!1(entryWheelABC, rotorI, reflectorC /*!*/ , "X");
1036     auto e2fixed = Enigma!(2, true  /*!*/ )(entryWheelABC, rotorI,
1037         rotorGamma('A') /*!*/ , reflectorCThin /*!*/ , "XA" /*!*/ ); // X*A*
1038 
1039     foreach (dchar c; "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
1040     {
1041         assert(m3(c) == m4(c));
1042         assert(e1(c) == e2fixed(c));
1043     }
1044 }
1045 
1046 /// Encipher with the M4 and decipher with the equivalent M3.
1047 unittest
1048 {
1049     import std.algorithm.comparison : equal;
1050     import std.algorithm.iteration : each, map;
1051     import std.array : appender;
1052 
1053     // These have the equivalent settings.
1054     auto m4 = EnigmaM4(plugboard("SBCDEGFHIJKLMNOPQRATUVWXYZ"), entryWheelABC, rotorIII('Y'),
1055         rotorII('V'), rotorI('R'), rotorBeta, reflectorBThin, "UEQA");
1056     auto m3 = EnigmaM3(plugboard("SBCDEGFHIJKLMNOPQRATUVWXYZ"), entryWheelABC, rotorIII('Y'),
1057         rotorII('V'), rotorI('R'), reflectorB, "UEQ");
1058 
1059     auto enciphered = appender!dstring;
1060     "ABCDEFGHIJKLMNOPQRSTUVWXYZ".map!m4.each!(c => enciphered.put(c));
1061     assert(enciphered.data == "RIIGSIBEBIZKCTZSSDGQMLSVUX");
1062 
1063     auto deciphered = enciphered.data.map!m3;
1064     assert(deciphered.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
1065 }