tdd-deciphered.com

created by @parsingphase

Chapter 15: Last element: Reflectors

As mentioned previously, we can consider the reflectors as simpler, fixed rotors, and so we can define their configuration as a simple array mapping. As this mapping must be entirely reciprocal (the inputs and outputs are the same physical contacts), we can define just 13 mapping pairs which we refer to as a “half mapping”.

If we set up a reflector test:

<?php
namespace Phase\Enigma;

class ReflectorTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @param array[] $halfMapping
     * @param string $inChar
     * @param string $expectedOutChar
     * @dataProvider mappingTestDataProvider
     */
    public function testCreateAndConfigureRotor($halfMapping, $inChar, $expectedOutChar)
    {
        $reflector = new Reflector;
        $this->assertTrue($reflector instanceof Reflector);
        $reflector->setHalfMapping($halfMapping);
        $outChar = $reflector->getOutputCharacterForInputCharacter($inChar);
        $this->assertSame($expectedOutChar, $outChar);
    }

    public function mappingTestDataProvider()
    {
        $halfMappingB = [
            'A' => 'Y',
            'B' => 'R',
            'C' => 'U',
            'D' => 'H',
            'E' => 'Q',
            'F' => 'S',
            'G' => 'L',
            'I' => 'P',
            'J' => 'X',
            'K' => 'N',
            'M' => 'O',
            'T' => 'Z',
            'V' => 'W'
        ];

        $data = [];

        //test specified mapping
        $data[] = [$halfMappingB, 'I', 'P'];
        $data[] = [$halfMappingB, 'Z', 'T'];

        return $data;
    }
}

we can implement with:

class Reflector implements EncryptorInterface
{
    /**
     * @var array[] full 26-char=>char array
     */
    protected $mapping;

    public function setHalfMapping($halfMapping)
    {
        $this->mapping = $halfMapping + array_flip($halfMapping);
    }

    /**
     * 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)
    {
        return $this->mapping[$inputCharacter];
    }
}

We can also add similar set up some input validity tests for our new class functions:

/**
 * @param $halfMapping
 * @dataProvider invalidHalfMappingProvider
 * @expectedException \InvalidArgumentException
 */
public function testInvalidRotorHalfMappings($halfMapping)
{
    $reflector = new Reflector();
    $reflector->setHalfMapping($halfMapping);
}

public function invalidHalfMappingProvider()
{
    $badMappings = [
        [null], // not array
        [['A']], // not an associative array
        [   // too short
            ['A' => 'Y', 'B' => 'R', 'C' => 'U', 'D' => 'H', 'E' => 'Q', 'F' => 'S', 'G' => 'L', 'I' => 'P']
        ],
        [   // mapping to self
            [
                'A' => 'A',
                'B' => 'R',
                'C' => 'U',
                'D' => 'H',
                'E' => 'Q',
                'F' => 'S',
                'G' => 'L',
                'I' => 'P',
                'J' => 'X',
                'K' => 'N',
                'M' => 'O',
                'T' => 'Z',
                'V' => 'W'
            ]
        ],
        [   // Same char in 2 mappings
            [
                'A' => 'Y',
                'B' => 'A',
                'C' => 'U',
                'D' => 'H',
                'E' => 'Q',
                'F' => 'S',
                'G' => 'L',
                'I' => 'P',
                'J' => 'X',
                'K' => 'N',
                'M' => 'O',
                'T' => 'Z',
                'V' => 'W'
            ]
        ],
        [ // non-char elements
            1 => 25,
            'B' => 'R',
            'C' => 'U',
            'D' => 'H',
            'E' => 'Q',
            'F' => 'S',
            'G' => 'L',
            'I' => 'P',
            'J' => 'X',
            'K' => 'N',
            'M' => 'O',
            'T' => 'Z',
            'V' => 'W'
        ]

    ];
    return $badMappings;
}

and handle this in the Reflector class:

//revised function
public function setHalfMapping($halfMapping)
{
    if (!$this->isValidHalfMapping($halfMapping)) {
        throw new \InvalidArgumentException;
    }
    $this->mapping = $halfMapping + array_flip($halfMapping);
}

//...
protected function isValidHalfMapping($halfMapping)
{
    $valid = false;
    if (is_array($halfMapping) && (count($halfMapping) == 13)) {
        $fullMapping = $halfMapping + array_flip($halfMapping);
        if (count($fullMapping) == 26) {
            $keysValuesOK = true;
            $seenKeys = $seenValues = [];
            foreach ($fullMapping as $k => $v) {
                if (isset($seenKeys[$k]) || isset($seenValues[$v])) {
                    $keysValuesOK = false;
                }
                if (
                    (!preg_match('/^[A-Z]$/', $k)) ||
                    (!preg_match('/^[A-Z]$/', $v))
                ) {
                    $keysValuesOK = false;
                }

                $seenKeys[$k] = true;
                $seenValues[$v] = true;
            }
            $valid = $keysValuesOK;
        }
    }

    return $valid;
}

Tag: Chapter15-1-Reflectors

We can also add a factory for the standard rotor types used with the Enigma:

<?php
namespace Phase\Enigma;

class ReflectorFactory
{

    const REFLECTOR_B = 'B';
    const REFLECTOR_C = 'C';
    const REFLECTOR_B_THIN = 'Bd';
    const REFLECTOR_C_THIN = 'Cd';

    protected $halfReflectorSpecs = [
        self::REFLECTOR_B => [
            'A' => 'Y',
            'B' => 'R',
            'C' => 'U',
            'D' => 'H',
            'E' => 'Q',
            'F' => 'S',
            'G' => 'L',
            'I' => 'P',
            'J' => 'X',
            'K' => 'N',
            'M' => 'O',
            'T' => 'Z',
            'V' => 'W'
        ],
        self::REFLECTOR_C => [
            'A' => 'F',
            'B' => 'V',
            'C' => 'P',
            'D' => 'J',
            'E' => 'I',
            'G' => 'O',
            'H' => 'Y',
            'K' => 'R',
            'L' => 'Z',
            'M' => 'X',
            'N' => 'W',
            'T' => 'Q',
            'S' => 'U'
        ],
        self::REFLECTOR_B_THIN => [
            'A' => 'E',
            'B' => 'N',
            'C' => 'K',
            'D' => 'Q',
            'F' => 'U',
            'G' => 'Y',
            'H' => 'W',
            'I' => 'J',
            'L' => 'O',
            'M' => 'P',
            'R' => 'X',
            'S' => 'Z',
            'T' => 'V',
        ],
        self::REFLECTOR_C_THIN => [
            'A' => 'R',
            'B' => 'D',
            'C' => 'O',
            'E' => 'J',
            'F' => 'N',
            'G' => 'T',
            'H' => 'K',
            'I' => 'V',
            'L' => 'M',
            'P' => 'W',
            'Q' => 'Z',
            'S' => 'X',
            'U' => 'Y',
        ]
    ];

    /**
     * @param $instanceId
     * @return Reflector
     */
    public function buildReflectorInstance($instanceId)
    {    
        $reflector = new Reflector();
        $reflector->setHalfMapping($this->halfReflectorSpecs[$instanceId]);

        return $reflector;
    }

    public function getSupportedReflectorIdentities()
    {
        return (array_keys($this->halfReflectorSpecs));
    }
}

And a simple test:

<?php
namespace Phase\Enigma;


class ReflectorFactoryTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @param Reflector $reflector
     * @dataProvider supportedReflectorsDataProvider
     */
    public function testBuildAllReflectorsNoExceptions(Reflector $reflector)
    {
        $this->assertTrue($reflector instanceof EncryptorInterface);
    }

    public function supportedReflectorsDataProvider()
    {
        $factory = new ReflectorFactory();
        $rotorIds = $factory->getSupportedReflectorIdentities();
        $parameterLists = [];
        foreach ($rotorIds as $reflectorId) {
            $parameterLists[] = [$factory->buildReflectorInstance($reflectorId)];
        }
        return $parameterLists;
    }
}

We now have all the elements we need.

Tag: Chapter15-2-ReflectorFactory