tdd-deciphered.com

created by @parsingphase

Chapter 17: Final assembly

We’re now in position to do end-to-end assembly and test. The easiest test to envision is one where we take known inputs and settings and see if we can achieve the correct outputs. To do this you’ll need one or more of:

  • Access to an original Enigma machine.
  • An existing message encrypt or decrypt with rotor settings and output.
  • An existing known-good simulator.

Now, original Enigma machines typically retail in the order of £100,000, so the first option won’t be widely available. And while I was lucky enough to have the chance to use one while volunteering at Bletchley Park, I don’t have any notes of the rotor and plugboard settings used.

Fortunately though, there are other simulators, such as Mininigmafor iOS, or Terry Long's simulator for OSX - and I’ve had the chance to verify these against a real machine.

As our first sample message then, we can use one from the simulators, with no plugboard connections:

Input Message: ACTIONTHISDAY
Rotors (left to right): III, II, I
Ring settings: A,A,C
Rotor offsets:A,A,P
Reflector: B
Plugboard: none
Output message: DWOUFBIGODOGS

Why this particular encrypt has a canine theme, I’ve no idea, but it’ll do for starters. If we build and configure a machine from the ground up in a test, it might look like:

<?php
namespace Phase\Enigma;

class MachineTest extends \PHPUnit_Framework_TestCase
{

    public function testWholeMachine()
    {
        $machine = new Machine();

        $rotorFactory = new RotorFactory();

        $rotors = [
            $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_ONE),
            $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_TWO),
            $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_THREE),
        ];

        /* @var Rotor[] $rotors */
        $rotors[0]->setRingOffset('C');
        $rotors[1]->setRingOffset('A');
        $rotors[2]->setRingOffset('A');

        /* @var RotorSlot[] $rotorSlots */
        $rotorSlots = [
            new RotorSlot(),
            new RotorSlot(),
            new RotorSlot(),
        ];

        foreach ($rotorSlots as $k => $v) {
            $v->loadRotor($rotors[$k]);
        }

        $rotorSlots[0]->setRotorOffset('P');
        $rotorSlots[1]->setRotorOffset('A');
        $rotorSlots[2]->setRotorOffset('A');

        /* @var Pawl[] $pawls */
        $pawls = [
            new Pawl(),
            new Pawl(),
            new Pawl(),
        ];

        $reflectorFactory = new ReflectorFactory();
        $reflector = $reflectorFactory->buildReflectorInstance(ReflectorFactory::REFLECTOR_B);

        $machine->setRotorSlots($rotorSlots);
        $machine->setPawls($pawls);
        $machine->setPlugboard(new Plugboard()); // defaults
        $machine->setReflector($reflector);

        $messageString = strtoupper('ActionThisDay');
        $messageArray = preg_split('//', $messageString, -1, PREG_SPLIT_NO_EMPTY);

        $out = [];

        foreach ($messageArray as $inChar) {
            $out[] = $machine->getOutputCharacterForInputCharacter($inChar);
        }
        $enciphered = join('', $out);

        $this->assertSame('DWOUFBIGODOGS', $enciphered);
    }
}

As we’re making no assumptions about the innards of our machine, we directly specify 3 rotors & slots with one pawl per slot. We’re also specifying rotors and slots right-to-left, and with more than 50 lines in our test function, we’re pushing the definition of a “unit” test somewhat too. However, we’ve tested all elements independently, and it’s useful to test the whole system too.

We looked at \Phase\Enigma\Machine::getOutputCharacterForInputCharacter() in the last chapter. If we work through the implied functions from both that and the test function, we end up with the rest of the class looking like this:

<?php
namespace Phase\Enigma;

class Machine implements EncryptorInterface
{

    /**
     * @var RotorSlot[]
     */
    protected $rotorSlots;

    /**
     * @var Pawl[]
     */
    protected $pawls;

    /**
     * @var Plugboard
     */
    protected $plugboard;

    /**
     * @var Reflector
     */
    protected $reflector;

    /**
     * @return Pawl[]
     */
    public function getPawls()
    {
        return $this->pawls;
    }

    /**
     * @param Pawl[] $pawls
     */
    public function setPawls($pawls)
    {
        $this->pawls = $pawls;
        $this->setupMechanicalInterconnects();
    }

    /**
     * @return Plugboard
     */
    public function getPlugboard()
    {
        return $this->plugboard;
    }

    /**
     * @param Plugboard $plugboard
     */
    public function setPlugboard($plugboard)
    {
        $this->plugboard = $plugboard;
    }

    /**
     * @return RotorSlot[]
     */
    public function getRotorSlots()
    {
        return $this->rotorSlots;
    }

    /**
     * @param RotorSlot[] $rotorSlots
     */
    public function setRotorSlots($rotorSlots)
    {
        $this->rotorSlots = $rotorSlots;
        $this->setupMechanicalInterconnects();
    }

    /**
     * @return Reflector
     */
    public function getReflector()
    {
        return $this->reflector;
    }

    /**
     * @param Reflector $reflector
     */
    public function setReflector($reflector)
    {
        $this->reflector = $reflector;
    }

    /**
     * Reset the mechanical interconnections between pawls and rotors for as many of each as are currently configured
     */    
    public function setupMechanicalInterconnects()
    {
        // interconnect pawls and rotor slots
        foreach ($this->rotorSlots as $index => $rotorSlot) {
            if (isset($this->pawls[$index])) {
                $rightPawl = $this->pawls[$index];
                $rotorSlot->setRightPawl($rightPawl);
            }

            if (isset($this->pawls[$index + 1])) {
                $leftPawl = $this->pawls[$index + 1];
                $rotorSlot->setLeftPawl($leftPawl);
                $leftPawl->setRightRotorSlot($rotorSlot);
            }
        }
    }

    public function performTurnover()
    {
        $rotorSlotsToTurn=[];
        foreach ($this->rotorSlots as $index => $rotorSlot) {
            if ($rotorSlot->canEngagePawl()) {
                $rotorSlotsToTurn[]=$index;
            }
        }
        foreach($rotorSlotsToTurn as $index) {
            $this->rotorSlots[$index]->incrementRotorOffset();
        }
    }

    /**
     * Return the output for the given encryptor input in its current state
     *
     * @param string $inputCharacter Single character, uppercase
     * @return string Single character, uppercase
     */
    public function getOutputCharacterForInputCharacter($inputCharacter)
    {
     // As above
    }
}

As the tests now all pass, we can be pretty sure that we’ve built a fully working device. We can take another example message from the M4 project at http://users.telenet.be/d.rijmenants/en/m4project.htm

Enigma model: Kriegsmarine M4

Reflector: B Thin (not B as the M4 site says; only thin reflectors can be used with 4 rotors in the M4)

Rotors: Beta - II - IV - I

Stecker: AT BL DF GJ HM NW OP QY RZ VX

Ringsettings: A-A-A-V

Rotor startposition: V-J-N-A

Encrypted message = ‘NCZW VUSX PNYM INHZ XMQX SFWX WLKJ AHSH NMCO CCAK UQPM KCSM HKSE INJU SBLK IOSX CKUB HMLL XCSJ USRR DVKO HULX WCCB GVLI YXEO AHXR HKKF VDRE WEZL XOBA FGYU JQUK GRTV UKAM EURB VEKS UHHV OYHA BCJW MAKL FKLM YFVN RIZR VVRT KOFD ANJM OLBG FFLE OPRG TFLV RHOW OPBE KVWM UQFM PWPA RMFH AGKX IIBG’

Decrypted message = ‘VONV ONJL OOKS JHFF TTTE INSE INSD REIZ WOYY QNNS NEUN INHA LTXX BEIA NGRI FFUN TERW ASSE RGED RUEC KTYW ABOS XLET ZTER GEGN ERST ANDN ULAC HTDR EINU LUHR MARQ UANT ONJO TANE UNAC HTSE YHSD REIY ZWOZ WONU LGRA DYAC HTSM YSTO SSEN ACHX EKNS VIER MBFA ELLT YNNN NNNO OOVI ERYS ICHT EINS NULL’

Note that we’ll have to remove whitespace from this to process it.

This message was sent on an M4 enigma, which had 4 slots rather than the three we’ve worked with previously, so we can wrap this into the following test:

/**
 * Test with message from http://users.telenet.be/d.rijmenants/en/m4project.htm
 * Note: using B-THIN reflector
 */
public function testM4Machine()
{
    $machine = new Machine();

    $rotorFactory = new RotorFactory();

    $rotors = [
        $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_ONE),
        $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_FOUR),
        $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_TWO),
        $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_BETA),
    ];

    /* @var Rotor[] $rotors */
    $rotors[0]->setRingOffset('V');
    $rotors[1]->setRingOffset('A');
    $rotors[2]->setRingOffset('A');
    $rotors[3]->setRingOffset('A');

    /* @var RotorSlot[] $rotorSlots */
    $rotorSlots = [
        new RotorSlot(),
        new RotorSlot(),
        new RotorSlot(),
        new RotorSlot(),
    ];

    foreach ($rotorSlots as $k => $v) {
        $v->loadRotor($rotors[$k]);
    }

    $rotorSlots[0]->setRotorOffset('A');
    $rotorSlots[1]->setRotorOffset('N');
    $rotorSlots[2]->setRotorOffset('J');
    $rotorSlots[3]->setRotorOffset('V');

    /* @var Pawl[] $pawls */
    $pawls = [
        new Pawl(),
        new Pawl(),
        new Pawl(), // not sure if M4 has 3 or 4 pawls.
    ];

    $reflectorFactory = new ReflectorFactory();
    $reflector = $reflectorFactory->buildReflectorInstance(ReflectorFactory::REFLECTOR_B_THIN);

    $plugboard = new Plugboard();
    $plugboard->setCableConnections(
        [
            'A' => 'T',
            'B' => 'L',
            'D' => 'F',
            'G' => 'J',
            'H' => 'M',
            'N' => 'W',
            'O' => 'P',
            'Q' => 'Y',
            'R' => 'Z',
            'V' => 'X'
        ]
    );

    $machine->setRotorSlots($rotorSlots);
    $machine->setPawls($pawls);
    $machine->setPlugboard($plugboard);
    $machine->setReflector($reflector);

    $rawInput = "NCZW VUSX PNYM INHZ XMQX SFWX WLKJ AHSH NMCO CCAK UQPM KCSM HKSE INJU SBLK IOSX CKUB HMLL XCSJ USRR DVKO HULX WCCB GVLI YXEO AHXR HKKF VDRE WEZL XOBA FGYU JQUK GRTV UKAM EURB VEKS UHHV OYHA BCJW MAKL FKLM YFVN RIZR VVRT KOFD ANJM OLBG FFLE OPRG TFLV RHOW OPBE KVWM UQFM PWPA RMFH AGKX IIBG";

    $messageString = preg_replace('/\W/', '', $rawInput);
    $messageArray = preg_split('//', $messageString, -1, PREG_SPLIT_NO_EMPTY);

    $out = [];

    foreach ($messageArray as $inChar) {
        $out[] = $machine->getOutputCharacterForInputCharacter($inChar);
    }
    $enciphered = join('', $out);

    $formattedOutput = "VONV ONJL OOKS JHFF TTTE INSE INSD REIZ WOYY QNNS NEUN INHA LTXX BEIA NGRI FFUN TERW ASSE RGED RUEC KTYW ABOS XLET ZTER GEGN ERST ANDN ULAC HTDR EINU LUHR MARQ UANT ONJO TANE UNAC HTSE YHSD REIY ZWOZ WONU LGRA DYAC HTSM YSTO SSEN ACHX EKNS VIER MBFA ELLT YNNN NNNO OOVI ERYS ICHT EINS NULL";

    $plainOutput = preg_replace('/\W/', '', $formattedOutput);

    $this->assertSame($plainOutput, $enciphered);
}

To actually read the resulting plaintext, see the explanation on the M4 site!

So… we’ve now got a fully working Enigma simulator, so we can wrap up the main body of this series here, with git tag Chapter17-1-WorkingMachine, and indeed with release 0.1.0

Development of this project will continue, but will not be documented quite as exhaustively as it has up to this point. To keep up, you're invited to watch the enigma-simulator Github repository .