tdd-deciphered.com

created by @parsingphase

Chapter 13: Combining the elements

From our understanding of the pawl and notch mechanism, we can see that we can only simulate this part of the system properly by integrating the elements that affect each other. We can consider a structure looking like:

Slot 2 Slot 1 Slot 0
Rotor Rotor Rotor
Pawl 2 Pawl 1 Pawl 0

If we simulate a Pawl class, we need to give it a canPush method that relates to the rotor notch positions in the adjacent slots. We noted the requirement above:

pawl.canPush = (rightRotor.isAbsent OR rightRotor.hasNotchOnLeftSideInCurrentPosition)

The rotor in a given slot will turn on keypress if slot.leftPawl.canPush or slot.rightPawl.canPush

So each slot needs to be told the identify of its adjacent pawls, and each pawl needs to know its right-hand slot’s rotor position.

Knowing this, we can set tests for the pawls:

<?php

namespace Phase\Enigma;

class PawlTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @param RotorSlot $rightSlot
     * @dataProvider primedSlotProvider
     */
    public function testCanPushOnValidConditions(RotorSlot $rightSlot)
    {
        $pawl = new Pawl;
        $pawl->setRightRotorSlot($rightSlot);
        $this->assertTrue($pawl->canPush());
    }


    /**
     * @param RotorSlot $rightSlot
     * @dataProvider primedSlotProvider We can use the existing provided and un-prime the rotors
     */
    public function testCannotPushOnInvalidConditions(RotorSlot $rightSlot)
    {
        $rightSlot->incrementRotorOffset(); // No provided rotor has two adjacent notches
        $pawl = new Pawl;
        $pawl->setRightRotorSlot($rightSlot);
        $this->assertFalse($pawl->canPush());
    }


    public function primedSlotProvider()
    {
        $rotorFactory = new RotorFactory();
        // use only rotors with notches
        $rotors = [
            $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_ONE),
            $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_THREE),
            $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_SIX),
        ];

        $params = [];

        foreach ($rotors as $rotor) {
            /* @var Rotor $rotor */
            $rotorNotchPositions = $rotor->getNotchPositions();
            $slot = new RotorSlot();
            $slot->loadRotor($rotor);
            $slot->setRotorOffset($rotorNotchPositions[0]); // loaded rotor is at a pushable position
            $params[] = [$slot];
        }

        return $params;
    }
}

and implement these in a Pawl class:

<?php

namespace Phase\Enigma;

class Pawl
{

    /**
     * @var RotorSlot|null
     */
    protected $rightRotorSlot;

    /**
     * @return null|RotorSlot
     */
    public function getRightRotorSlot()
    {
        return $this->rightRotorSlot;
    }

    /**
     * @param null|RotorSlot $rightRotorSlot
     */
    public function setRightRotorSlot(RotorSlot $rightRotorSlot)
    {
        $this->rightRotorSlot = $rightRotorSlot;
    }

    public function canPush()
    {
        if ($this->rightRotorSlot && $this->rightRotorSlot->getRotor()) {
            $rotor = $this->rightRotorSlot->getRotor();
            $notchChars = $rotor->getNotchPositions();
            $rotorOffset = $this->rightRotorSlot->getRotorOffset();
            $rotorOffsetChar = (chr($rotorOffset + 64)); //really need to normalise this at some point
            $canPush = in_array($rotorOffsetChar, $notchChars);
        } else {
            $canPush = true;
        }

        return $canPush;
    }

}

Note that we’ve needed to add an accessor function to the RotorSlot class so we can check the rotor’s notch positions:

/**
 * @return Rotor
 */
public function getRotor()
{
    return $this->rotor;
}

Tag: Chapter13-1-Pawls

We can add similar tests and capabilities to the slots:

Tests:

/**
 * Test one pawl to the right of one rotor
 */
public function testEngagePawlSingleRotorSinglePawl()
{
    $pawl = new Pawl();
    $rotorFactory = new RotorFactory();
    $rotor = $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_ONE);
    $rotorSlot = new RotorSlot();
    $rotorSlot->loadRotor($rotor);
    $rotorSlot->setRightPawl($pawl); // make pawls act on slots as it saves us reconfiguring if we swap rotors

    $this->assertTrue($pawl->canPush()); // no rotor to right of pawl
    $this->assertTrue($rotorSlot->canEngagePawl()); // will always push in this situation
}

/**
 * Test one pawl between two rotors
 */
public function testEngagePawlTwoRotorsSinglePawl()
{
    $pawl = new Pawl();
    $rotorFactory = new RotorFactory();
    $leftRotor = $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_ONE);
    $leftRotorSlot = new RotorSlot();
    $leftRotorSlot->loadRotor($leftRotor);
    $leftRotorSlot->setRightPawl($pawl); // make pawls act on slots as it saves us reconfiguring if we swap rotors

    $rightRotor = $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_TWO);
    $rightRotorSlot = new RotorSlot();
    $rightRotorSlot->loadRotor($rightRotor);
    $rightRotorSlot->setLeftPawl($pawl);

    $pawl->setRightRotorSlot($rightRotorSlot);

    // Pawl can only engage if both rotors are in turnover positions:
    $leftRotorSlot->setRotorOffset($leftRotor->getNotchPositions()[0]);
    $rightRotorSlot->setRotorOffset($rightRotor->getNotchPositions()[0]);

    $this->assertTrue($pawl->canPush());
    $this->assertTrue($leftRotorSlot->canEngagePawl());
    $this->assertTrue($rightRotorSlot->canEngagePawl());

    // then turn rotors, and neither can push on next attempt

    $leftRotorSlot->incrementRotorOffset();
    $rightRotorSlot->incrementRotorOffset();

    $this->assertFalse($pawl->canPush());
    $this->assertFalse($leftRotorSlot->canEngagePawl());
    $this->assertFalse($rightRotorSlot->canEngagePawl());
}


/**
 * Test two rotors with pawl to right of each
 */
public function testEngagePawlTwoRotorsTwoPawls()
{
    $leftPawl = new Pawl();
    $rightPawl = new Pawl();
    $rotorFactory = new RotorFactory();
    $leftRotor = $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_ONE);
    $leftRotorSlot = new RotorSlot();
    $leftRotorSlot->loadRotor($leftRotor);
    $leftRotorSlot->setRightPawl($leftPawl); // make pawls act on slots as it saves us reconfiguring if we swap rotors

    $rightRotor = $rotorFactory->buildRotorInstance(RotorFactory::ROTOR_TWO);
    $rightRotorSlot = new RotorSlot();
    $rightRotorSlot->loadRotor($rightRotor);
    $rightRotorSlot->setLeftPawl($leftPawl);
    $rightRotorSlot->setRightPawl($rightPawl);

    $leftPawl->setRightRotorSlot($rightRotorSlot);

    // Pawl can only engage if both rotors are in turnover positions:
    $leftRotorSlot->setRotorOffset($leftRotor->getNotchPositions()[0]);
    $rightRotorSlot->setRotorOffset($rightRotor->getNotchPositions()[0]);

    $this->assertTrue($leftPawl->canPush());
    $this->assertTrue($rightPawl->canPush());
    $this->assertTrue($leftRotorSlot->canEngagePawl());
    $this->assertTrue($rightRotorSlot->canEngagePawl());

    // then turn rotors, and only right pawl/slot can push on next attempt

    $leftRotorSlot->incrementRotorOffset();
    $rightRotorSlot->incrementRotorOffset();

    $this->assertFalse($leftPawl->canPush());
    $this->assertTrue($rightPawl->canPush());
    $this->assertFalse($leftRotorSlot->canEngagePawl());
    $this->assertTrue($rightRotorSlot->canEngagePawl());
}

and implementation:

class RotorSlot implements EncryptorInterface
{
//…
    /**
     * @var Rotor
     */
    protected $rotor;
    /**
     * @var int
     */
    protected $rotorOffset;

    /**
     * @var Pawl
     */
    protected $leftPawl;

    /**
     * @var Pawl
     */
    protected $rightPawl;

//…

    /**
     * @return Pawl
     */
    public function getLeftPawl()
    {
        return $this->leftPawl;
    }

    /**
     * @param Pawl $leftPawl
     */
    public function setLeftPawl($leftPawl)
    {
        $this->leftPawl = $leftPawl;
    }

    /**
     * @return Pawl
     */
    public function getRightPawl()
    {
        return $this->rightPawl;
    }

    /**
     * @param Pawl $rightPawl
     */
    public function setRightPawl($rightPawl)
    {
        $this->rightPawl = $rightPawl;
    }

    /**
     * Are either of the pawls adjacent to this slot in a position to engage the rotor?
     */
    public function canEngagePawl()
    {
        return(($this->leftPawl && $this->leftPawl->canPush()) || ($this->rightPawl && $this->rightPawl->canPush()));
    }

Tag: Chapter13-2-PawlEngagement