enigma_machine 0.0.1.alpha1 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # EnigmaMachine
2
+
3
+ This is a simulator for an [Enigma Machine](http://en.wikipedia.org/wiki/Enigma_machine). It currently simulates both the Enigma I/M3 and the Enigma M4. It also allows for specifying your own custom rotor/reflector configurations.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'enigma_machine'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install enigma_machine
18
+
19
+ ## Usage
20
+
21
+ e = EnigmaMachine.new(
22
+ :reflector => :B,
23
+ :rotors => [[:i, 10], [:ii, 14], [:iii, 21]],
24
+ :plug_pairs => %w(AP BR CM FZ GJ IL NT OV QS WX)
25
+ )
26
+ e.set_rotors('V', 'Q', 'Q')
27
+
28
+ result = e.translate('HABHV HLYDF NADZY')
29
+
30
+ See `integration_spec.rb` for some more examples
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
@@ -1,16 +1,16 @@
1
1
  class EnigmaMachine
2
2
  class Rotor
3
3
  STANDARD_ROTORS = {
4
- :i => "EKMFLGDQVZNTOWYHXUSPAIBRCJ_A",
5
- :ii => "AJDKSIRUXBLHWTMCQGZNPYFVOE_A",
6
- :iii => "BDFHJLCPRTXVZNYEIWGAKMUSQO_A",
7
- :iv => "ESOVPZJAYQUIRHXLNFTGKDCMWB_A",
8
- :v => "VZBRGITYUPSDNHLXAWMJQOFECK_A",
9
- :vi => "JPGVOUMFYQBENHZRDKASXLICTW_A",
10
- :vii => "NZJHGRCXMYSWBOUFAIVLPEKQDT_A",
11
- :viii => "FKQHTLXOCBJSPDZRAMEWNIUYGV_A",
12
- :beta => "LEYJVCNIXWPBQMDRTAKZGFUHOS_A",
13
- :gamma => "FSOKANUERHMBTIYCWLQPZXVGJD_A",
4
+ :i => "EKMFLGDQVZNTOWYHXUSPAIBRCJ_Q",
5
+ :ii => "AJDKSIRUXBLHWTMCQGZNPYFVOE_E",
6
+ :iii => "BDFHJLCPRTXVZNYEIWGAKMUSQO_V",
7
+ :iv => "ESOVPZJAYQUIRHXLNFTGKDCMWB_J",
8
+ :v => "VZBRGITYUPSDNHLXAWMJQOFECK_Z",
9
+ :vi => "JPGVOUMFYQBENHZRDKASXLICTW_MZ",
10
+ :vii => "NZJHGRCXMYSWBOUFAIVLPEKQDT_MZ",
11
+ :viii => "FKQHTLXOCBJSPDZRAMEWNIUYGV_MZ",
12
+ :beta => "LEYJVCNIXWPBQMDRTAKZGFUHOS_", # The beta and gamma rotors caould only go
13
+ :gamma => "FSOKANUERHMBTIYCWLQPZXVGJD_", # in position 4, and hence have no notches.
14
14
  }
15
15
 
16
16
  def initialize(rotor_spec, ring_setting, decorated)
@@ -18,8 +18,9 @@ class EnigmaMachine
18
18
  raise ConfigurationError unless STANDARD_ROTORS.has_key?(rotor_spec)
19
19
  rotor_spec = STANDARD_ROTORS[rotor_spec]
20
20
  end
21
- mapping, step_points = rotor_spec.split('_', 2)
21
+ mapping, notch_positions = rotor_spec.split('_', 2)
22
22
  @mapping = mapping.each_char.map {|c| ALPHABET.index(c) }
23
+ @notch_positions = notch_positions.split('')
23
24
  @ring_offset = ring_setting - 1
24
25
  @decorated = decorated
25
26
  self.position = 'A'
@@ -36,6 +37,10 @@ class EnigmaMachine
36
37
  @position = (@position + 1).modulo(26)
37
38
  end
38
39
 
40
+ def at_notch?
41
+ @notch_positions.include?(self.position)
42
+ end
43
+
39
44
  def forward(letter)
40
45
  index = add_offset ALPHABET.index(letter)
41
46
  new_index = sub_offset @mapping[index]
@@ -1,3 +1,3 @@
1
1
  class EnigmaMachine
2
- VERSION = "0.0.1.alpha1"
2
+ VERSION = "0.0.1"
3
3
  end
@@ -28,8 +28,8 @@ class EnigmaMachine
28
28
  case letter
29
29
  when /[A-Z]/
30
30
  press_key(letter)
31
- when ' '
32
- ' '
31
+ when /[ -]/
32
+ letter
33
33
  end
34
34
  end.join
35
35
  end
@@ -37,7 +37,8 @@ class EnigmaMachine
37
37
  private
38
38
 
39
39
  def advance_rotors
40
- # Temporarily only advance right rotor
40
+ @rotors[-3].advance_position if @rotors[-2].at_notch?
41
+ @rotors[-2].advance_position if @rotors[-2].at_notch? or @rotors[-1].at_notch?
41
42
  @rotors[-1].advance_position
42
43
  end
43
44
  end
@@ -63,10 +63,11 @@ describe EnigmaMachine do
63
63
  @e.translate('ABC').should == 'BCD'
64
64
  end
65
65
 
66
- it "should pass through spaces unmodified" do
66
+ it "should pass through spaces and dashes unmodified" do
67
67
  @e.should_not_receive(:press_key).with(' ')
68
+ @e.should_not_receive(:press_key).with('-')
68
69
 
69
- @e.translate('ABC DEF').should == 'ZZZ ZZZ'
70
+ @e.translate('ABCDE F----').should == 'ZZZZZ Z----'
70
71
  end
71
72
 
72
73
  it "should upcase the input before passing to press_key" do
@@ -109,6 +110,83 @@ describe EnigmaMachine do
109
110
  end
110
111
 
111
112
  describe "advancing rotors" do
113
+ before :each do
114
+ @left_rotor = mock("Rotor", :at_notch? => false, :advance_position => nil)
115
+ @middle_rotor = mock("Rotor", :at_notch? => false, :advance_position => nil)
116
+ @right_rotor = mock("Rotor", :at_notch? => false, :advance_position => nil)
117
+ EnigmaMachine::Rotor.stub!(:new).with(:i, anything(), anything()).and_return(@left_rotor)
118
+ EnigmaMachine::Rotor.stub!(:new).with(:ii, anything(), anything()).and_return(@middle_rotor)
119
+ EnigmaMachine::Rotor.stub!(:new).with(:iii, anything(), anything()).and_return(@right_rotor)
120
+ EnigmaMachine::Reflector.stub!(:new)
121
+ EnigmaMachine::Plugboard.stub!(:new)
122
+
123
+ @e = EnigmaMachine.new(:rotors => [[:i,1], [:ii,2], [:iii,3]])
124
+ end
125
+
126
+ describe "right rotor" do
127
+ it "should be advanced every time" do
128
+ @right_rotor.should_receive(:advance_position)
129
+ @e.send(:advance_rotors)
130
+ end
131
+ end
132
+
133
+ describe "middle rotor" do
134
+ it "should be advanced if the right rotor is at a notch" do
135
+ @right_rotor.stub!(:at_notch?).and_return(true)
136
+ @middle_rotor.should_receive(:advance_position)
137
+
138
+ @e.send(:advance_rotors)
139
+ end
140
+
141
+ it "should be advanced if it is at a notch" do
142
+ @middle_rotor.stub!(:at_notch?).and_return(true)
143
+ @middle_rotor.should_receive(:advance_position)
112
144
 
145
+ @e.send(:advance_rotors)
146
+ end
147
+
148
+ it "should not be advanced otherwise" do
149
+ @middle_rotor.should_not_receive(:advance_position)
150
+
151
+ @e.send(:advance_rotors)
152
+ end
153
+ end
154
+
155
+ describe "left rotor" do
156
+ it "should be advanced if the middle rotor is at a notch" do
157
+ @middle_rotor.stub!(:at_notch?).and_return(true)
158
+ @left_rotor.should_receive(:advance_position)
159
+
160
+ @e.send(:advance_rotors)
161
+ end
162
+
163
+ it "should not be advanced if it is at a notch" do
164
+ @left_rotor.stub!(:at_notch?).and_return(true)
165
+ @left_rotor.should_not_receive(:advance_position)
166
+
167
+ @e.send(:advance_rotors)
168
+ end
169
+
170
+ it "should not be advanced otherwise" do
171
+ @left_rotor.should_not_receive(:advance_position)
172
+
173
+ @e.send(:advance_rotors)
174
+ end
175
+ end
176
+
177
+ describe "4th rotor (when present)" do
178
+ before :each do
179
+ @fourth_rotor = mock("Rotor", :at_notch? => false, :advance_position => nil)
180
+ @e.instance_variable_get('@rotors').unshift(@fourth_rotor)
181
+ end
182
+
183
+ it "should never be advanced" do
184
+ @fourth_rotor.stub!(:at_notch?).and_return(true)
185
+ @left_rotor.stub!(:at_notch?).and_return(true)
186
+ @fourth_rotor.should_not_receive(:advance_position)
187
+
188
+ @e.send(:advance_rotors)
189
+ end
190
+ end
113
191
  end
114
192
  end
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Integration tests" do
@@ -15,6 +16,46 @@ describe "Integration tests" do
15
16
  e.translate('AEFAE JXXBN XYJTY').should == 'CONGR ATULA TIONS'
16
17
  end
17
18
 
19
+ it "should translate a message with rotor turnover" do
20
+ e = EnigmaMachine.new(
21
+ :reflector => :B,
22
+ :rotors => [[:i, 1], [:ii, 1], [:iii, 1]]
23
+ )
24
+ e.set_rotors('A', 'B', 'R')
25
+
26
+ e.translate('MABEK GZXSG').should == 'TURNM IDDLE'
27
+ end
28
+
29
+ it "should translate a message with double stepping" do
30
+ e = EnigmaMachine.new(
31
+ :reflector => :B,
32
+ :rotors => [[:i, 1], [:ii, 1], [:iii, 1]]
33
+ )
34
+ e.set_rotors('A', 'D', 'S')
35
+
36
+ e.translate('RZFOG FYHPL').should == 'TURNS THREE'
37
+ end
38
+
39
+ it "should translate a message with ring settings" do
40
+ e = EnigmaMachine.new(
41
+ :reflector => :B,
42
+ :rotors => [[:i, 10], [:ii, 14], [:iii, 21]]
43
+ )
44
+ e.set_rotors('X', 'Y', 'Z')
45
+
46
+ e.translate('QKTPE BZIUK').should == 'GOODR ESULT'
47
+ end
48
+
49
+ it "should translate a message with a plugboard as well" do
50
+ e = EnigmaMachine.new(
51
+ :reflector => :B,
52
+ :rotors => [[:i, 10], [:ii, 14], [:iii, 21]],
53
+ :plug_pairs => %w(AP BR CM FZ GJ IL NT OV QS WX)
54
+ )
55
+ e.set_rotors('V', 'Q', 'Q')
56
+
57
+ e.translate('HABHV HLYDF NADZY').should == 'THATS ITWEL LDONE'
58
+ end
18
59
  end
19
60
 
20
61
  describe "real world sample messages" do
@@ -63,12 +104,28 @@ describe "Integration tests" do
63
104
 
64
105
  e.set_rotors('U', 'Z', 'V')
65
106
  result = e.translate('YKAE NZAP MSCH ZBFO CUVM RMDP YCOF HADZ IZME FXTH FLOL PZLF GGBO TGOX GRET DWTJ IQHL MXVJ WKZU ASTR')
66
- result.should == 'STEUE REJTA NAFJO RDJAN STAND ORTQU AAACC CVIER NEUNN EUNZW OFAHR TZWON ULSMX XSCHA RNHOR STHCO'
107
+ result.should == 'STEU EREJ TANA FJOR DJAN STAN DORT QUAA ACCC VIER NEUN NEUN ZWOF AHRT ZWON ULSM XXSC HARN HORS THCO'
67
108
 
68
109
  # German: Steuere Tanafjord an. Standort Quadrat AC4992, fahrt 20sm. Scharnhorst. [hco - padding?]
69
110
  # English: Heading for Tanafjord. Position is square AC4992, speed 20 knots. Scharnhorst.
70
111
  end
71
112
  end
72
113
 
114
+ describe "Enigma M4" do
115
+ specify "U-264 (Kapitänleutnant Hartwig Looks), 1942" do
116
+ e = EnigmaMachine.new(
117
+ :reflector => :Bthin,
118
+ :rotors => [[:beta, 1], [:ii, 1], [:iv, 1], [:i, 22]],
119
+ :plug_pairs => %w(AT BL DF GJ HM NW OP QY RZ VX)
120
+ )
121
+
122
+ e.set_rotors('V', 'J', 'N', 'A')
123
+ result = e.translate('NCZW VUSX PNYM INHZ XMQX SFWX WLKJ AHSH NMCO CCAK UQPM KCSM HKSE INJU SBLK IOSX CKUB HMLL XCSJ USRR DVKO HULX WCCB GVLI YXEO AHXR HKKF VDRE WEZL XOBA FGYU JQUK GRTV UKAM EURB VEKS UHHV OYHA BCJW MAKL FKLM YFVN RIZR VVRT KOFD ANJM OLBG FFLE OPRG TFLV RHOW OPBE KVWM UQFM PWPA RMFH AGKX IIBG')
124
+ result.should == 'VONV ONJL OOKS JHFF TTTE INSE INSD REIZ WOYY QNNS NEUN INHA LTXX BEIA NGRI FFUN TERW ASSE RGED RUEC KTYW ABOS XLET ZTER GEGN ERST ANDN ULAC HTDR EINU LUHR MARQ UANT ONJO TANE UNAC HTSE YHSD REIY ZWOZ WONU LGRA DYAC HTSM YSTO SSEN ACHX EKNS VIER MBFA ELLT YNNN NNNO OOVI ERYS ICHT EINS NULL'
125
+
126
+ # German: Von Von 'Looks' F T 1132/19 Inhalt: Bei Angriff unter Wasser gedrückt, Wasserbomben. Letzter Gegnerstandort 08:30 Uhr Marine Quadrat AJ9863, 220 Grad, 8sm, stosse nach. 14mb fällt, NNO 4, Sicht 10.
127
+ # English: From Looks, radio-telegram 1132/19 contents: Forced to submerge under attack, depth charges. Last enemy location 08:30 hours, sea square AJ9863, following 220 degrees, 8 knots. [Pressure] 14 millibars falling, [wind] north-north-east 4, visibility 10.
128
+ end
129
+ end
73
130
  end
74
131
  end
data/spec/rotor_spec.rb CHANGED
@@ -14,48 +14,70 @@ describe EnigmaMachine::Rotor do
14
14
  r = EnigmaMachine::Rotor.new(:i, 1, :next)
15
15
  r.forward('A').should == 'E'
16
16
  r.forward('Q').should == 'X'
17
+ r.position = 'Q'
18
+ r.at_notch?.should == true
17
19
  end
18
20
 
19
21
  it "should support rotor ii" do
20
22
  r = EnigmaMachine::Rotor.new(:ii, 1, :next)
21
23
  r.forward('A').should == 'A'
22
24
  r.forward('M').should == 'W'
25
+ r.position = 'E'
26
+ r.at_notch?.should == true
23
27
  end
24
28
 
25
29
  it "should support rotor iii" do
26
30
  r = EnigmaMachine::Rotor.new(:iii, 1, :next)
27
31
  r.forward('A').should == 'B'
28
32
  r.forward('Q').should == 'I'
33
+ r.position = 'V'
34
+ r.at_notch?.should == true
29
35
  end
30
36
 
31
37
  it "should support rotor iv" do
32
38
  r = EnigmaMachine::Rotor.new(:iv, 1, :next)
33
39
  r.forward('A').should == 'E'
34
40
  r.forward('Q').should == 'N'
41
+ r.position = 'J'
42
+ r.at_notch?.should == true
35
43
  end
36
44
 
37
45
  it "should support rotor v" do
38
46
  r = EnigmaMachine::Rotor.new(:v, 1, :next)
39
47
  r.forward('A').should == 'V'
40
48
  r.forward('Q').should == 'A'
49
+ r.position = 'Z'
50
+ r.at_notch?.should == true
41
51
  end
42
52
 
43
53
  it "should support rotor vi" do
44
54
  r = EnigmaMachine::Rotor.new(:vi, 1, :next)
45
55
  r.forward('A').should == 'J'
46
56
  r.forward('Q').should == 'D'
57
+ r.position = 'M'
58
+ r.at_notch?.should == true
59
+ r.position = 'Z'
60
+ r.at_notch?.should == true
47
61
  end
48
62
 
49
63
  it "should support rotor vii" do
50
64
  r = EnigmaMachine::Rotor.new(:vii, 1, :next)
51
65
  r.forward('A').should == 'N'
52
66
  r.forward('Q').should == 'A'
67
+ r.position = 'M'
68
+ r.at_notch?.should == true
69
+ r.position = 'Z'
70
+ r.at_notch?.should == true
53
71
  end
54
72
 
55
73
  it "should support rotor viii" do
56
74
  r = EnigmaMachine::Rotor.new(:viii, 1, :next)
57
75
  r.forward('A').should == 'F'
58
76
  r.forward('Q').should == 'A'
77
+ r.position = 'M'
78
+ r.at_notch?.should == true
79
+ r.position = 'Z'
80
+ r.at_notch?.should == true
59
81
  end
60
82
 
61
83
  it "should support rotor beta" do
@@ -71,9 +93,10 @@ describe EnigmaMachine::Rotor do
71
93
  end
72
94
  end
73
95
  end
96
+
74
97
  describe "setting and manipulating rotor positions" do
75
98
  before :each do
76
- @rotor = EnigmaMachine::Rotor.new("ABCD", 1, :foo)
99
+ @rotor = EnigmaMachine::Rotor.new(:i, 1, :foo)
77
100
  end
78
101
 
79
102
  it "should set the position to 'A' by default" do
@@ -99,6 +122,53 @@ describe EnigmaMachine::Rotor do
99
122
  end
100
123
  end
101
124
 
125
+ describe "checking notch position" do
126
+ it "should return true if the rotor is at one of the notch positions" do
127
+ rotor = EnigmaMachine::Rotor.new('EKMFLGDQVZNTOWYHXUSPAIBRCJ_DR', 1, :foo)
128
+
129
+ rotor.position = 'D'
130
+ rotor.at_notch?.should == true
131
+
132
+ rotor.position = 'R'
133
+ rotor.at_notch?.should == true
134
+ end
135
+
136
+ it "should return false otherwise" do
137
+ rotor = EnigmaMachine::Rotor.new('EKMFLGDQVZNTOWYHXUSPAIBRCJ_DR', 1, :foo)
138
+
139
+ rotor.position = 'C'
140
+ rotor.at_notch?.should == false
141
+ rotor.position = 'E'
142
+ rotor.at_notch?.should == false
143
+
144
+ rotor.position = 'Q'
145
+ rotor.at_notch?.should == false
146
+ rotor.position = 'S'
147
+ rotor.at_notch?.should == false
148
+
149
+ rotor.position = 'A'
150
+ rotor.at_notch?.should == false
151
+ end
152
+
153
+ it "should be unaffected by the ring position" do
154
+ rotor = EnigmaMachine::Rotor.new('EKMFLGDQVZNTOWYHXUSPAIBRCJ_DR', 6, :foo)
155
+
156
+ rotor.position = 'C'
157
+ rotor.at_notch?.should == false
158
+ rotor.position = 'D'
159
+ rotor.at_notch?.should == true
160
+ rotor.position = 'E'
161
+ rotor.at_notch?.should == false
162
+
163
+ rotor.position = 'Q'
164
+ rotor.at_notch?.should == false
165
+ rotor.position = 'R'
166
+ rotor.at_notch?.should == true
167
+ rotor.position = 'S'
168
+ rotor.at_notch?.should == false
169
+ end
170
+ end
171
+
102
172
  describe "forward and reverse translation" do
103
173
  context "with a ring-setting of 1 (no adjustment), and a rotor position of A (the default)" do
104
174
  before :each do
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enigma_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha1
5
- prerelease: 6
4
+ version: 0.0.1
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Alex Tomlins
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-21 00:00:00.000000000 Z
12
+ date: 2012-07-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &13890560 !ruby/object:Gem::Requirement
16
+ requirement: &18339180 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *13890560
24
+ version_requirements: *18339180
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &13890040 !ruby/object:Gem::Requirement
27
+ requirement: &18357640 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *13890040
35
+ version_requirements: *18357640
36
36
  description: Enigma machine simulator
37
37
  email:
38
38
  - alex@tomlins.org.uk
@@ -43,6 +43,7 @@ files:
43
43
  - .gitignore
44
44
  - .rspec
45
45
  - Gemfile
46
+ - README.md
46
47
  - Rakefile
47
48
  - enigma_machine.gemspec
48
49
  - lib/enigma_machine.rb
@@ -70,13 +71,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
71
  version: '0'
71
72
  segments:
72
73
  - 0
73
- hash: -1944969372318025887
74
+ hash: 2207890655996414444
74
75
  required_rubygems_version: !ruby/object:Gem::Requirement
75
76
  none: false
76
77
  requirements:
77
- - - ! '>'
78
+ - - ! '>='
78
79
  - !ruby/object:Gem::Version
79
- version: 1.3.1
80
+ version: '0'
81
+ segments:
82
+ - 0
83
+ hash: 2207890655996414444
80
84
  requirements: []
81
85
  rubyforge_project:
82
86
  rubygems_version: 1.8.10