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