Mercurial > public > m209
view m209/converter.py @ 2:c292c6b5e7ae
First cut at the M209 class. This is a WIP. Encryption works.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Wed, 29 May 2013 21:32:12 -0500 |
parents | |
children | 328790e5bc5d |
line wrap: on
line source
# Copyright (C) 2013 by Brian Neal. # This file is part of m209, the M-209 simulation. # m209 is released under the MIT License (see LICENSE.txt). """This module contains the M209 class, which puts together all the parts to assemble a complete M-209 converter. """ import string from . import M209Error from .data import KEY_WHEEL_DATA from .key_wheel import KeyWheel from .drum import Drum ALLOWED_PLAINTEXT = set(string.ascii_uppercase) CIPHER = list(reversed(string.ascii_uppercase)) class M209: """The M209 class is the top-level class in the M-209 simulation. It aggregates key wheels and a drum and orchestrates their movements to provide encrypt and decrypt functions for the operator. """ def __init__(self): """Build a M209 instance with all pins in the ineffective state and all drum lugs in neutral positions. """ self.key_wheels = [KeyWheel(*args) for args in KEY_WHEEL_DATA] self.drum = Drum() self.letter_counter = 0 def set_pins(self, n, effective_pins): """Sets the pin settings on the key wheel specified by n, where n is between 0-5, inclusive. Key wheel 0 is the left-most wheel and wheel 5 is the right-most. effective_pins - must be an iterable of letters whose pins are slid to the "effective" position (to the right). Letters not appearing in this sequence are considered to be in the "ineffective" position (to the left). If None or empty, all pins are set to be ineffective. """ if not (0 <= n < len(self.key_wheels)): raise M209Error("set_pins(): invalid key wheel index {}".format(n)) self.key_wheels[n].set_pins(effective_pins) def set_drum_lugs(self, lug_list): """Sets the drum lugs according to the given lug_list parameter. If lug_list is None or empty, all lugs will be placed in neutral positions. Otherwise, the lug_list can either be a list or a string. If lug_list is passed a list, it must be a list of 1 or 2-tuple integers, where each integer is between 0-5, inclusive, and represents a 0-based key wheel position. The list can not be longer than 27 items. If lug_list is passed as a string, it is assumed to be in key list format. That is, it must consist of at most 27 whitespace separated pairs of integers separated by dashes. For example: '1-0 2-0 2-0 0-3 0-5 0-5 0-6 2-4 3-6' Each integer pair must be in the form 'm-n' where m & n are integers between 0 and 6, inclusive. Each integer represents a lug position where 0 is a neutral position, and 1-6 correspond to key wheel positions. If m & n are both non-zero, they cannot be equal. If a string or list has less than 27 items, it is assumed all remaining bars have both lugs in the neutral (0) positions. Order in a list or string doesn't matter. """ if isinstance(lug_list, str): drum = Drum.from_key_list(lug_list) else: drum = Drum(lug_list) self.drum = drum def encrypt(self, plaintext, group=True): ciphertext = [] for p in plaintext: if p not in ALLOWED_PLAINTEXT: raise M209Error("Illegal input to encrypt(): {}".format(p)) pins = [kw.is_effective() for kw in self.key_wheels] count = self.drum.rotate(pins) c = CIPHER[(ord(p) - ord('A') - count) % 26] ciphertext.append(c) for kw in self.key_wheels: kw.rotate() self.letter_count += 1 if group: s = ' '.join(''.join(ciphertext[i:i+5]) for i in range(0, len(ciphertext), 5)) else: s = ''.join(ciphertext) return s if __name__ == '__main__': m209 = M209() m209.set_drum_lugs('1-0 2-0 2-0 2-0 2-0 0-3 0-4 0-4 0-4 0-5 0-5 0-5 0-6 0-6 0-6 0-6 0-6 0-6 0-6 0-6 0-6 0-6 0-6 2-5 2-6 3-4 4-5') m209.set_pins(0, 'BFJKLOSTUWXZ') m209.set_pins(1, 'ABDJKLMORTUV') m209.set_pins(2, 'EHJKNPQRSX') m209.set_pins(3, 'ABCHIJLMPQR') m209.set_pins(4, 'BCDGJLNOPQS') m209.set_pins(5, 'AEFHIJP') pt = 'A' * 26 print(m209.encrypt(pt))