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