Source code for component

from typing import TypeVar,  Union, Optional, Callable, Dict,  List,  Tuple
import random
import copy

stdAlphabet : str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
sgsAlphabet : str = 'ABCDEFGHIJKLMNOPQRSTUVXYZÅÄÖ'

class Umkehrwalze: pass
class Steckerbrett: pass
class Zusatzwalze: pass
class Walze: pass
Component = TypeVar('Component', Umkehrwalze, Steckerbrett, Zusatzwalze, Walze)

[docs]class Umkehrwalze(object): """Represents an Umkehrwalze (UKW == reflector) .. csv-table:: Wheel Controls :header: "Item", "Value" :widths: 30, 30 *encryption*, forward only *ring*, none *notches*, none :param name: name of the Umkehrwalze :param wiring: wiring of the Umkehrwalze, positions related to the *alphabet* :param alphabet: unencoded alphabet to be used :param notify: notification function (e.g. print) """ def __init__(self, name : str = '', wiring : Union[str, Dict[str, str]] = '', alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> None: self.notify = notify self._nextComponent : Optional[Component] = None self._prevComponent : Optional[Component] = None assert name, '{}: At least 1 character in name required'.format(self.__class__.__name__) self._name = name assert alphabet, '{}/{}: At least 1 character in alphabet required'.format(self.__class__.__name__, self._name) self._alphabet = alphabet self._numberOfPositions = len(self.alphabet) if isinstance(wiring, dict): wiringList = self.alphabet.split('') for src, tgt in wiring.items(): assert src in self.alphabet, '{}: Source character {} not in {}'.format(self.__class__.__name__, src, self.alphabet) srcID = self.alphabet.index(src) wiringList[srcID] = tgt wiring = ''.join(wiringList) self.wiring = wiring if self.__class__.__name__ == 'Umkehrwalze': for n, c in enumerate(self._alphabet): assert wiring[n] != c, '{}: Character {} reflected to itself'.format(self.__class__.__name__, c) def copy(self) -> Component: return copy.deepcopy(self) def nCC(self) -> int: ncc = 0 for n, c in enumerate(self._wiring): ncc += (self._alphabet[n] != c) return ncc @property def name(self) -> str: """ :getter: Returns the name of the componenrt :setter: None """ return self._name @property def alphabet(self) -> str: """ :getter: Returns the alphabet of the engine :setter: None """ return self._alphabet @property def numberOfPositions(self) -> int: """ :getter: Returns the number of positions, usually len(alphabet) :setter: None """ return self._numberOfPositions @property def wiring(self) -> str: """ :getter: Returns the wiring :setter: Sets wiring and reverse wiring """ return self._wiring @wiring.setter def wiring(self, newWiring : str) -> None: assert len(newWiring) == self._numberOfPositions,'{} {}: len(wiring) = {} != len(alphabet) = {}'.format(self.__class__.__name__, self._name, len(newWiring), self._numberOfPositions) _wiring = '' for n, c in enumerate(newWiring): assert c in self.alphabet, '{}/wiring: Character {} not in {}'.format(self.__class__.__name__, c, self.alphabet) assert c not in _wiring, '{}/wiring: Duplicate character {} in wiring'.format(self.__class__.__name__, c) _wiring += c self._wiring = _wiring if '_rwiring' in vars(self): rwiringList = [' '] * self._numberOfPositions for n, c in enumerate(self._wiring): nr = self.alphabet.index(c) rwiringList[nr] = self.alphabet[n] self._rwiring = ''.join(rwiringList) @property def nextComponent(self) -> str: """ :getter: Returns the next component in the chain :setter: Sets wiring and reverse wiring, if any """ return self._nextComponent @nextComponent.setter def nextComponent(self, component : Component) -> None: self._nextComponent = component @property def prevComponent(self) -> str: """ :getter: Returns the previous component in the chain :setter: Sets wiring and reverse wiring """ return self._prevComponent @prevComponent.setter def prevComponent(self, component : Component) -> None: if self.__class__.__name__ == 'Umkehrwalze': raise TypeError('{} is unidirectional'.format(self.__class__.__name__)) self._prevComponent = component
[docs] def encode(self, c : str, forward : bool, componentOnly : bool = False) -> str: """Runs forward or backward through a component :param c: single character :param forward: direction :param componentOnly: do not go to next or previous component :returns: encoded character (mapped back to the alphabet) """ assert len(c) == 1, '{}.encode({})/{}: len({}) != 1'.format(self.__class__.__name__, forward, self.name, c) assert c in self._alphabet, '{}.encode({})/{}: Character {} out of range'.format(self.__class__.__name__, forward, self.name, c) if '_ringstellung' in vars(self): state = self._alphabet.index(self._ringstellung) else: state = 0 if forward: output = self._wiring[(self._alphabet.index(c) + state) % self._numberOfPositions] else: output = self._alphabet[(self._alphabet.index(self._rwiring[self._alphabet.index(c)]) - state) % self._numberOfPositions] if self.notify is not None: self.notify('{}.encode({})/{}: {} => {}'.format(self.__class__.__name__, forward, self.name, c, output)) if not componentOnly: if self._nextComponent and self.__class__ == Umkehrwalze: return self._nextComponent.encode(output, forward = False) elif self._nextComponent and forward: return self._nextComponent.encode(output, forward = True) elif self._prevComponent and not forward: return self._prevComponent.encode(output, forward = False) return output
[docs] def chain(self, forward : bool = True) -> List[Tuple[Component, bool]]: """Creates a chain of visited components :param forward: direction :param _chain: current chain list (do not use) :returns: (component, forward) list """ chainList = list() lastComponent = self while lastComponent is not None: chainList.append((lastComponent, forward or lastComponent.__class__ == Umkehrwalze)) if self.notify is not None: self.notify('{}.chain({})/{}'.format(self.__class__.__name__, forward, self.name)) if lastComponent.__class__ == Umkehrwalze: lastComponent = lastComponent.nextComponent forward = False elif forward: lastComponent = lastComponent.nextComponent else: lastComponent = lastComponent.prevComponent return chainList
[docs] def wiringToDict(self, connectedOnly : bool = False) -> Dict[str, str]: """Creates a dictionary starting from the wiring :param connectedOnly: include only characters not connected to itself :returns: dictionary of alphabet vs. wiring character """ wiringDict = dict() for nc, c in enumerate(self.alphabet): if c != self.wiring[nc] or not connectedOnly: wiringDict[c] = self.wiring[nc] return wiringDict
[docs] def dictToWiring(self, wiringDict : Dict[str, str]) -> None: """Creates a dictionary starting from the wiring :param wiringDict: dictionary of (at least connected) wirings """ self.wiring = list(self.alphabet) for src, tgt in wiringDict.items(): self.wiring[self.alphabet.index(src)] = tgt self.wiring = ''.join(self.wiring)
def __eq__(self, component : Component) -> bool: return self._name == component.name \ and self._alphabet == component.alphabet \ and self._wiring == component.wiring def __repr__(self) -> str: return 'class: {}\nname: {}\nalphabet: {}\nwiring: {}'.format( self.__class__.__name__, self._name, self._alphabet, self._wiring)
[docs]class Steckerbrett(Umkehrwalze): """Represents a Steckerbrett (plugboard) .. csv-table:: Wheel Controls :header: "Item", "Value" :widths: 30, 30 *encryption*, forward and backward *ring*, none *notches*, none :param name: name of the Steckerbrett :param wiring: wiring of the Steckerbrett, positions related to the *alphabet* :param alphabet: unencoded alphabet to be used :param notify: notification function (e.g. print) """ def __init__(self, name : str = '', wiring : Union[str, Dict[str, str]] = '', alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> None: self._rwiring = '' super(Steckerbrett, self).__init__(name, wiring, alphabet, notify) if self.__class__ == Steckerbrett: if name == 'Mark 1': for group, groupWiring in zip([alphabet[:len(alphabet)//2], alphabet[len(alphabet)//2:]], [wiring[:len(alphabet)//2], wiring[len(alphabet)//2:]]): for ec in groupWiring: assert ec in group, '{} {}: Character {} not in {}'.format(self.__class__.__name__, self._name, ec, group) elif name == 'Mark 3': for c, ec in zip(alphabet, wiring): ecIndex = alphabet.index(ec) assert c == ec or c == wiring[ecIndex], '{} {}: Invalid connection {} -> {}'.format(self.__class__.__name__, self._name, c, ec)
[docs] @staticmethod def Mark_1( replacement : bool = True, alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> Steckerbrett: """ Random Initialization of a Mark 1 Steckerbrett (`crytomuseumSB`_) The contacts are splitted into 2 separated groups (sockets 1-13 and sockets 14-26) .. _crytomuseumSB: https://www.cryptomuseum.com/crypto/enigma/i/sb.htm """ wiring = str() for group in [alphabet[:len(alphabet)//2], alphabet[len(alphabet)//2:]]: while True: actWiring = ''.join(random.sample(group, len(group))) hasReplacement = False if not replacement: for c, ec in zip(group, actWiring): if c == ec: hasReplacement = False if not hasReplacement: wiring += actWiring break return Steckerbrett('Mark 1', wiring, alphabet, notify)
[docs] @staticmethod def Mark_2( replacement : bool = True, alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> Steckerbrett: """ Random Initialization of a Mark 2 Steckerbrett (`crytomuseumSB`_) The 52 contacts are connected in unlimited fashion .. _crytomuseumSB: https://www.cryptomuseum.com/crypto/enigma/i/sb.htm """ wiring = str() while True: actWiring = ''.join(random.sample(alphabet, len(alphabet))) hasReplacement = False if not replacement: for c, ec in zip(alphabet, actWiring): if c == ec: hasReplacement = False if not hasReplacement: wiring += actWiring break return Steckerbrett('Mark 2', wiring, alphabet, notify)
[docs] @staticmethod def Mark_3( nConnections : int = 10, alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> Steckerbrett: """ Random Initialization of a Mark 3 Steckerbrett (`crytomuseumSB`_) The 26 contacts are cross-connected, i.e. MU is combined with UM .. _crytomuseumSB: https://www.cryptomuseum.com/crypto/enigma/i/sb.htm """ assert nConnections >= 0 and nConnections <= len(alphabet) // 2, '{}: 0 < nConnections = {} < {} required'.format(Steckerbrett.__class__.__name__, nConnections, len(alphabet) // 2) sources = ''.join(random.sample(alphabet, nConnections)) targetAlphabet = str() for c in alphabet: if c not in sources: targetAlphabet += c targets = ''.join(random.sample(targetAlphabet, nConnections)) wiring = list(alphabet) for src, tgt in zip(sources, targets): srcIndex = alphabet.index(src) tgtIndex = alphabet.index(tgt) wiring[srcIndex] = tgt wiring[tgtIndex] = src return Steckerbrett('Mark 3', ''.join(wiring), alphabet, notify)
[docs] def isMark1(self) -> bool: """ Checks, if the steckerbrett is of type Mark1 :returns: indicator """ halfAlphabet2 = self.alphabet[len(self.alphabet)//2:] for c in self.alphabet[:len(self.alphabet)//2]: if c not in halfAlphabet2: return False return True
[docs] def isMark3(self) -> bool: """ Checks, if the steckerbrett is of type Mark3 :returns: indicator """ return self.wiring == self.rwiring
[docs] def nConnections(self) -> int: """ Counts the number of connections of a steckerbrett """ nConnections2 = 0 for n, c in enumerate(self.alphabet): if self.wiring[n] != c: nConnections2 += 1 return nConnections2 // 2
@staticmethod def _replaceChar(actStr : str, index : str, newStr : str) -> str: return actStr[:index] + newStr + actStr[index+1:]
[docs] def clearMark3Setting(self, c : str) -> None: """ Clear one setting of a Steckerbrett (`crytomuseumSB`_) """ assert len(c) == 1, '{}.clearMark3Setting: {} is not a single character'.format(self.__class__.__name__, c) assert c in self.alphabet, '{}.clearMark3Setting: {} is not in alphabet'.format(self.__class__.__name__, c) srcIndex = self.alphabet.index(c) tgtIndex = self.alphabet.index(self.wiring[srcIndex]) if srcIndex == tgtIndex: return assert c == self.wiring[tgtIndex], '{}.clearMark3Setting: {} is not in Mark 3 steckerbrett'.format(self.__class__.__name__, self.name) wiring = self.wiring wiring = self._replaceChar(wiring, srcIndex, self.alphabet[srcIndex]) wiring = self._replaceChar(wiring, tgtIndex, self.alphabet[tgtIndex]) self.wiring = wiring
[docs] def addMark3Setting(self, src : str, tgt : str) -> None: """ Add one setting to a Steckerbrett (`crytomuseumSB`_) """ assert len(src) == 1 and len(tgt) == 1, '{}.addMark3Setting: Either src = {} and tgt = {} are not single characters'.format( self.__class__.__name__, src, tgt) assert src in self.alphabet and tgt in self.alphabet, '{}.addMark3Setting: Either src = {} and tgt = {} is not in alphabet'.format( self.__class__.__name__, src, tgt) if src == tgt: self.clearMark3Setting(src) srcIndex = self.alphabet.index(src) tgtIndex = self.alphabet.index(tgt) if src == self.wiring[tgtIndex] and tgt == self.wiring[srcIndex]: return assert src == self.wiring[srcIndex], '{}.addMark3Setting: src = {} is already connected'.format(self.__class__.__name__, src) assert tgt == self.wiring[tgtIndex], '{}.addMark3Setting: tgt = {} is already connected'.format(self.__class__.__name__, tgt) wiring = self.wiring wiring = self._replaceChar(wiring, srcIndex, tgt) wiring = self._replaceChar(wiring, tgtIndex, src) self.wiring = wiring
[docs] def exchangeMark3Setting(self, c1 : str, c2 : str) -> None: """ Exchanges 2 settings of a Steckerbrett (`crytomuseumSB`_) """ assert len(c1) == 1 and len(c2) == 1, '{}.exchangeMark3Setting: Either src = {} and tgt = {} are not single characters'.format( self.__class__.__name__, c1, c2) assert c1 in self.alphabet and c2 in self.alphabet, '{}.exchangeMark3Setting: Either src = {} and tgt = {} is not in alphabet'.format( self.__class__.__name__, c1, c2) if c1 == c2: return c1Index = self.wiring.index(c1) assert c1 != self.alphabet[c1Index], '{}.exchangeMark3Setting: c1 = {} is unconnected'.format(self.__class__.__name__, c1) c2Index = self.wiring.index(c2) assert c2 != self.alphabet[c2Index], '{}.exchangeMark3Setting: c2 = {} is unconnected'.format(self.__class__.__name__, c2) wiring = self.wiring wiring = self._replaceChar(wiring, c1Index, c2) wiring = self._replaceChar(wiring, c2Index, c1) self.wiring = wiring
@property def rwiring(self) -> str: """ :getter: Returns the reverse wiring """ return self._rwiring def __repr__(self) -> str: return super(Steckerbrett, self).__repr__() + '\nreverse wiring: {}'.format(self._rwiring)
[docs]class Zusatzwalze(Steckerbrett) : """Represents an Zusatzwalze (ZW == extra rotor) .. csv-table:: Wheel Controls :header: "Item", "Value" :widths: 30, 30 *encryption*, forward and backward *ring*, yes *notches*, none :param name: name of the Zusatzwalze :param wiring: wiring of the Zusatzwalze, positions related to the *alphabet* :param alphabet: unencoded alphabet to be used :param notify: notification function (e.g. print) """ def __init__(self, name : str = '', wiring : Union[str, Dict[str, str]] = '', ringstellung : Optional[str] = None, alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> None: """ Initialization """ super(Zusatzwalze, self).__init__(name, wiring, alphabet, notify) self._prevComponent : Optional[Component] = None if ringstellung is None: self._ringstellung = self._alphabet[0] else: assert len(ringstellung) == 1 assert ringstellung in self._alphabet self._ringstellung = ringstellung @property def ringstellung(self) -> str: """ :getter: Returns the ringstellung :setter: Sets the ringstellung """ return self._ringstellung @ringstellung.setter def ringstellung(self, ringstellung : str) -> None: assert len(ringstellung) == 1, '{}.ringstellung {}: 1 character expected, {} got'.format(self.__class__.__name__, self._name, ringstellung) assert ringstellung in self._alphabet self._ringstellung = ringstellung def __eq__(self, component : Component) -> bool: return super(Zusatzwalze, self).__eq__(component) and self._ringstellung == component._ringstellung def __repr__(self) -> str: return super(Zusatzwalze, self).__repr__() + '\nringstellung: {}'.format(self._ringstellung)
[docs]class Walze(Zusatzwalze): """Represents a Walze (rotor) .. csv-table:: Wheel Controls :header: "Item", "Value" :widths: 30, 30 *encryption*, forward and backward *ring*, yes *notches*, yes :param name: name of the walze :param wiring: wiring of the walze, positions related to the *alphabet* :param ringstellung: visible character at the beginning, position related to the *alphabet* :param notches: visible characters for the notches, positions related to the *alphabet* :param alphabet: unencoded alphabet to be used :param notify: notification function (e.g. print) """ def __init__(self, name : str = '', wiring : Union[str, Dict[str, str]] = '', ringstellung : Optional[str] = None, notches : str = '', alphabet : str = stdAlphabet, notify : Optional[Callable[[str], None]] = None) -> None: """ Initialization """ super(Walze, self).__init__(name, wiring, ringstellung, alphabet, notify) assert notches, '{} {}: At least 1 notch required'.format(self.__class__.__name__, self._name) self._notches = '' for notch in notches: assert notch in self._alphabet, '{} {}: Notch {} out of range'.format(self.__class__.__name__, self._name, notch) assert notch not in self._notches, '{} {}: Duplicate notch {}'.format(self.__class__.__name__, self._name, notch) self._notches += notch
[docs] def stepN(self, n : int)-> None: """ Steps the Walze n times """ assert n > 0, '{} {}: n == {} <= 0'.format(self.__class__.__name__, self._name, n) for _ in range(n): self.step()
[docs] def step(self)-> None: """ Steps the Walze and inititiates of the next Walze if available III II I <-- wheel order A D O A D P A D Q A E R <-- 1st step of middle wheel B F S <-- 2nd step of middle wheel B F T B F U """ notchFound = self._ringstellung in self._notches if notchFound: if self._nextComponent and self._nextComponent.__class__.__name__ == 'Walze': self._nextComponent.step() self._ringstellung = self._alphabet[(self._alphabet.index(self._ringstellung) + 1) % self._numberOfPositions] if self.notify: self.notify('{}.step {}: {}, notch = {}'.format(self.__class__.__name__, self.name, self._ringstellung, notchFound))
def __eq__(self, component : Component) -> bool: return super(Walze, self).__eq__(component) and self._notches == component._notches def __repr__(self) -> str: return super(Walze, self).__repr__() + '\nnotches: {}'.format(self._notches) @property def notches(self) -> str: """ :getter: Returns the notches :setter: None """ return self._notches