Mercurial > public > cpp-enigma
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: