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