tdd-deciphered.com

created by @parsingphase

Chapter 16: Completing the wiring

Now that we’ve simulated all the parts, we can put them together into a single, functional machine, which also implements our EncryptorInterface. We can consider the requirements on the machine by looking at how we’d implement the getOutputCharacterForInputCharacter method:

<?php
namespace Phase\Enigma;

class Machine implements EncryptorInterface
{ 
    /**
     * 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)
    {
        //Mechanical phase happens first
        $this->performTurnover();

        $signal = $inputCharacter;

        // Route through plugboard
        $signal = $this->plugboard->getOutputCharacterForInputCharacter($signal);

        // Route through each rotor slot in turn, right to left
        foreach ($this->rotorSlots as $rotorSlot) {
            $signal = $rotorSlot->getOutputCharacterForInputCharacter($signal);
        }

        // Route through reflector
        $signal = $this->reflector->getOutputCharacterForInputCharacter($signal);

        // Now route through rotors, in reverse order, right to left
        $rotorSlotsBackwards = array_reverse($this->rotorSlots);
        /* @var RotorSlot[] $rotorSlotsBackwards */
        foreach ($rotorSlotsBackwards as $rotorSlot) {
            $signal = $rotorSlot->getOutputCharacterForInputCharacterReversedSignal($signal);
        }

        // Route back out through the plugboard (which is symmetrical)
        $signal = $this->plugboard->getOutputCharacterForInputCharacter($signal);

        return $signal;
    }

We’ve got a new requirement that emerges from routing the signal back from the reflector and out through the plugboard, to simulate a signal entering a rotor slot from the left and exiting to the right:

$rotorSlot->getOutputCharacterForInputCharacterReversedSignal($signal)

For the rotor slot to be able to do this, the rotor it contains needs to have the same capability. We can reflect this by adding an extended version of our EncryptorInterface:

<?php

namespace Phase\Enigma;

interface EncryptorInterfaceReversible extends EncryptorInterface {

    /**
     * Return the output for the given encryptor input in its current state, swapping input and output terminals
     *
     * @param string $inputCharacter Single character, uppercase
     * @return string Single character, uppercase
     */
    public function getOutputCharacterForInputCharacterReversedSignal($inputCharacter);
}

Our RotorSlot and Rotor classes can then implement that interface instead of the simpler EncryptorInterface. This is most easily done by refactoring these classes; we know that our regression tests will ensure that we don’t break the functionality as we do so.

The current implementation of RotorSlot::getOutputCharacterForInputCharacter() can be replaced with:

public function getOutputCharacterForInputCharacter($inputCharacter)
{
    return $this->encipherCharacterDirectional($inputCharacter, true);
}

public function getOutputCharacterForInputCharacterReversedSignal($inputCharacter)
{
    return $this->encipherCharacterDirectional($inputCharacter, false);
}

/**
 * @param $inputCharacter
 * @return string
 */
protected function encipherCharacterDirectional($inputCharacter, $forward)
{
    $inputCharacter = strtoupper($inputCharacter);
    $rotorInputCharacter = $this->getCharacterOffsetBy(
        $inputCharacter,
        $this->rotorOffset - 1
    );

    if ($forward) {
        $rotorOutputCharacter = $this->rotor->getOutputCharacterForInputCharacter(
            $rotorInputCharacter
        );
    } else {
        $rotorOutputCharacter = $this->rotor->getOutputCharacterForInputCharacterReversedSignal(
            $rotorInputCharacter
        );
    }

    $outputCharacter = $this->getCharacterOffsetBy(
        $rotorOutputCharacter,
        0 - ($this->rotorOffset - 1)
    );
    return $outputCharacter;
} 

Similarly for Rotor:

/**
 * 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)
{
    $outputCharacter = $this->encipherCharacterDirectional($inputCharacter, true);
    return ($outputCharacter);
}

/**
 * Return the output for the given encryptor input in its current state, left-to-right
 *
 * @param string $inputCharacter Single character, uppercase
 * @return string Single character, uppercase
 */
public function getOutputCharacterForInputCharacterReversedSignal($inputCharacter)
{
    $outputCharacter = $this->encipherCharacterDirectional($inputCharacter, false);
    return ($outputCharacter);
}

/**
 * @param $inputCharacter
 * @return string
 */
protected function encipherCharacterDirectional($inputCharacter, $forwardDirection = true)
{
    $inputCharacter = strtoupper($inputCharacter);

    if (!preg_match('/^[A-Z]$/', $inputCharacter)) {
        throw new \InvalidArgumentException;
    }

    $coreInputCharacter = $this->getCharacterOffsetBy(
        $inputCharacter,
        $this->ringOffset - 1
    );

    $mapping = $forwardDirection ? $this->coreMapping : array_flip($this->coreMapping);

    $coreOutputCharacter = $mapping[$coreInputCharacter];

    $outputCharacter = $this->getCharacterOffsetBy(
        $coreOutputCharacter,
        0 - ($this->ringOffset - 1)
    );

    return $outputCharacter;
}

We can most easily test these by modifying \Phase\Enigma\RotorTest::testRotorIMappingOffset() and adding one last line:

    $this->assertSame($testInputCharacter,
        $rotor->getOutputCharacterForInputCharacterReversedSignal($testOutputCharacter));

and something similar for \Phase\Enigma\RotorSlotTest::testSingleSlotRotorOneRingOffsetOne():

    $slotOutputReversed = $slot->getOutputCharacterForInputCharacterReversedSignal($slotOutput);
    $this->assertSame($slotOutputReversed, $slotInput);

Tag: Chapter16-1-ReversibleWiring