Mercurial > public > enigma
view enigma/plugboard.py @ 28:067205259796
Correct name of command-line argument for key file.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Tue, 29 May 2012 17:21:16 -0500 |
parents | abf2132ad338 |
children | 160e1bd59965 |
line wrap: on
line source
# Copyright (C) 2012 by Brian Neal. # This file is part of Py-Enigma, the Enigma Machine simulation. # Py-Enigma is released under the MIT License (see License.txt). """Contains the Plugboard class for simulating the plugboard (Steckerbrett) component of the Enigma Machine. """ import collections from itertools import chain import string # On Heer & Luftwaffe (?) models, the plugs are labeled with upper case letters HEER_LABELS = string.ascii_uppercase # The number of plugboard cables supplied with a machine: MAX_PAIRS = 10 class PlugboardError(Exception): pass class Plugboard: """The plugboard allows the operator to swap letters before and after the entry wheel. This is accomplished by connecting cables between pairs of plugs that are marked with letters (Heer & Luftwaffe models) or numbers (Kriegsmarine). Ten cables were issued with each machine; thus up to 10 of these swappings could be used as part of a machine setup. Each cable swaps both the input and output signals. Thus if A is connected to B, A crosses to B in the keyboard to entry wheel direction and also in the reverse entry wheel to lamp direction. """ def __init__(self, wiring_pairs=None): """Configure the plugboard according to a list or tuple of integer pairs, or None. A value of None or an empty list/tuple indicates no plugboard connections are to be used (i.e. a straight mapping). Otherwise wiring_pairs must be an iterable of integer pairs, where each integer is between 0-25, inclusive. At most 10 such pairs can be specified. Each value represents an input/output path through the plugboard. It is invalid to specify the same path more than once in the list. If an invalid wiring_pairs parameter is given, a PlugboardError is raised. """ # construct wiring mapping table with default 1-1 mappings self.wiring_map = list(range(26)) # use settings if provided if not wiring_pairs: return if len(wiring_pairs) > MAX_PAIRS: raise PlugboardError('Please specify %d or less pairs' % MAX_PAIRS) # ensure a path occurs at most once in the list counter = collections.Counter(chain.from_iterable(wiring_pairs)) path, count = counter.most_common(1)[0] if count != 1: raise PlugboardError('duplicate connection: %d' % path) # make the connections for pair in wiring_pairs: m = pair[0] n = pair[1] if not (0 <= m < 26) or not (0 <= n < 26): raise PlugboardError('invalid connection: %s' % str(pair)) self.wiring_map[m] = n self.wiring_map[n] = m @classmethod def from_key_sheet(cls, settings=None): """Configure the plugboard according to a settings string as you may find on a key sheet. Two syntaxes are supported, the Heer/Luftwaffe and Kriegsmarine styles: In the Heer syntax, the settings are given as a string of alphabetic pairs. For example: 'PO ML IU KJ NH YT GB VF RE DC' In the Kriegsmarine syntax, the settings are given as a string of number pairs, separated by a '/'. Note that the numbering uses 1-26, inclusive. For example: '18/26 17/4 21/6 3/16 19/14 22/7 8/1 12/25 5/9 10/15' To specify no plugboard connections, settings can be None or an empty string. A PlugboardError will be raised if the settings string is invalid, or if it contains more than MAX_PAIRS pairs. Each plug should be present at most once in the settings string. """ if not settings: return cls(None) wiring_pairs = [] # detect which syntax is being used if settings.find('/') != -1: # Kriegsmarine syntax pairs = settings.split() for p in pairs: try: m, n = p.split('/') m, n = int(m), int(n) except ValueError: raise PlugboardError('invalid pair: %s' % p) wiring_pairs.append((m - 1, n - 1)) else: # Heer/Luftwaffe syntax pairs = settings.upper().split() for p in pairs: if len(p) != 2: raise PlugboardError('invalid pair: %s' % p) m = p[0] n = p[1] if m not in HEER_LABELS or n not in HEER_LABELS: raise PlugboardError('invalid pair: %s' % p) wiring_pairs.append((ord(m) - ord('A'), ord(n) - ord('A'))) return cls(wiring_pairs) def signal(self, n): """Simulate a signal entering the plugboard on wire n, where n must be an integer between 0 and 25. Returns the wire number of the output signal (0-25). Note that since the plugboard always crosses pairs of wires, it doesn't matter what direction (keyboard -> entry wheel or vice versa) the signal is coming from. """ return self.wiring_map[n]