# HG changeset patch # User Brian Neal # Date 1386038666 21600 # Node ID eea5734a16552165140319142f43a33b46d99c47 # Parent 2e3d919383ddb7b8d299259f133cd86cefeabdfb Build encrypt wiring table inside switches. Add tests. Separate switch stepping into a new method in preparation for adding encrypt. diff -r 2e3d919383dd -r eea5734a1655 purple/machine.py --- a/purple/machine.py Sun Dec 01 20:30:06 2013 -0600 +++ b/purple/machine.py Mon Dec 02 20:44:26 2013 -0600 @@ -191,62 +191,68 @@ n = self.plugboard[c] if n < 6: # This input goes to the sixes switch - x = self.sixes[n] + x = self.sixes.decrypt(n) else: # This input goes to the chain of twenties switches n -= 6 - x = self.twenties[0][self.twenties[1][self.twenties[2][n]]] + x = self.twenties[0].decrypt(self.twenties[1].decrypt( + self.twenties[2].decrypt(n))) x += 6 plaintext.append(self.alphabet[x]) - # Now step the switches. First read the sixes and middle switch - # positions before stepping anything. Use these latched values in - # the decision processes for stepping a twenties. This is crucial! - sixes_pos = self.sixes.pos - middle_pos = self.middle_switch.pos - - # Now we can step the sixes. It unconditionally steps after every - # letter is processed. - self.sixes.step() - - # Only 1 twenties switch steps at a time. - # - # Normally the fast switch advances every letter. - # - # However if the sixes is at the last position (24), the middle - # switch steps instead. - # - # But if the sixes is at the last position (24) and the middle - # switch is at the last position (24), the slow switch will step. - # The middle switch will step on the next letter in this case. - # - # Here we reproduce figure 10 from the reference paper with offsets - # adjusted for 0-based indexing to illustrate the movement. In this - # example the fast is twenties #1, middle is #2, and slow is #3. - # - # Sixes Twenties #1 Twenties #2 Twenties #3 - # ----------------------------------------------------- - # 20 0 24 4 - # 21 1 24 4 - # 22 2 24 4 - # 23 3 24 4 - # * 24 3 24 5 - # # 0 3 0 5 - # 1 4 0 5 - # - # Here we see the fast switch stepping. However at (*) both the - # sixes and middle switch have reached their last positions, so the - # slow switch steps. At the next letter (#) the middle switch plays - # "catch up" and advances while the fast switch holds steady. - - if sixes_pos == 24 and middle_pos == 24: - self.slow_switch.step() - self.middle_switch_catchup = True - elif sixes_pos == 24 or self.middle_switch_catchup: - self.middle_switch.step() - self.middle_switch_catchup = False - else: - self.fast_switch.step() + # Now step the switches. + self.step() return ''.join(plaintext) + + def step(self): + """Step the stepping switches.""" + # First read the sixes and middle switch + # positions before stepping anything. Use these latched values in + # the decision processes for stepping a twenties. This is crucial! + sixes_pos = self.sixes.pos + middle_pos = self.middle_switch.pos + + # Now we can step the sixes. It unconditionally steps after every + # letter is processed. + self.sixes.step() + + # Only 1 twenties switch steps at a time. + # + # Normally the fast switch advances every letter. + # + # However if the sixes is at the last position (24), the middle + # switch steps instead. + # + # But if the sixes is at the last position (24) and the middle + # switch is at the last position (24), the slow switch will step. + # The middle switch will step on the next letter in this case. + # + # Here we reproduce figure 10 from the reference paper with offsets + # adjusted for 0-based indexing to illustrate the movement. In this + # example the fast is twenties #1, middle is #2, and slow is #3. + # + # Sixes Twenties #1 Twenties #2 Twenties #3 + # ----------------------------------------------------- + # 20 0 24 4 + # 21 1 24 4 + # 22 2 24 4 + # 23 3 24 4 + # * 24 3 24 5 + # # 0 3 0 5 + # 1 4 0 5 + # + # Here we see the fast switch stepping. However at (*) both the + # sixes and middle switch have reached their last positions, so the + # slow switch steps. At the next letter (#) the middle switch plays + # "catch up" and advances while the fast switch holds steady. + + if sixes_pos == 24 and middle_pos == 24: + self.slow_switch.step() + self.middle_switch_catchup = True + elif sixes_pos == 24 or self.middle_switch_catchup: + self.middle_switch.step() + self.middle_switch_catchup = False + else: + self.fast_switch.step() diff -r 2e3d919383dd -r eea5734a1655 purple/switch.py --- a/purple/switch.py Sun Dec 01 20:30:06 2013 -0600 +++ b/purple/switch.py Mon Dec 02 20:44:26 2013 -0600 @@ -27,16 +27,19 @@ 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. + 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.wiring = wiring + 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): @@ -57,13 +60,33 @@ self.pos = (self.pos + 1) % self.num_positions return self.pos - def __getitem__(self, level): + def decrypt(self, level): """This method is how to determine the output signal from the stepping - switch. The key parameter 'level' is the integer input level for the - incoming signal. The integer outgoing contact level is returned. + 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.wiring[self.pos][level] + 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): diff -r 2e3d919383dd -r eea5734a1655 purple/tests/test_switch.py --- a/purple/tests/test_switch.py Sun Dec 01 20:30:06 2013 -0600 +++ b/purple/tests/test_switch.py Mon Dec 02 20:44:26 2013 -0600 @@ -50,32 +50,59 @@ new_pos = switch.step() self.assertEqual((pos + 1) % 25, new_pos) - def test_output(self): + def test_decrypt(self): switch = create_switch(purple.switch.SIXES) switch.set_pos(0) - self.assertEqual(switch[0], 1) - self.assertEqual(switch[1], 0) - self.assertEqual(switch[2], 2) - self.assertEqual(switch[3], 4) - self.assertEqual(switch[4], 3) - self.assertEqual(switch[5], 5) + self.assertEqual(switch.decrypt(0), 1) + self.assertEqual(switch.decrypt(1), 0) + self.assertEqual(switch.decrypt(2), 2) + self.assertEqual(switch.decrypt(3), 4) + self.assertEqual(switch.decrypt(4), 3) + self.assertEqual(switch.decrypt(5), 5) switch.step() - self.assertEqual(switch[0], 5) - self.assertEqual(switch[1], 2) - self.assertEqual(switch[2], 4) - self.assertEqual(switch[3], 1) - self.assertEqual(switch[4], 0) - self.assertEqual(switch[5], 3) + self.assertEqual(switch.decrypt(0), 5) + self.assertEqual(switch.decrypt(1), 2) + self.assertEqual(switch.decrypt(2), 4) + self.assertEqual(switch.decrypt(3), 1) + self.assertEqual(switch.decrypt(4), 0) + self.assertEqual(switch.decrypt(5), 3) switch.set_pos(24) - self.assertEqual(switch[0], 4) - self.assertEqual(switch[1], 1) - self.assertEqual(switch[2], 3) - self.assertEqual(switch[3], 2) - self.assertEqual(switch[4], 5) - self.assertEqual(switch[5], 0) + self.assertEqual(switch.decrypt(0), 4) + self.assertEqual(switch.decrypt(1), 1) + self.assertEqual(switch.decrypt(2), 3) + self.assertEqual(switch.decrypt(3), 2) + self.assertEqual(switch.decrypt(4), 5) + self.assertEqual(switch.decrypt(5), 0) + + def test_encrypt(self): + + switch = create_switch(purple.switch.SIXES) + switch.set_pos(0) + self.assertEqual(switch.encrypt(0), 1) + self.assertEqual(switch.encrypt(1), 0) + self.assertEqual(switch.encrypt(2), 2) + self.assertEqual(switch.encrypt(3), 4) + self.assertEqual(switch.encrypt(4), 3) + self.assertEqual(switch.encrypt(5), 5) + + switch.step() + self.assertEqual(switch.encrypt(0), 4) + self.assertEqual(switch.encrypt(1), 3) + self.assertEqual(switch.encrypt(2), 1) + self.assertEqual(switch.encrypt(3), 5) + self.assertEqual(switch.encrypt(4), 2) + self.assertEqual(switch.encrypt(5), 0) + + switch.set_pos(24) + self.assertEqual(switch.encrypt(0), 5) + self.assertEqual(switch.encrypt(1), 1) + self.assertEqual(switch.encrypt(2), 3) + self.assertEqual(switch.encrypt(3), 2) + self.assertEqual(switch.encrypt(4), 0) + self.assertEqual(switch.encrypt(5), 4) def test_bad_create_switch(self):