changeset 21:eeda81e07800

Added more procedure tests. Decided to remove group option from encrypt and z_sub option from decrypt since this procedure always groups and always performs Z substitution.
author Brian Neal <bgneal@gmail.com>
date Sun, 09 Jun 2013 14:18:24 -0500 (2013-06-09)
parents 8b9c6a6e7669
children 986876a37f4a
files m209/procedure.py m209/tests/test_procedure.py
diffstat 2 files changed, 103 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/m209/procedure.py	Sun Jun 09 13:11:23 2013 -0500
+++ b/m209/procedure.py	Sun Jun 09 14:18:24 2013 -0500
@@ -72,9 +72,14 @@
 
         if key_list:
             self.set_key_list(key_list)
+        else:
+            self.key_list = None
 
     def get_key_list(self):
-        """Returns the currently installed key list object."""
+        """Returns the currently installed key list object or None if one has
+        not been set.
+
+        """
         return self.key_list
 
     def set_key_list(self, key_list):
@@ -90,16 +95,13 @@
         self.m_209.set_drum_lugs(key_list.lugs)
         self.m_209.set_all_pins(key_list.pin_list)
 
-    def encrypt(self, plaintext, group=True, spaces=True, ext_msg_ind=None, sys_ind=None):
+    def encrypt(self, plaintext, spaces=True, ext_msg_ind=None, sys_ind=None):
         """Encrypts a plaintext message using standard procedure. The encrypted text
         with the required message indicators are returned as one string.
 
         The encrypt function accepts these parameters:
 
         plaintext - Input string of text to be encrypted
-        group - If True, the resulting encrypted text will be grouped into 5-letter
-            groups with a space between each group. If False, no spaces will be
-            present in the output.
         spaces - If True, space characters in the input plaintext will automatically
             be replaced with 'Z' characters before encrypting.
         ext_msg_ind - This is the external message indicator, which, if supplied,
@@ -124,8 +126,8 @@
             try:
                 self.m_209.set_key_wheels(ext_msg_ind)
             except M209Error as ex:
-                raise M209Error("invalid external message indicator {} - {}".format(
-                    ext_msg_ind, ex))
+                raise ProcedureError(
+                    "invalid external message indicator {} - {}".format(ext_msg_ind, ex))
         else:
             ext_msg_ind = self.m_209.set_random_key_wheels()
 
@@ -146,17 +148,17 @@
         self._set_int_message_indicator(int_msg_ind)
 
         # Now encipher the message on the M209
-        ciphertext = self.m_209.encrypt(plaintext, group=group, spaces=spaces)
+        ciphertext = self.m_209.encrypt(plaintext, group=True, spaces=spaces)
 
-        # If we are grouping, and the final group in the ciphertext has less than
-        # 5 letters, pad with X's to make a complete group:
-        if group:
-            total_len = len(ciphertext)
-            num_groups = total_len // 5
-            num_spaces = num_groups - 1 if num_groups >= 2 else 0
-            x_count = 5 - (total_len - num_spaces) % 5
-            if 0 < x_count < 5:
-                ciphertext = ciphertext + 'X' * x_count
+        # If the final group in the ciphertext has less than 5 letters, pad with
+        # X's to make a complete group:
+
+        total_len = len(ciphertext)
+        num_groups = total_len // 5
+        num_spaces = num_groups - 1 if num_groups >= 2 else 0
+        x_count = 5 - (total_len - num_spaces) % 5
+        if 0 < x_count < 5:
+            ciphertext = ciphertext + 'X' * x_count
 
         # Add the message indicators to pad each end of the message
 
@@ -165,9 +167,8 @@
 
         msg_parts = [pad1, pad2, ciphertext, pad1, pad2]
 
-        # Assemble the final message; group if requested
-        sep = ' ' if group else ''
-        return sep.join(msg_parts)
+        # Assemble the final message
+        return ' '.join(msg_parts)
 
     def set_decrypt_message(self, msg):
         """Prepare to decrypt the supplied message.
@@ -210,14 +211,10 @@
 
         return self.decrypt_params
 
-    def decrypt(self, z_sub=True):
+    def decrypt(self):
         """Decrypt the message set in a previous set_decrypt_message() call. The
         resulting plaintext is returned as a string.
 
-        If z_sub is True, 'Z' characters in the output plaintext will be
-        replaced by space characters, just like an actual M-209. If z_sub is
-        False, no such substitution will occur.
-
         A ProcedureError will be raised if the procedure instance has not been
         configured with the required key list.
 
@@ -246,7 +243,7 @@
 
         self.m_209.letter_counter = 0
         plaintext = self.m_209.decrypt(self.decrypt_params.ciphertext,
-                                       spaces=True, z_sub=z_sub)
+                                       spaces=True, z_sub=True)
         return plaintext
 
     def _set_int_message_indicator(self, indicator):
--- a/m209/tests/test_procedure.py	Sun Jun 09 13:11:23 2013 -0500
+++ b/m209/tests/test_procedure.py	Sun Jun 09 14:18:24 2013 -0500
@@ -7,7 +7,7 @@
 import unittest
 
 from ..keylist import KeyList
-from ..procedure import StdProcedure
+from ..procedure import StdProcedure, ProcedureError
 
 
 PLAINTEXT = 'ATTACK AT DAWN'
@@ -44,3 +44,82 @@
 
         plaintext = self.proc.decrypt()
         self.assertEqual(plaintext[:len(PLAINTEXT)], PLAINTEXT)
+
+    def test_blair_decrypt(self):
+
+        ciphertext = (
+            'DDGPD UCOFM JSCPS XZTGR HHWJG '
+            'BDKKK SHISC IMDFK RLUVH TWGAW '
+            'SUYMM VZBQP OEBJE KPMBW GPGNI '
+            'OFGAL VRYJC LSPLJ GRFYE UQVZT '
+            'PSNDT OAPYG SKGKM CKQTD JCPBE '
+            'NHYRX DDGPD UCOFM')
+        plaintext = ('MISSION ACCOMPLISHED X ALL ENEMY FORCES NEUTRALI ED X'
+                     '  ERO CASUALTIES X EIGHT PRISONERS TAKEN X'
+                     ' AWAITING FURTHER ORDERSO')
+
+        result = self.proc.set_decrypt_message(ciphertext)
+
+        self.assertEqual(result.sys_ind, 'D')
+        self.assertEqual(result.ext_msg_ind, 'GPDUCO')
+        self.assertEqual(result.key_list_ind, 'FM')
+        self.assertEqual(result.ciphertext, ciphertext[12:-12])
+
+        result = self.proc.decrypt()
+        self.assertEqual(result, plaintext)
+
+
+class ProcedureErrorsTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.fm = KeyList(indicator="FM",
+                    lugs='1-0 2-0*8 0-3*7 0-4*5 0-5*2 1-5 1-6 3-4 4-5',
+                    pin_list=[
+                        'BCEJOPSTUVXY',
+                        'ACDHJLMNOQRUYZ',
+                        'AEHJLOQRUV',
+                        'DFGILMNPQS',
+                        'CEHIJLNPS',
+                        'ACDFHIMN'
+                    ],
+                    letter_check='TNMYS CRMKK UHLKW LDQHM RQOLW R')
+
+    def test_encrypt_no_key_list(self):
+        proc = StdProcedure()
+        self.assertRaises(ProcedureError, proc.encrypt, 'TEST')
+
+    def test_encrypt_bad_ext_msg_ind(self):
+        proc = StdProcedure(key_list=self.fm)
+        self.assertRaises(ProcedureError, proc.encrypt, 'TEST', ext_msg_ind='WWWWWW')
+
+    def test_encrypt_bad_sys_ind(self):
+        proc = StdProcedure(key_list=self.fm)
+        self.assertRaises(ProcedureError, proc.encrypt, 'TEST', sys_ind='!')
+
+    def test_decrypt_invalid_msg(self):
+        proc = StdProcedure(key_list=self.fm)
+        self.assertRaisesRegex(ProcedureError, 'message format',
+                proc.set_decrypt_message, 'TEST')
+
+    def test_decrypt_invalid_sys_ind(self):
+        proc = StdProcedure(key_list=self.fm)
+
+        ciphertext = 'GZABC DEFFM NQHNL CAARZ OLTVX GZABC DEFFM'
+        self.assertRaisesRegex(ProcedureError, 'system indicator',
+                proc.set_decrypt_message, ciphertext)
+
+    def test_decrypt_no_params(self):
+        proc = StdProcedure(key_list=self.fm)
+        self.assertRaises(ProcedureError, proc.decrypt)
+
+    def test_decrypt_no_key_list(self):
+        proc = StdProcedure()
+        proc.set_decrypt_message(CIPHERTEXT)
+        self.assertRaisesRegex(ProcedureError, "key list 'FM' required", proc.decrypt)
+
+    def test_decrypt_wrong_key_list(self):
+        proc = StdProcedure(self.fm)
+        ciphertext = 'GGABC DEFYL NQHNL CAARZ OLTVX GGABC DEFYL'
+        proc.set_decrypt_message(ciphertext)
+        self.assertRaisesRegex(ProcedureError, "key list 'YL' required", proc.decrypt)
+