Mercurial > public > m209
view m209/keylist/generate.py @ 26:425981f27876
More work on generating a key list. Added some tests.
TODO: need to account for not all wheels having all the letters.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Fri, 14 Jun 2013 21:46:42 -0500 |
parents | b6b52d63cd50 |
children | 941590d946c0 |
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 routines to generate key lists.""" import collections import random from .key_list import KeyList from ..converter import M209 from .. import M209Error from ..data import KEY_WHEEL_DATA # Maximum number of attempts to generate valid settings before giving up and # raising a M209Error: MAX_ATTEMPTS = 128 # Total number of pins on all 6 wheels: TOTAL_PINS = sum(len(letters) for letters, _ in KEY_WHEEL_DATA) def generate_key_list(indicator): """Create a key list at random with the given indicator. The procedure used is based upon manuals for the M-209 as found online: [1]: TM-11-380, War Department, Technical Manual, Converter M-209, April 27, 1942. http://maritime.org/tech/csp1500inst.htm [2]: TM-11-380, War Department, Technical Manual, Converter M-209, M-209-A, M-209-B (cipher) 17 March, 1944. http://www.ilord.com/m209manual.html Page 1 of reference [2] says: "This manual supercedes TM-11-380, 27 April 1942, and TM-11-380B, 20 September 1943." """ lugs = generate_lugs() pin_list = generate_pin_list() letter_check = generate_letter_check(lugs=lugs, pin_list=pin_list) return KeyList(indicator=indicator, lugs=lugs, pin_list=pin_list, letter_check=letter_check) def generate_lugs(): """Return random lug settings based on Army procedure.""" return '' def generate_pin_list(): """Return a random pin list based on Army procedure.""" cards = ['R'] * 78 cards.extend(['L'] * (156 - len(cards))) for n in range(MAX_ATTEMPTS): random.shuffle(cards) deck = collections.deque(cards) pin_list = [] for letters, _ in KEY_WHEEL_DATA: pins = [c for c in letters if 'R' == deck.pop()] pin_list.append(''.join(pins)) if pin_list_check(pin_list): break else: raise M209Error("generate_pin_list: too many attempts") return pin_list def generate_letter_check(lugs, pin_list): """Return a letter check string for the given pin list and lug settings.""" m_209 = M209(lugs, pin_list) m_209.set_key_wheels('A' * 6) return m_209.encrypt('A' * 26, group=True) def pin_list_check(pin_list): """Returns True if the supplied pin list meets Army procedure criteria. To pass the check, the number of effective pins must be between 40-60%. Furthermore, there cannot be more than 6 consecutive effective or non-effective pins on any wheel. """ num_eff = sum(len(s) for s in pin_list) ratio = num_eff / TOTAL_PINS if not (0.4 <= ratio <= 0.6): return False # Check for more than 6 consecutive effective pins on a wheel # TODO: not all letters are on every wheel for pins in pin_list: run = 0 for i in range(len(pins) - 1): if ord(pins[i]) + 1 == ord(pins[i + 1]): run = 2 if run == 0 else run + 1 else: run = 0 if run >= 7: return False # Check for more than 6 consecutive ineffective pins on a wheel # TODO: not all letters are on every wheel for pins in pin_list: x = 'A' for y in pins: if ord(y) - ord(x) >= 8: return False x = y return True