changeset 3:f4e25e6b76c3

Created plugboard class and tests.
author Brian Neal <bgneal@gmail.com>
date Sat, 23 Jun 2012 23:28:17 -0500
parents 713fa2a9ea9a
children 2792ca4ffa84
files enigma/SConscript enigma/plugboard.cpp enigma/plugboard.h enigma/tests/SConscript enigma/tests/test_plugboard.t.h enigma/tests/test_rotor.t.h
diffstat 6 files changed, 540 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/enigma/SConscript	Fri Jun 22 22:02:55 2012 -0500
+++ b/enigma/SConscript	Sat Jun 23 23:28:17 2012 -0500
@@ -3,6 +3,7 @@
 sources = Split("""
    rotor.cpp
    rotor_factory.cpp
+   plugboard.cpp
    """)
 
 env.StaticLibrary('enigma', sources)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/enigma/plugboard.cpp	Sat Jun 23 23:28:17 2012 -0500
@@ -0,0 +1,201 @@
+// Copyright (C) 2012 by Brian Neal.
+// This file is part of Cpp-Enigma, the Enigma Machine simulation.
+// Cpp-Enigma is released under the MIT License (see License.txt).
+//
+// plugboard.cpp - This is the implementation file for the plugboard class.
+
+#include <algorithm>
+#include <set>
+#include <sstream>
+#include <utility>
+#include "plugboard.h"
+
+using namespace enigma;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace
+{
+   // Returns a wiring map with "straight-through" mapping, where every input
+   // pin 'i' is wired to the output pin 'i':
+
+   alpha_int_array straight_through_mapping()
+   {
+      alpha_int_array result;
+      for (alpha_int_array::size_type i = 0; i < result.size(); ++i)
+      {
+         result[i] = i;
+      }
+      return result;
+   }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+plugboard::plugboard()
+ : wiring_map(straight_through_mapping())
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+plugboard::plugboard(const pair_vector& pairs)
+ : wiring_map(straight_through_mapping())
+{
+   construct_wiring(pairs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+plugboard::plugboard(const std::string& settings)
+ : wiring_map(straight_through_mapping())
+{
+   if (settings.empty())
+   {
+      return;
+   }
+
+   pair_vector pairs;
+
+   // detect which syntax is being used
+   if (settings.find('/') == std::string::npos)
+   {
+      // Assume Heer (army) syntax
+
+      std::istringstream iss(settings);
+      std::string s;
+      while (iss >> s)
+      {
+         if (s.size() != 2)
+         {
+            throw plugboard_error("invalid settings string");
+         }
+         const int m = std::toupper(s[0]) - 'A';
+         const int n = std::toupper(s[1]) - 'A';
+
+         pairs.push_back(std::make_pair(m, n));
+      }
+   }
+   else
+   {
+      // Assume Kriegsmarine (navy) syntax
+
+      std::istringstream iss(settings);
+      std::string s;
+      while (iss >> s)
+      {
+         const std::size_t x = s.find('/');
+         if (x == std::string::npos || x == s.size() - 1)
+         {
+            throw plugboard_error("invalid settings string");
+         }
+
+         int m;
+         int n;
+         std::istringstream mss(s.substr(0, x));
+         std::istringstream nss(s.substr(x + 1));
+
+         if ((mss >> m) && (nss >> n))
+         {
+            pairs.push_back(std::make_pair(m - 1, n - 1));
+         }
+         else
+         {
+            throw plugboard_error("invalid settings string");
+         }
+      }
+   }
+
+   construct_wiring(pairs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+plugboard::pair_vector plugboard::get_pairs() const
+{
+   std::set<std::pair<int, int>> pair_set;
+   for (int i = 0; i < 26; ++i)
+   {
+      const int j = wiring_map[i];
+      if (i < j)
+      {
+         pair_set.insert(std::make_pair(i, j));
+      }
+   }
+
+   return pair_vector(pair_set.begin(), pair_set.end());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::string plugboard::army_str() const
+{
+   const auto pairs = get_pairs();
+
+   std::string s;
+
+   for (const auto p : pairs)
+   {
+      s += static_cast<char>(p.first + 'A');
+      s += static_cast<char>(p.second + 'A');
+      s += ' ';
+   }
+   s.erase(s.size() - 1);     // erase trailing space
+   return s;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::string plugboard::navy_str() const
+{
+   const auto pairs = get_pairs();
+
+   std::ostringstream os;
+   for (const auto p : pairs)
+   {
+      os << (p.first + 1) << '/' << (p.second + 1) << ' ';
+   }
+
+   std::string s(os.str());
+   s.erase(s.size() - 1);     // erase trailing space
+   return s;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void plugboard::construct_wiring(const pair_vector& pairs)
+{
+   if (pairs.size() > max_pairs)
+   {
+      throw plugboard_error("Too many pairs");
+   }
+
+   // range check the wiring & ensure a path appears at most once
+   // (the double braces were added because gcc 4.6.3 emits a warning without
+   // them with -std=c++0x -Wall -Wextra -pedantic)
+   alpha_int_array counts = {{ 0 }};
+   for (const auto& p : pairs)
+   {
+      if (p.first < 0 || p.second < 0 || p.first >= 26 || p.second >= 26)
+      {
+         throw plugboard_error("invalid wiring pair");
+      }
+      ++counts[p.first];
+      ++counts[p.second];
+   }
+
+   if (std::find_if(counts.begin(),
+                    counts.end(),
+                    [](int n) { return n > 1; }) != counts.end())
+   {
+      throw plugboard_error("duplicate connection");
+   }
+
+   // all checks pass if we made it this far; make the connections
+
+   for (auto& p : pairs)
+   {
+      wiring_map[p.first] = p.second;
+      wiring_map[p.second] = p.first;
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/enigma/plugboard.h	Sat Jun 23 23:28:17 2012 -0500
@@ -0,0 +1,191 @@
+#ifndef CPP_ENIGMA_PLUGBOARD_H
+#define CPP_ENIGMA_PLUGBOARD_H
+// Copyright (C) 2012 by Brian Neal.
+// This file is part of Cpp-Enigma, the Enigma Machine simulation.
+// Cpp-Enigma is released under the MIT License (see License.txt).
+//
+// plugboard.h - This file contains the plugboard class.
+
+#include <vector>
+#include <utility>
+#include <string>
+#include <cstddef>
+#include "enigma_types.h"
+
+namespace enigma
+{
+   class plugboard_error : public enigma_error
+   {
+   public:
+      explicit plugboard_error(const std::string& what_arg)
+       : enigma_error(what_arg)
+      {}
+   };
+
+   // The plugboard allows the operator to swap letters before and after the
+   // entry wheel. This is accomplished by connecting cables between pairs of
+   // plugs that are marked with letters (Heer & Luftwaffe models) or numbers
+   // (Kriegsmarine). Ten cables were issued with each machine; thus up to 10 of
+   // these swappings could be used as part of a machine setup.
+
+   // Each cable swaps both the input and output signals. Thus if A is connected
+   // to B, A crosses to B in the keyboard to entry wheel direction and also in
+   // the reverse entry wheel to lamp direction.
+
+   class plugboard
+   {
+   public:
+      const static std::size_t max_pairs = 10;
+
+      typedef std::vector<std::pair<int, int>> pair_vector;
+
+      // Construct a plugboard with no connections:
+      plugboard();
+
+      // Construct from a vector of integer pairs that describe the
+      // connections. Each integer must be between [0-25], and the
+      // vector can have no more than max_pairs pairs. Each plug should
+      // be present at most once. A plugboard_error will be thrown if
+      // the pair_vector is invalid.
+
+      explicit plugboard(const pair_vector& pairs);
+
+      // Configure the plugboard according to a settings string as you may
+      // find on a key sheet.
+      //
+      // Two syntaxes are supported, the Heer/Luftwaffe and Kriegsmarine styles:
+      //
+      // In the Heer syntax, the settings are given as a string of
+      // alphabetic pairs. For example: 'PO ML IU KJ NH YT GB VF RE DC'
+      //
+      // In the Kriegsmarine syntax, the settings are given as a string of number
+      // pairs, separated by a '/'. Note that the numbering uses 1-26, inclusive.
+      // For example: '18/26 17/4 21/6 3/16 19/14 22/7 8/1 12/25 5/9 10/15'
+      //
+      // To specify no plugboard connections, settings can be an empty string.
+      //
+      // A PlugboardError will be raised if the settings string is invalid, or if
+      // it contains more than max_pairs pairs. Each plug should be present at
+      // most once in the settings string.
+
+      explicit plugboard(const std::string& settings);
+
+      // Return the current settings as a vector of pairs:
+      pair_vector get_pairs() const;
+
+      // Return the current settings as a string in Heer (army) format:
+      std::string army_str() const;
+
+      // Return the current settings as a string in Kriegsmarine (navy) format:
+      std::string navy_str() const;
+
+      // Simulate a signal entering the plugboard on wire n, where n must be
+      // an integer between 0 and 25.
+      //
+      // Returns the wire number of the output signal (0-25).
+      //
+      // Note that since the plugboard always crosses pairs of wires, it doesn't
+      // matter what direction (keyboard -> entry wheel or vice versa) the signal
+      // is coming from.
+
+      int signal(int n) const
+      {
+         return wiring_map[n];
+      }
+
+      //
+      // Functions to support hill-climbing:
+      //
+
+      // Return the internal state of the wiring:
+      alpha_int_array get_wiring() const
+      {
+         return wiring_map;
+      }
+
+      // Sets the internal state of the wiring:
+      void set_wiring(const alpha_int_array& wiring)
+      {
+         wiring_map = wiring;
+      }
+
+      // Returns true if connection n has a cable attached to it.
+      // 0 <= n < 26
+      bool is_wired(int n) const
+      {
+         return wiring_map[n] != n;
+      }
+
+      // Returns true if connection n has no cable attached to it.
+      // 0 <= n < 26
+      bool is_free(int n) const
+      {
+         return wiring_map[n] == n;
+      }
+
+      // Removes cable from plug number n [0-25].
+      void disconnect(int n)
+      {
+         const int x = wiring_map[n];
+         wiring_map[x] = x;
+         wiring_map[n] = n;
+      }
+
+      // Connects plug x to plug y, removing any existing connection first.
+      // x & y must be in [0-25].
+      void connect(int x, int y)
+      {
+         // disconnect any existing connections
+         const int m = wiring_map[x];
+         const int n = wiring_map[y];
+         wiring_map[m] = m;
+         wiring_map[n] = n;
+
+         wiring_map[x] = y;
+         wiring_map[y] = x;
+      }
+
+      // Returns true if plug x is connected to plug y.
+      // x & y must be in [0-25].
+      bool is_connected(int x, int y)
+      {
+         return wiring_map[x] == y && wiring_map[y] == x;
+      }
+
+   private:
+      alpha_int_array wiring_map;
+
+      // common constructor code:
+      void construct_wiring(const pair_vector& pairs);
+   };
+
+
+   // This class can be used to save & restore the state of a plugboard
+   // in RAII style:
+
+   class plugboard_state_saver
+   {
+   public:
+      explicit plugboard_state_saver(plugboard& pb)
+       : pb(pb)
+      {
+         state = pb.get_wiring();
+      }
+
+      ~plugboard_state_saver()
+      {
+         pb.set_wiring(state);
+      }
+
+      // disable copying & assignment
+      plugboard_state_saver(const plugboard_state_saver&) = delete;
+      plugboard_state_saver& operator=(const plugboard_state_saver&) = delete;
+
+   private:
+      plugboard& pb;
+      alpha_int_array state;
+   };
+
+}
+
+#endif
--- a/enigma/tests/SConscript	Fri Jun 22 22:02:55 2012 -0500
+++ b/enigma/tests/SConscript	Sat Jun 23 23:28:17 2012 -0500
@@ -1,4 +1,8 @@
 Import('env')
 
-env.CxxTest('rotor_suite', 'test_rotor.t.h')
+env.CxxTest('test_suite', [
+      'test_rotor.t.h',
+      'test_plugboard.t.h',
+   ]
+)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/enigma/tests/test_plugboard.t.h	Sat Jun 23 23:28:17 2012 -0500
@@ -0,0 +1,141 @@
+// Copyright (C) 2012 by Brian Neal.
+// This file is part of Cpp-Enigma, the Enigma Machine simulation.
+// Cpp-Enigma is released under the MIT License (see License.txt).
+//
+// test_plugboard.t.h - Unit tests for the plugboard class.
+
+#include <cxxtest/TestSuite.h>
+#include "plugboard.h"
+
+using namespace enigma;
+
+
+class plugboard_test_suite : public CxxTest::TestSuite
+{
+public:
+
+   void test_bad_settings()
+   {
+      // too many
+      TS_ASSERT_THROWS(plugboard("AB CD EF GH IJ KL MN OP QR ST UV"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("18/26 17/4 21/6 3/16 19/14 22/7 8/1 12/25 5/9 10/15 2/20"),
+                       plugboard_error);
+
+      // duplicate
+      TS_ASSERT_THROWS(plugboard("AB CD EF GH IJ KL MN OF QR ST"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("AB CD EF GH IJ KL MN FP QR ST"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("18/26 17/4 21/6 3/16 19/14 22/3 8/1 12/25"), plugboard_error);
+
+      // invalid
+      TS_ASSERT_THROWS(plugboard("A2 CD EF GH IJ KL MN FP QR ST"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("AB CD EF *H IJ KL MN FP QR ST"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("ABCD EF GH IJKLMN OP"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("A-D EF GH OP"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("A"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("9"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("1*/26 17/4 21/6 3/16 19/14 22/3 8/1 12/25"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("18/26 17/4 2A/6 3/16 19/14 22/3 8/1 12/25"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("100/2"), plugboard_error);
+      TS_ASSERT_THROWS(plugboard("T/C"), plugboard_error);
+   }
+
+   void test_valid_settings()
+   {
+      TS_ASSERT_THROWS_NOTHING(plugboard{});
+      TS_ASSERT_THROWS_NOTHING(plugboard{""});
+      TS_ASSERT_THROWS_NOTHING(plugboard(plugboard::pair_vector{}));
+      TS_ASSERT_THROWS_NOTHING(plugboard{"AB CD EF GH IJ KL MN OP QR ST"});
+      TS_ASSERT_THROWS_NOTHING(plugboard{"CD EF GH IJ KL MN OP QR ST"});
+      TS_ASSERT_THROWS_NOTHING(plugboard{"EF GH IJ KL MN OP QR ST"});
+      TS_ASSERT_THROWS_NOTHING(plugboard{" GH "});
+      TS_ASSERT_THROWS_NOTHING(plugboard{"18/26 17/4 21/6 3/16 19/14 22/7 8/1 12/25 5/9 10/15"});
+      TS_ASSERT_THROWS_NOTHING(plugboard{"18/26 17/4"});
+      TS_ASSERT_THROWS_NOTHING(plugboard{" 18/26 "});
+   }
+
+   void test_default_wiring()
+   {
+      plugboard p;
+      for (int i = 0; i < 26; ++i)
+      {
+         TS_ASSERT_EQUALS(i, p.signal(i));
+      }
+   }
+
+   void test_wiring()
+   {
+      std::vector<std::string> settings{"AB CD EF GH IJ KL MN OP QR ST",
+                  "1/2 3/4 5/6 7/8 9/10 11/12 13/14 15/16 17/18 19/20"};
+
+      for (const auto& setting : settings)
+      {
+         plugboard p{setting};
+         for (int n = 0; n < 26; ++n)
+         {
+            if (n < 20)
+            {
+               if (n % 2 == 0)
+               {
+                  TS_ASSERT_EQUALS(p.signal(n), n + 1);
+               }
+               else
+               {
+                  TS_ASSERT_EQUALS(p.signal(n), n - 1);
+               }
+            }
+            else
+            {
+               TS_ASSERT_EQUALS(p.signal(n), n);
+            }
+         }
+      }
+   }
+
+   void test_wiring2()
+   {
+      std::string stecker{"AV BS CG DL FU HZ IN KM OW RX"};
+      plugboard p{stecker};
+      plugboard::pair_vector pairs{p.get_pairs()};
+
+      plugboard::pair_vector expected_pairs{
+         {0, 21}, {1, 18}, {2, 6}, {3, 11}, {5, 20},
+         {7, 25}, {8, 13}, {10, 12}, {14, 22}, {17, 23}
+      };
+
+      TS_ASSERT_EQUALS(expected_pairs, pairs);
+
+      std::map<int, int> wiring;
+      for (const auto& p : pairs)
+      {
+         wiring.insert(p);
+         wiring.insert(std::make_pair(p.second, p.first));
+      }
+
+      for (int n = 0; n < 26; ++n)
+      {
+         auto iter = wiring.find(n);
+         if (iter != wiring.end())
+         {
+            TS_ASSERT_EQUALS(p.signal(n), iter->second);
+         }
+         else
+         {
+            TS_ASSERT_EQUALS(p.signal(n), n);
+         }
+      }
+   }
+
+   void test_army_str()
+   {
+      std::string stecker{"AB CD EF GH IJ KL MN OP QR ST"};
+      plugboard p{stecker};
+      TS_ASSERT_EQUALS(stecker, p.army_str());
+   }
+
+   void test_navy_str()
+   {
+      std::string stecker{"1/2 3/4 5/6 7/8 9/10 11/12 13/14 15/16 17/18 19/20"};
+      plugboard p{stecker};
+      TS_ASSERT_EQUALS(stecker, p.navy_str());
+   }
+};
--- a/enigma/tests/test_rotor.t.h	Fri Jun 22 22:02:55 2012 -0500
+++ b/enigma/tests/test_rotor.t.h	Sat Jun 23 23:28:17 2012 -0500
@@ -18,7 +18,7 @@
 const char* const wiring = "EKMFLGDQVZNTOWYHXUSPAIBRCJ";
 
 
-class MyTestSuite1 : public CxxTest::TestSuite
+class rotor_test_suite : public CxxTest::TestSuite
 {
 public: