view purple/switch.py @ 14:eea5734a1655

Build encrypt wiring table inside switches. Add tests. Separate switch stepping into a new method in preparation for adding encrypt.
author Brian Neal <bgneal@gmail.com>
date Mon, 02 Dec 2013 20:44:26 -0600
parents d2c21b6c5bbc
children adaca0a6b2b4
line wrap: on
line source
# Copyright (C) 2013 by Brian Neal.
# This file is part of purple, the PURPLE (Cipher Machine 97) simulation.
# purple is released under the MIT License (see LICENSE.txt).

"""This module contains the SteppingSwitch class and a factory function to
create the standard switches used on the PURPLE machine.

"""
import purple.data as data

# "Enum" to name the standard switches:
SIXES, TWENTIES_1, TWENTIES_2, TWENTIES_3 = range(4)


class SteppingSwitchError(Exception):
    """Exception class for all stepping switch errors"""


class SteppingSwitch:
    """This class simulates a stepping switch, the primary cryptographic element
    in the PURPLE cipher machine.

    """
    def __init__(self, wiring, init_pos=0):
        """Construct a SteppingSwitch from a wiring list, which must be a list
        of lists (one list per input level). Each inner list is a list of
        integers representing the output contacts. The initial position of the
        switch can also be set; this defaults to 0.

        The wiring lists are assumed to be 0-based and represent the decrypt
        path through the switch. A reciprocal encrypt wiring map will be
        constructed from this argument internally.

        """
        self.dec_wiring = wiring
        self.num_positions = len(wiring)
        self.num_levels = len(wiring[0])

        if not all(self.num_levels == len(level) for level in wiring):
            raise SteppingSwitchError("Ragged wiring table")

        self._build_encrypt_wiring()
        self.set_pos(init_pos)

    def set_pos(self, pos):
        """Set the switch position to pos.
        Raises a SteppingSwitchError if pos is out of range.

        """
        if not (0 <= pos < self.num_positions):
            raise SteppingSwitchError("Illegal switch position")
        self.pos = pos

    def step(self):
        """Advance the stepping switch position.

        The new 0-based position of the switch is returned.

        """
        self.pos = (self.pos + 1) % self.num_positions
        return self.pos

    def decrypt(self, level):
        """This method is how to determine the output signal from the stepping
        switch for the decrypt path. The parameter 'level' is the integer input
        level for the incoming signal. The integer outgoing contact level is
        returned.

        """
        return self.dec_wiring[self.pos][level]

    def encrypt(self, level):
        """This method is how to determine the output signal from the stepping
        switch for the encrypt path. The parameter 'level' is the integer input
        level for the incoming signal. The integer outgoing contact level is
        returned.

        """
        return self.enc_wiring[self.pos][level]

    def _build_encrypt_wiring(self):
        """This method builds an encrypt wiring table from the decrypt wiring
        table member.

        """
        self.enc_wiring = []
        for level in self.dec_wiring:
            self.enc_wiring.append(
                [level.index(n) for n in range(self.num_levels)])


def create_switch(switch_type, init_pos=0):
    """Factory function for building a SteppingSwitch of the requested
    standard type. The initial position of the switch can be specified.

    The switch_type parameter must be one of the module level constants:
        * SIXES
        * TWENTIES_1
        * TWENTIES_2
        * TWENTIES_3

    A ValueError will be raised if switch_type is an illegal value.

    """
    wiring_map = {
        SIXES: data.SIXES_DATA,
        TWENTIES_1: data.TWENTIES_1_DATA,
        TWENTIES_2: data.TWENTIES_2_DATA,
        TWENTIES_3: data.TWENTIES_3_DATA,
    }
    wiring = wiring_map.get(switch_type)
    if not wiring:
        raise ValueError("illegal switch type")

    return SteppingSwitch(wiring, init_pos)