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 { 39 import boolean_matrix : lowerRotator, upperRotator; 40 41 this.perm = lowerRotator!N(ringOffset) * perm * upperRotator!N(ringOffset); 42 } 43 44 /++ 45 + Constructs a rotor with one turnover notch. 46 + If turnover is `1`, the next rotor steps when this rotor steps from B to C. 47 + If ringOffset is `2`, it corresponds to "C-03". 48 +/ 49 this()(in auto ref BSM!N perm, size_t turnover, size_t ringOffset) pure 50 { 51 this(perm, ringOffset); 52 this.turnovers = [turnover % N]; 53 hasNotch = true; 54 } 55 56 /++ 57 + Constructs a rotor with two turnover notches. 58 + If turnover1 is `1` and turnover2 is `25`, the next rotor steps 59 + when this rotor steps from B to C and from Z to A. 60 + If ringOffset is `2`, it corresponds to "C-03". 61 +/ 62 this()(in auto ref BSM!N perm, size_t turnover1, size_t turnover2, size_t ringOffset) pure 63 { 64 import std.algorithm.sorting : sort; 65 import std.array : array; 66 67 this(perm, ringOffset); 68 this.turnovers = [turnover1 % N, turnover2 % N].sort().array; 69 hasNotch = true; 70 } 71 } 72 73 /++ 74 + A convenience function to make a rotor with no turnover notches 75 + from a forward substitution. 76 + If ringSetting is `'C'`, it corresponds to "C-03". 77 +/ 78 auto rotor(S)(in S forwardSubstitution, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 79 in 80 { 81 import std.algorithm.comparison : isPermutation; 82 import std.algorithm.iteration : map; 83 import std.ascii : isAlpha, toUpper; 84 import std.range : iota, walkLength; 85 86 assert(forwardSubstitution.walkLength == N, "Bad length."); 87 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 88 assert(ringSetting.isAlpha); 89 } 90 body 91 { 92 import std.algorithm.iteration : map; 93 import std.array : array; 94 import std.ascii : toUpper; 95 import boolean_matrix : permutation; 96 97 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 98 ringSetting.toUpper - 'A'); 99 } 100 101 /++ 102 + A convenience function to make a rotor with one turnover notch 103 + from a forward substitution. 104 + If turnover is `'B'`, the next rotor steps when this rotor steps from B to C. 105 + If ringSetting is `'C'`, it corresponds to "C-03". 106 +/ 107 auto rotor(S)(in S forwardSubstitution, dchar turnover, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 108 in 109 { 110 import std.algorithm.comparison : isPermutation; 111 import std.algorithm.iteration : map; 112 import std.ascii : isAlpha, toUpper; 113 import std.range : iota, walkLength; 114 115 assert(forwardSubstitution.walkLength == N, "Bad length."); 116 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 117 assert(turnover.isAlpha); 118 assert(ringSetting.isAlpha); 119 } 120 body 121 { 122 import std.algorithm.iteration : map; 123 import std.array : array; 124 import std.ascii : toUpper; 125 import boolean_matrix : permutation; 126 127 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 128 turnover.toUpper - 'A', ringSetting.toUpper - 'A'); 129 } 130 131 /++ 132 + A convenience function to make a rotor with two turnover notches 133 + from a forward substitution. 134 + If turnover1 is `'B'` and turnover2 is `'Z'`, the next rotor steps 135 + when this rotor steps from B to C and from Z to A. 136 + If ringSetting is `'C'`, it corresponds to "C-03". 137 +/ 138 auto rotor(S)(in S forwardSubstitution, dchar turnover1, dchar turnover2, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 139 in 140 { 141 import std.algorithm.comparison : isPermutation; 142 import std.algorithm.iteration : map; 143 import std.ascii : isAlpha, toUpper; 144 import std.range : iota, walkLength; 145 146 assert(forwardSubstitution.walkLength == N, "Bad length."); 147 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 148 assert(turnover1.isAlpha); 149 assert(turnover2.isAlpha); 150 assert(ringSetting.isAlpha); 151 } 152 body 153 { 154 import std.algorithm.iteration : map; 155 import std.array : array; 156 import std.ascii : toUpper; 157 import boolean_matrix : permutation; 158 159 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 160 turnover1.toUpper - 'A', turnover2.toUpper - 'A', ringSetting.toUpper - 'A'); 161 } 162 163 /// 164 struct EntryWheel 165 { 166 import boolean_matrix : BSM; 167 168 immutable BSM!N perm; 169 /// 170 this()(in auto ref BSM!N perm) pure 171 { 172 this.perm = cast(immutable) perm; 173 } 174 175 alias perm this; 176 } 177 178 /++ 179 + A convenience function to make an entry wheel from a substitution. 180 +/ 181 auto entryWheel(S)(in S backwardSubstitution) pure if (isSomeStringOrDcharRange!S) 182 in 183 { 184 import std.algorithm.comparison : isPermutation; 185 import std.algorithm.iteration : map; 186 import std.ascii : toUpper; 187 import std.range : iota, walkLength; 188 189 assert(backwardSubstitution.walkLength == N, "Bad length."); 190 assert(N.iota.isPermutation(backwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 191 } 192 body 193 { 194 import std.algorithm.iteration : map; 195 import std.array : array; 196 import std.ascii : toUpper; 197 import boolean_matrix : permutation, transpose; 198 199 return EntryWheel(backwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N.transpose); 200 } 201 202 /// 203 struct Plugboard 204 { 205 import boolean_matrix : BSM; 206 207 immutable BSM!N perm; 208 /// 209 this()(in auto ref BSM!N perm) pure 210 { 211 this.perm = cast(immutable) perm; 212 } 213 214 alias perm this; 215 } 216 217 /++ 218 + A convenience function to make a plugboard from a substitution. 219 +/ 220 auto plugboard(S)(in S substitution) pure if (isSomeStringOrDcharRange!S) 221 in 222 { 223 import std.algorithm.comparison : isPermutation; 224 import std.algorithm.iteration : map; 225 import std.ascii : toUpper; 226 import std.range : iota, walkLength; 227 228 assert(substitution.walkLength == N, "Bad length."); 229 assert(N.iota.isPermutation(substitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 230 } 231 out (r) 232 { 233 import boolean_matrix : transpose; 234 235 assert(r == r.transpose); 236 } 237 body 238 { 239 import std.algorithm.iteration : map; 240 import std.array : array; 241 import std.ascii : toUpper; 242 import boolean_matrix : permutation; 243 244 return Plugboard(substitution.map!toUpper.map!"a-'A'".array.permutation!N); 245 } 246 247 /// 248 struct Reflector 249 { 250 import boolean_matrix : BSM; 251 252 immutable BSM!N perm; 253 /// 254 this()(in auto ref BSM!N perm, size_t ringOffset) pure 255 { 256 import boolean_matrix : lowerRotator, upperRotator; 257 258 this.perm = lowerRotator!N(ringOffset) * perm * upperRotator!N(ringOffset); 259 } 260 261 alias perm this; 262 } 263 264 /++ 265 + A convenience function to make a reflector from a substitution. 266 +/ 267 auto reflector(S)(in S substitution, dchar ringSetting = 'A') pure if (isSomeStringOrDcharRange!S) 268 in 269 { 270 import std.algorithm.comparison : isPermutation; 271 import std.algorithm.iteration : map; 272 import std.ascii : isAlpha, toUpper; 273 import std.range : iota, walkLength; 274 275 assert(substitution.walkLength == N, "Bad length."); 276 assert(N.iota.isPermutation(substitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 277 assert(ringSetting.isAlpha); 278 } 279 out (r) 280 { 281 import boolean_matrix : transpose; 282 283 assert(r == r.transpose); 284 } 285 body 286 { 287 import std.algorithm.iteration : map; 288 import std.array : array; 289 import std.ascii : toUpper; 290 import boolean_matrix : permutation; 291 292 return Reflector(substitution.map!toUpper.map!"a-'A'".array.permutation!N, 293 ringSetting.toUpper - 'A'); 294 } 295 296 /// Currently machines with the double-stepping mechanism are available. 297 struct Enigma(size_t rotorN, bool fixedFinalRotor = false, bool hasPlugboard = true, bool settableReflectorPos = false) 298 { 299 import boolean_matrix : BSM; 300 private immutable BSM!N composedInputPerm; 301 private immutable Rotor[rotorN] rotors; 302 private immutable BSM!N reflector; 303 private size_t[rotorN] rotationStates; 304 305 import meta_workaround : Repeat; 306 307 /// 308 this(in EntryWheel entryWheel, in Repeat!(rotorN, Rotor) rotors, 309 in Reflector reflector, in dchar[rotorN] rotorStartPos) 310 in 311 { 312 foreach (dchar c; rotorStartPos) 313 { 314 import std.ascii : isAlpha; 315 316 assert(c.isAlpha); 317 } 318 } 319 body 320 { 321 foreach (i, ref e; rotationStates) 322 { 323 import std.ascii : toUpper; 324 325 e = rotorStartPos[i].toUpper - 'A'; 326 } 327 328 this.composedInputPerm = cast(immutable) entryWheel.perm; 329 this.rotors[] = cast(immutable)[rotors][]; 330 this.reflector = cast(immutable) reflector.perm; 331 } 332 333 /// 334 static if (settableReflectorPos) 335 { 336 this(in EntryWheel entryWheel, in Repeat!(rotorN, Rotor) rotors, 337 in Reflector reflector, in dchar[rotorN] rotorStartPos, 338 dchar reflectorPos) 339 in 340 { 341 import std.ascii : isAlpha; 342 343 assert(reflectorPos.isAlpha); 344 } 345 body 346 { 347 this(entryWheel, rotors, reflector, rotorStartPos); 348 349 import std.ascii : toUpper; 350 import boolean_matrix : lowerRotator, upperRotator; 351 352 immutable refOffset = reflectorPos.toUpper - 'A'; 353 this.reflector = upperRotator!N(refOffset) * reflector * lowerRotator!N(refOffset); 354 } 355 } 356 357 /// 358 static if (hasPlugboard) 359 { 360 this(in Plugboard plugboard, in EntryWheel entryWheel, 361 in Repeat!(rotorN, Rotor) rotors, 362 in Reflector reflector, in dchar[rotorN] rotorStartPos) 363 { 364 this(entryWheel, rotors, reflector, rotorStartPos); 365 this.composedInputPerm = cast(immutable) (entryWheel * plugboard); 366 } 367 368 /// 369 static if (settableReflectorPos) 370 { 371 this(in Plugboard plugboard, in EntryWheel entryWheel, 372 in Repeat!(rotorN, Rotor) rotors, 373 in Reflector reflector, in dchar[rotorN] rotorStartPos, 374 dchar reflectorPos) 375 in 376 { 377 import std.ascii : isAlpha; 378 379 assert(reflectorPos.isAlpha); 380 } 381 body 382 { 383 this(plugboard, entryWheel, rotors, reflector, rotorStartPos); 384 385 import std.ascii : toUpper; 386 import boolean_matrix : lowerRotator, upperRotator; 387 388 immutable refOffset = reflectorPos.toUpper - 'A'; 389 this.reflector = upperRotator!N(refOffset) * reflector * lowerRotator!N(refOffset); 390 } 391 } 392 } 393 394 private void step() 395 { 396 enum movableRotorN = fixedFinalRotor ? rotorN - 1 : rotorN; 397 bool[movableRotorN] stepFlag; 398 399 stepFlag[0] = true; 400 401 // Handles double stepping 402 foreach (rotorID; 0 .. movableRotorN - 1) 403 { 404 import std.algorithm.searching : canFind; 405 406 if (rotors[rotorID].turnovers.canFind(rotationStates[rotorID])) 407 { 408 stepFlag[rotorID] = true; 409 stepFlag[rotorID + 1] = true; 410 } 411 } 412 413 foreach (rotorID, e; stepFlag) 414 { 415 if (e) 416 { 417 rotationStates[rotorID] = (rotationStates[rotorID] + 1) % N; 418 } 419 } 420 } 421 422 import boolean_matrix : BSM; 423 424 /+ 425 + fwdPerm = (Um*Rm*Lm)*(Um-1*Rm-1*Lm-1)*...*(U0*R0*L0)*P 426 + = Um*(Rm*Lm*Um-1)*(Rm-1*Lm-1*Um-2)*...*(R0*L0)*P 427 + = Um*(Rm*RELm)*(Rm-1*RELm-1)*...*(R0*L0)*P 428 +/ 429 private BSM!N composeForwardPermutation(in ref BSM!N prevPerm, size_t rotorID) 430 { 431 import boolean_matrix : lowerRotator, upperRotator, Identity; 432 433 immutable ptrdiff_t x = rotorID == 0 ? rotationStates[0] : rotationStates[rotorID] - rotationStates[rotorID - 1]; 434 immutable relRotator = x > 0 ? lowerRotator!N(x) : upperRotator!N(-x); 435 immutable composedPerm = rotors[rotorID].perm * relRotator * prevPerm; 436 return rotorID == rotorN - 1 ? upperRotator!N(rotationStates[rotorID]) * composedPerm 437 : composeForwardPermutation(composedPerm, rotorID + 1); 438 } 439 440 private auto process(size_t keyInputID) 441 { 442 step(); 443 444 import boolean_matrix : transpose, BCV; 445 446 immutable fwdPerm = composeForwardPermutation(composedInputPerm, 0); 447 // bwdPerm = fwdPerm^-1 = fwdPerm^T 448 immutable wholePerm = fwdPerm.transpose * reflector * fwdPerm; 449 BCV!N v; 450 v[keyInputID] = true; 451 immutable w = wholePerm * v; 452 import std.algorithm.searching : countUntil; 453 454 immutable r = w[].countUntil(true); 455 assert(r >= 0); 456 return r; 457 458 } 459 460 /// Enciphers only an alphabetical character through the current Enigma machine. 461 dchar opCall(dchar keyInput) 462 { 463 import std.ascii : isAlpha, toUpper; 464 465 return keyInput.isAlpha ? process(keyInput.toUpper - 'A') + 'A' : keyInput; 466 } 467 } 468 469 /// Enigma I 'Wehrmacht', which has three rotor slots. 470 alias EnigmaI = Enigma!3; 471 472 /// Enigma M3, which has three rotor slots. 473 alias EnigmaM3 = Enigma!3; 474 475 /// Enigma M4, which has four rotor slots. The fourth rotor never rotates. 476 alias EnigmaM4 = Enigma!(4, true); 477 478 /// Enigma D, which has three rotor slots and no plugboard. The reflector can be set to any positions. 479 alias EnigmaD = Enigma!(3, false, false, true); 480 481 /// Swiss K, which has three rotor slots and no plugboard. The reflector can be set to any positions. 482 alias SwissK = EnigmaD; 483 484 /// Predefined existent rotors. 485 auto rotorI(dchar ringSetting = 'A') pure 486 { 487 return rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 'Q', ringSetting); 488 } 489 490 /// ditto 491 auto rotorII(dchar ringSetting = 'A') pure 492 { 493 return rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 'E', ringSetting); 494 } 495 496 /// ditto 497 auto rotorIII(dchar ringSetting = 'A') pure 498 { 499 return rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 'V', ringSetting); 500 } 501 502 /// ditto 503 auto rotorIV(dchar ringSetting = 'A') pure 504 { 505 return rotor("ESOVPZJAYQUIRHXLNFTGKDCMWB", 'J', ringSetting); 506 } 507 508 /// ditto 509 auto rotorV(dchar ringSetting = 'A') pure 510 { 511 return rotor("VZBRGITYUPSDNHLXAWMJQOFECK", 'Z', ringSetting); 512 } 513 514 /// ditto 515 auto rotorVI(dchar ringSetting = 'A') pure 516 { 517 return rotor("JPGVOUMFYQBENHZRDKASXLICTW", 'Z', 'M', ringSetting); 518 } 519 520 /// ditto 521 auto rotorVII(dchar ringSetting = 'A') pure 522 { 523 return rotor("NZJHGRCXMYSWBOUFAIVLPEKQDT", 'Z', 'M', ringSetting); 524 } 525 526 /// ditto 527 auto rotorVIII(dchar ringSetting = 'A') pure 528 { 529 return rotor("FKQHTLXOCBJSPDZRAMEWNIUYGV", 'Z', 'M', ringSetting); 530 } 531 532 /// ditto 533 auto rotorID(dchar ringSetting = 'A') pure 534 { 535 return rotor("LPGSZMHAEOQKVXRFYBUTNICJDW", 'Y', ringSetting); 536 } 537 538 /// ditto 539 auto rotorIID(dchar ringSetting = 'A') pure 540 { 541 return rotor("SLVGBTFXJQOHEWIRZYAMKPCNDU", 'E', ringSetting); 542 } 543 544 /// ditto 545 auto rotorIIID(dchar ringSetting = 'A') pure 546 { 547 return rotor("CJGDPSHKTURAWZXFMYNQOBVLIE", 'N', ringSetting); 548 } 549 550 /// ditto 551 auto rotorIK(dchar ringSetting = 'A') pure 552 { 553 return rotor("PEZUOHXSCVFMTBGLRINQJWAYDK", 'Y', ringSetting); 554 } 555 556 /// ditto 557 auto rotorIIK(dchar ringSetting = 'A') pure 558 { 559 return rotor("ZOUESYDKFWPCIQXHMVBLGNJRAT", 'E', ringSetting); 560 } 561 562 /// ditto 563 auto rotorIIIK(dchar ringSetting = 'A') pure 564 { 565 return rotor("EHRVXGAOBQUSIMZFLYNWKTPDJC", 'N', ringSetting); 566 } 567 568 /++ 569 + Predefined existent rotors. Because these rotors have no turnover notches, they are generally set 570 + side by side with a reflector. 571 +/ 572 auto rotorBeta(dchar ringSetting = 'A') pure 573 { 574 return rotor("LEYJVCNIXWPBQMDRTAKZGFUHOS", ringSetting); 575 } 576 577 /// ditto 578 auto rotorGamma(dchar ringSetting = 'A') pure 579 { 580 return rotor("FSOKANUERHMBTIYCWLQPZXVGJD", ringSetting); 581 } 582 583 /// Predefined the simplest entry wheel which does not substitute. 584 auto entryWheelABC() pure 585 { 586 return entryWheel("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 587 } 588 589 /// Predefined entry wheel: QWE... -> ABC... 590 auto entryWheelQWE() pure 591 { 592 return entryWheel("QWERTZUIOASDFGHJKPYXCVBNML"); 593 } 594 595 /// Predefined the simplest plugboard which does not substitute. 596 auto plugboardDoNothing() pure 597 { 598 return plugboard("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 599 } 600 601 /// Predefined existent reflectors. 602 auto reflectorA() pure 603 { 604 return reflector("EJMZALYXVBWFCRQUONTSPIKHGD"); 605 } 606 607 auto reflectorB() pure 608 { 609 return reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT"); 610 } 611 612 /// ditto 613 auto reflectorC() pure 614 { 615 return reflector("FVPJIAOYEDRZXWGCTKUQSBNMHL"); 616 } 617 618 /// ditto 619 auto reflectorBThin() pure 620 { 621 return reflector("ENKQAUYWJICOPBLMDXZVFTHRGS"); 622 } 623 624 /// ditto 625 auto reflectorCThin() pure 626 { 627 return reflector("RDOBJNTKVEHMLFCWZAXGYIPSUQ"); 628 } 629 630 /// ditto 631 auto reflectorD(dchar ringSetting = 'A') pure 632 { 633 return reflector("IMETCGFRAYSQBZXWLHKDVUPOJN", ringSetting); 634 } 635 636 /// ditto 637 alias reflectorK = reflectorD; 638 639 // Double stepping test (http://www.cryptomuseum.com/crypto/enigma/working.htm) 640 unittest 641 { 642 auto m3 = EnigmaM3(plugboardDoNothing, entryWheelABC, rotorI, rotorII, rotorIII, reflectorB, "ODA"); 643 644 assert(m3.rotationStates == [14, 3, 0]); 645 646 assert(m3('A') == 'H'); 647 assert(m3.rotationStates == [15, 3, 0]); 648 649 assert(m3('A') == 'D'); 650 assert(m3.rotationStates == [16, 3, 0]); 651 652 assert(m3('A') == 'Z'); 653 assert(m3.rotationStates == [17, 4, 0]); 654 655 assert(m3('A') == 'G'); 656 assert(m3.rotationStates == [18, 5, 1]); 657 658 assert(m3('A') == 'O'); 659 assert(m3.rotationStates == [19, 5, 1]); 660 661 assert(m3('A') == 'V'); 662 assert(m3.rotationStates == [20, 5, 1]); 663 } 664 665 // The noches are fixed to the ring. 666 unittest 667 { 668 auto ed = EnigmaD(entryWheelQWE, rotorID, rotorIID, rotorIIID, reflectorD, "UDN" /*!*/ , 'B'); 669 670 assert(ed.rotationStates == [20, 3, 13]); 671 672 assert(ed('A') == 'Z'); 673 assert(ed.rotationStates == [21, 3, 13]); 674 675 assert(ed('A') == 'D'); 676 assert(ed.rotationStates == [22, 3, 13]); 677 678 assert(ed('A') == 'V'); 679 assert(ed.rotationStates == [23, 3, 13]); 680 681 assert(ed('A') == 'I'); 682 assert(ed.rotationStates == [24, 3, 13]); 683 684 assert(ed('A') == 'C'); 685 assert(ed.rotationStates == [25, 4, 13]); 686 687 assert(ed('A') == 'Z'); 688 assert(ed.rotationStates == [0, 5, 14]); 689 690 691 // The K's rotor positions are same as the D's. 692 auto sk = SwissK(entryWheelQWE, rotorIK('Z'), rotorIIK('Y'), rotorIIIK, reflectorK('E'), "UDN" /*!*/ , 'X'); 693 694 assert(sk.rotationStates == [20, 3, 13]); 695 696 assert(sk('A') == 'Y'); 697 assert(sk.rotationStates == [21, 3, 13]); 698 699 assert(sk('A') == 'H'); 700 assert(sk.rotationStates == [22, 3, 13]); 701 702 assert(sk('A') == 'U'); 703 assert(sk.rotationStates == [23, 3, 13]); 704 705 assert(sk('A') == 'M'); 706 assert(sk.rotationStates == [24, 3, 13]); 707 708 assert(sk('A') == 'V'); 709 assert(sk.rotationStates == [25, 4, 13]); 710 711 assert(sk('A') == 'Q'); 712 assert(sk.rotationStates == [0, 5, 14]); 713 } 714 715 /// Step-by-step enciphering. 716 unittest 717 { 718 immutable pbCI = plugboard("ABIDEFGHCJKLMNOPQRSTUVWXYZ"); // C <-> I 719 immutable enWh = entryWheel("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 720 immutable refB = reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT"); 721 immutable rot1 = rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 'Q', 'A'); 722 immutable rot2 = rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 'E', 'B'); 723 immutable rot3 = rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 'V', 'A'); 724 725 auto e3 = Enigma!3(pbCI, enWh, rot1, rot2, rot3, refB, ['X', 'Q', 'E']); 726 assert(e3('A') == 'K'); 727 assert(e3('a') == 'T'); // A lowercase is automatically converted to an uppercase. 728 assert(e3('5') == '5'); // A non-alphabetical character does not changes 729 assert(e3('Ü') == 'Ü'); // the machine state and will be output as it is. 730 assert(e3('A') == 'Q'); 731 } 732 733 /// Encipherment is decipherment. 734 unittest 735 { 736 // These have the same settings. 737 auto encipherer = Enigma!2(entryWheelABC, rotorVI, rotorVII, reflectorC, "PY"); 738 auto decipherer = Enigma!2(entryWheelABC, rotorVI, rotorVII, reflectorC, "PY"); 739 740 foreach (dchar c; "ABCDEFGHIJKLMNOPQRSTUVWXYZ") 741 { 742 auto enciphered = encipherer(c); 743 auto deciphered = decipherer(enciphered); 744 } 745 } 746 747 /++ 748 + A certain equivalence of the M3 and the M4. 749 +/ 750 unittest 751 { 752 // These have the equivalent settings. 753 auto m3 = EnigmaM3(entryWheelABC, rotorIII, rotorII, rotorI, reflectorB /*!*/ , 754 "FOO"); 755 auto m4 = EnigmaM4(entryWheelABC, rotorIII, rotorII, rotorI, 756 rotorBeta('A') /*!*/ , reflectorBThin /*!*/ , "FOOA" /*!*/ ); // FOO*A* 757 758 // If each machine has just one movable rotor... 759 auto e1 = Enigma!1(entryWheelABC, rotorI, reflectorC /*!*/ , "X"); 760 auto e2fixed = Enigma!(2, true /*!*/ )(entryWheelABC, rotorI, 761 rotorGamma('A') /*!*/ , reflectorCThin /*!*/ , "XA" /*!*/ ); // X*A* 762 763 foreach (dchar c; "ABCDEFGHIJKLMNOPQRSTUVWXYZ") 764 { 765 assert(m3(c) == m4(c)); 766 assert(e1(c) == e2fixed(c)); 767 } 768 } 769 770 /// Encipher with the M4 and decipher with the equivalent M3. 771 unittest 772 { 773 import std.algorithm.comparison : equal; 774 import std.algorithm.iteration : each, map; 775 import std.array : appender; 776 777 // These have the equivalent settings. 778 auto m4 = EnigmaM4(plugboard("SBCDEGFHIJKLMNOPQRATUVWXYZ"), entryWheelABC, rotorIII('Y'), 779 rotorII('V'), rotorI('R'), rotorBeta, reflectorBThin, "UEQA"); 780 auto m3 = EnigmaM3(plugboard("SBCDEGFHIJKLMNOPQRATUVWXYZ"), entryWheelABC, rotorIII('Y'), 781 rotorII('V'), rotorI('R'), reflectorB, "UEQ"); 782 783 auto enciphered = appender!dstring; 784 "ABCDEFGHIJKLMNOPQRSTUVWXYZ".map!m4.each!(c => enciphered.put(c)); 785 assert(enciphered.data == "RIIGSIBEBIZKCTZSSDGQMLSVUX"); 786 787 auto deciphered = enciphered.data.map!m3; 788 assert(deciphered.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); 789 }