rtunnel 0.3.8 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG +13 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +48 -0
  4. data/README.markdown +84 -0
  5. data/Rakefile +45 -0
  6. data/bin/rtunnel_client +2 -1
  7. data/bin/rtunnel_server +2 -1
  8. data/lib/rtunnel/client.rb +308 -0
  9. data/lib/rtunnel/command_processor.rb +62 -0
  10. data/lib/rtunnel/command_protocol.rb +50 -0
  11. data/lib/rtunnel/commands.rb +233 -0
  12. data/lib/rtunnel/connection_id.rb +24 -0
  13. data/lib/rtunnel/core.rb +58 -0
  14. data/lib/rtunnel/crypto.rb +106 -0
  15. data/lib/rtunnel/frame_protocol.rb +34 -0
  16. data/lib/rtunnel/io_extensions.rb +54 -0
  17. data/lib/rtunnel/leak.rb +35 -0
  18. data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
  19. data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
  20. data/lib/rtunnel/server.rb +351 -0
  21. data/lib/rtunnel/socket_factory.rb +119 -0
  22. data/lib/rtunnel.rb +20 -0
  23. data/rtunnel.gemspec +51 -0
  24. data/spec/client_spec.rb +47 -0
  25. data/spec/cmds_spec.rb +127 -0
  26. data/spec/integration_spec.rb +105 -0
  27. data/spec/server_spec.rb +21 -0
  28. data/spec/spec_helper.rb +3 -0
  29. data/test/command_stubs.rb +77 -0
  30. data/test/protocol_mocks.rb +43 -0
  31. data/test/scenario_connection.rb +109 -0
  32. data/test/test_client.rb +48 -0
  33. data/test/test_command_protocol.rb +82 -0
  34. data/test/test_commands.rb +49 -0
  35. data/test/test_connection_id.rb +30 -0
  36. data/test/test_crypto.rb +127 -0
  37. data/test/test_frame_protocol.rb +109 -0
  38. data/test/test_io_extensions.rb +70 -0
  39. data/test/test_server.rb +70 -0
  40. data/test/test_socket_factory.rb +42 -0
  41. data/test/test_tunnel.rb +186 -0
  42. data/test_data/authorized_keys2 +4 -0
  43. data/test_data/known_hosts +4 -0
  44. data/test_data/random_rsa_key +27 -0
  45. data/test_data/ssh_host_dsa_key +12 -0
  46. data/test_data/ssh_host_rsa_key +27 -0
  47. data/tests/_ab_test.rb +16 -0
  48. data/tests/_stress_test.rb +96 -0
  49. data/tests/lo_http_server.rb +55 -0
  50. metadata +127 -31
  51. data/History.txt +0 -3
  52. data/Manifest.txt +0 -13
  53. data/README.txt +0 -362
  54. data/lib/client.rb +0 -185
  55. data/lib/cmds.rb +0 -166
  56. data/lib/core.rb +0 -53
  57. data/lib/rtunnel_client_cmd.rb +0 -25
  58. data/lib/rtunnel_server_cmd.rb +0 -20
  59. data/lib/server.rb +0 -181
  60. data/rtunnel_client.rb +0 -3
  61. data/rtunnel_server.rb +0 -3
data/spec/cmds_spec.rb ADDED
@@ -0,0 +1,127 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'cmds'
4
+
5
+ include RTunnel
6
+
7
+ describe RTunnel::CreateConnectionCommand do
8
+ it "should be able to serialize and parse itself" do
9
+ serialized = CreateConnectionCommand.new("id").to_s
10
+
11
+ cmd = Command.parse(serialized)
12
+ cmd.class.should == CreateConnectionCommand
13
+ cmd.conn_id.should == "id"
14
+ end
15
+
16
+ it "should be able to match itself and remove itself from the stream" do
17
+ serialized = CreateConnectionCommand.new("abcbdefg").to_s
18
+
19
+ Command.match(serialized).should == true
20
+ Command.parse(serialized)
21
+ serialized.should be_empty
22
+ end
23
+ end
24
+
25
+ describe RTunnel::SendDataCommand do
26
+ it "should be able to serialzied and parse itself" do
27
+ serialized = SendDataCommand.new("id", "here is some data").to_s
28
+
29
+ cmd = Command.parse(serialized)
30
+ cmd.class.should == SendDataCommand
31
+ cmd.conn_id.should == "id"
32
+ cmd.data.should == "here is some data"
33
+ end
34
+
35
+ it "should be able to match itself and remove itself from the stream" do
36
+ serialized = SendDataCommand.new("abcbdefg", "and here is some data").to_s
37
+
38
+ serialized << "WAY MORE CRAP DATA!!!"
39
+
40
+ Command.match(serialized).should == true
41
+ Command.parse(serialized)
42
+ serialized.should == "WAY MORE CRAP DATA!!!"
43
+ end
44
+ end
45
+
46
+ describe RTunnel::CloseConnectionCommand do
47
+ it "should be able to serialize and parse itself" do
48
+ serialized = CloseConnectionCommand.new("id").to_s
49
+
50
+ cmd = Command.parse(serialized)
51
+ cmd.class.should == CloseConnectionCommand
52
+ cmd.conn_id.should == "id"
53
+ end
54
+
55
+ it "should be able to match itself and remove itself from the stream" do
56
+ serialized = CloseConnectionCommand.new("abcbdefg").to_s
57
+
58
+ Command.match(serialized).should == true
59
+ Command.parse(serialized)
60
+ serialized.should be_empty
61
+ end
62
+ end
63
+
64
+ describe RTunnel::PingCommand do
65
+ it "should be able to serialize and parse itself" do
66
+ serialized = PingCommand.new.to_s
67
+
68
+ cmd = Command.parse(serialized)
69
+ cmd.class.should == PingCommand
70
+ end
71
+
72
+ it "should be able to match itself and remove itself from the stream" do
73
+ serialized = PingCommand.new.to_s
74
+
75
+ Command.match(serialized).should == true
76
+ Command.parse(serialized)
77
+ serialized.should be_empty
78
+ end
79
+ end
80
+
81
+
82
+ describe RTunnel::RemoteListenCommand do
83
+ it "should be able to serialize and parse itself" do
84
+ serialized = RemoteListenCommand.new("0.0.0.0:1234").to_s
85
+
86
+ cmd = Command.parse(serialized)
87
+ cmd.class.should == RemoteListenCommand
88
+ cmd.address.should == "0.0.0.0:1234"
89
+ end
90
+
91
+ it "should be able to match itself and remove itself from the stream" do
92
+ serialized = RemoteListenCommand.new("0.0.0.0:1234").to_s
93
+
94
+ Command.match(serialized).should == true
95
+ Command.parse(serialized)
96
+ serialized.should be_empty
97
+ end
98
+ end
99
+
100
+
101
+ describe RTunnel::Command do
102
+ it "should be able to match a command in a stream" do
103
+ Command.match("C1234abc|").should == true
104
+ Command.match("C1234abc").should == false
105
+ Command.match("C1234abc|C|").should == true
106
+
107
+ Command.match("Did|15|DATA").should == false
108
+ Command.match("Did|1|DATA").should == true
109
+ Command.match("Did|4|\0\0\0\0").should == true
110
+
111
+ data = "C1234abc|D1234abc|15|here is my dataX1234abc|L0.0.0.0:1234|CASDF"
112
+ cmd1 = Command.parse(data)
113
+ cmd2 = Command.parse(data)
114
+ cmd3 = Command.parse(data)
115
+ cmd4 = Command.parse(data)
116
+ cmd1.class.should == CreateConnectionCommand
117
+ cmd1.conn_id.should == "1234abc"
118
+ cmd2.class.should == SendDataCommand
119
+ cmd2.conn_id.should == "1234abc"
120
+ cmd3.class.should == CloseConnectionCommand
121
+ cmd3.conn_id.should == "1234abc"
122
+ cmd4.class.should == RemoteListenCommand
123
+ cmd4.address.should == "0.0.0.0:1234"
124
+
125
+ Command.match(data).should == false
126
+ end
127
+ end
@@ -0,0 +1,105 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'client'
4
+ require 'server'
5
+
6
+ describe "RTunnel" do
7
+ class TextServer < GServer
8
+ def initialize(port, text)
9
+ @text = text
10
+ super(port)
11
+ end
12
+
13
+ def serve(io)
14
+ io.write(@text)
15
+ end
16
+ end
17
+
18
+ def read_from_address(addr)
19
+ timeout(2) { TCPSocket.open(*addr.split(/:/)) {|s| s.read } }
20
+ end
21
+
22
+ it "should work!" do
23
+ begin
24
+ server = RTunnel::Server.new(:control_address => 'localhost:9999')
25
+ client = RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30002', :tunnel_to_address => '30003')
26
+
27
+ server.start
28
+ client.start
29
+
30
+ s = TextServer.new(30003, echo_text = "tunnel this txt plz!")
31
+ s.start
32
+
33
+ sleep 0.5
34
+
35
+ # direct connect (sanity check)
36
+ read_from_address('localhost:30003').should == echo_text
37
+ # tunneled connect
38
+ read_from_address('localhost:30002').should == echo_text
39
+ ensure
40
+ begin
41
+ client.stop
42
+ server.stop
43
+ s.stop
44
+ rescue
45
+ end
46
+ end
47
+ end
48
+
49
+ it "should ping all clients periodically" do
50
+ begin
51
+ server = RTunnel::Server.new(:control_address => 'localhost:9999', :ping_interval => 0.2)
52
+ clients = []
53
+ clients << RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30002', :tunnel_to_address => '30003')
54
+ clients << RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30012', :tunnel_to_address => '30013')
55
+ server.start
56
+ clients.each{|c|c.start}
57
+
58
+ pings = Hash.new {|h,k| h[k] = [] }
59
+ t = Thread.new do
60
+ loop do
61
+ clients.each do |client|
62
+ pings[client] << client.instance_eval { @last_ping }
63
+ end
64
+ sleep 0.05
65
+ end
66
+ end
67
+
68
+ sleep 2
69
+
70
+ clients.each do |client|
71
+ # 2 seconds, 0.2 pings a sec = ~10 pings
72
+ (9..11).should include(pings[client].uniq.size)
73
+ end
74
+ ensure
75
+ t.kill
76
+ begin
77
+ clients.each{|c|c.stop}
78
+ server.stop
79
+ rescue
80
+ p $!,$@
81
+ end
82
+ end
83
+ end
84
+
85
+ it "the client shouldnt fail even if there is no server running at the tunnel_to address" do
86
+ begin
87
+ server = RTunnel::Server.new(:control_address => 'localhost:9999')
88
+ client = RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30002', :tunnel_to_address => '30003')
89
+
90
+ server.start
91
+ client.start
92
+
93
+ sleep 0.5
94
+
95
+ # tunneled connect
96
+ read_from_address('localhost:30002').should be_empty
97
+ ensure
98
+ begin
99
+ client.stop
100
+ server.stop
101
+ rescue
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'server'
4
+
5
+ include RTunnel
6
+ describe RTunnel::Server, "addresses" do
7
+ it "control address should listen on all interfaces and use default control port if not specified" do
8
+ Server.new(:control_address => nil).
9
+ instance_eval { @control_address }.should == "0.0.0.0:#{DEFAULT_CONTROL_PORT}"
10
+
11
+ Server.new(:control_address => '5555').
12
+ instance_eval { @control_address }.should == "0.0.0.0:5555"
13
+
14
+ Server.new(:control_address => 'interface2').
15
+ instance_eval { @control_address }.should == "interface2:#{DEFAULT_CONTROL_PORT}"
16
+
17
+ Server.new(:control_address => 'interface2:2222').
18
+ instance_eval { @control_address }.should == "interface2:2222"
19
+ end
20
+
21
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
2
+
3
+ require 'spec'
@@ -0,0 +1,77 @@
1
+ # Contains generate_ and verify_ names for all RTunnel commands.
2
+ module CommandStubs
3
+ @@test_id1 = "1029384756ALSKDJFH"
4
+ @@test_id2 = "ALSKDJFH1029384756"
5
+ @@test_address = "192.168.43.95"
6
+ @@data = (0..255).to_a.pack('C*')
7
+ @@ekey = (128...192).to_a.pack('C*') * 8
8
+ @@pubfp = (0...128).to_a.pack('C*') * 5
9
+
10
+ def generate_keep_alive
11
+ RTunnel::KeepAliveCommand.new
12
+ end
13
+ def verify_keep_alive(cmd)
14
+ assert_equal RTunnel::KeepAliveCommand, cmd.class
15
+ end
16
+
17
+ def generate_create
18
+ RTunnel::CreateConnectionCommand.new @@test_id1
19
+ end
20
+ def verify_create(cmd)
21
+ assert_equal RTunnel::CreateConnectionCommand, cmd.class
22
+ assert_equal @@test_id1, cmd.connection_id
23
+ end
24
+
25
+ def generate_close
26
+ RTunnel::CloseConnectionCommand.new @@test_id2
27
+ end
28
+ def verify_close(cmd)
29
+ assert_equal RTunnel::CloseConnectionCommand, cmd.class
30
+ assert_equal @@test_id2, cmd.connection_id
31
+ end
32
+
33
+ def generate_listen
34
+ RTunnel::RemoteListenCommand.new @@test_address
35
+ end
36
+ def verify_listen(cmd)
37
+ assert_equal RTunnel::RemoteListenCommand, cmd.class
38
+ assert_equal @@test_address, cmd.address
39
+ end
40
+
41
+ def generate_send
42
+ RTunnel::SendDataCommand.new @@test_id1, @@data
43
+ end
44
+ def verify_send(cmd)
45
+ assert_equal RTunnel::SendDataCommand, cmd.class
46
+ assert_equal @@test_id1, cmd.connection_id
47
+ assert_equal @@data, cmd.data
48
+ end
49
+
50
+ def generate_set_sk
51
+ RTunnel::SetSessionKeyCommand.new @@ekey
52
+ end
53
+ def verify_set_sk(cmd)
54
+ assert_equal RTunnel::SetSessionKeyCommand, cmd.class
55
+ assert_equal @@ekey, cmd.encrypted_keys
56
+ end
57
+
58
+ def generate_gen_sk
59
+ RTunnel::GenerateSessionKeyCommand.new @@pubfp
60
+ end
61
+ def verify_gen_sk(cmd)
62
+ assert_equal RTunnel::GenerateSessionKeyCommand, cmd.class
63
+ assert_equal @@pubfp, cmd.public_key_fp
64
+ end
65
+
66
+ # An array with the names of all commands.
67
+ # Use these names to obtain the names of the genrate_ and verify_ methods.
68
+ def self.command_names
69
+ [:keep_alive, :create, :close, :listen, :send, :gen_sk, :set_sk]
70
+ end
71
+
72
+ # A sequence of command names useful for testing "real" connections.
73
+ def self.command_test_sequence
74
+ [:create, :keep_alive, :gen_sk, :set_sk, :keep_alive, :listen, :keep_alive,
75
+ :send, :send, :keep_alive, :keep_alive, :send, :close]
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ # Mocks the sending end of an EventMachine connection.
2
+ # The sent data is concatenated in a string available by calling #string.
3
+ class EmSendMock
4
+ attr_reader :string
5
+
6
+ def initialize
7
+ @string = ''
8
+ end
9
+
10
+ def send_data(data)
11
+ @string << data
12
+ end
13
+ end
14
+
15
+ # Mocks the receiving end of an EventMachine connection.
16
+ # The data to be received is passed as an array of strings to the constructor.
17
+ # Calling #replay mocks receiving the data.
18
+ class EmReceiveMock
19
+ attr_accessor :strings
20
+ attr_accessor :objects
21
+
22
+ def initialize(strings = [''])
23
+ @strings = strings
24
+ @objects = []
25
+ end
26
+
27
+ def replay
28
+ @strings.each { |str| receive_data str }
29
+ self
30
+ end
31
+
32
+ def receive_object(object)
33
+ @objects << object
34
+ end
35
+
36
+ # Declares the name of the object to be received. For instance, a frame
37
+ # protocol would use :frame for name. This generates a receive_frame method,
38
+ # and a frames accessor.
39
+ def self.object_name(name)
40
+ alias_method "receive_#{name}".to_sym, :receive_object
41
+ alias_method "#{name}s".to_sym, :objects
42
+ end
43
+ end
@@ -0,0 +1,109 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ # Event Machine connection that runs a fixed send-expect scenario.
5
+ class ScenarioConnection < EventMachine::Connection
6
+ def initialize(test_case, scenario = nil)
7
+ super()
8
+
9
+ @test_case = test_case
10
+ @scenario = scenario || yield
11
+ @ignore_unbind = false
12
+ next_step
13
+ end
14
+
15
+ def post_init
16
+ scenario_can_send
17
+ end
18
+
19
+ def receive_data(data)
20
+ scenario_received data
21
+ end
22
+
23
+ def unbind
24
+ return if @ignore_unbind
25
+
26
+ unless @step and @step.first == :unbind
27
+ scenario_fail "Received unexpected unbind\n"
28
+ end
29
+ next_step
30
+ while @step and @step.first == :proc
31
+ @step.last.call
32
+ next_step
33
+ end
34
+ if @step and @step.first == :stop
35
+ scenario_stop @step.last
36
+ end
37
+ end
38
+
39
+ # Called when data is received. Plays the connection scenario.
40
+ def scenario_received(data)
41
+ unless @step and @step.first == :recv
42
+ scenario_fail "Received unexpected data: #{data}\n"
43
+ end
44
+
45
+ @test_case.send :assert_equal, @step.last, data
46
+ next_step
47
+ scenario_can_send
48
+ end
49
+
50
+ # Called when data can be sent. Plays the connection scenario.
51
+ def scenario_can_send
52
+ while @step
53
+ case @step.first
54
+ when :proc
55
+ @step.last.call
56
+ when :send
57
+ send_data @step.last
58
+ else
59
+ break
60
+ end
61
+ next_step
62
+ end
63
+
64
+ unless @step
65
+ # EM might stifle this exception and reraise
66
+ msg = "Scenario completed prematurely"
67
+ $stderr.puts msg
68
+ fail msg
69
+ end
70
+
71
+ case @step.first
72
+ when :receive
73
+ # wait to receive
74
+ return
75
+ when :unbind
76
+ # wait for unbind
77
+ return
78
+ when :close
79
+ @ignore_unbind = true
80
+ close_connection_after_writing
81
+ next_step
82
+ end
83
+ end
84
+
85
+ def scenario_fail(fail_message)
86
+ if @step
87
+ fail_message << "Expected to #{@step.inspect}"
88
+ else
89
+ fail_message << "Scenario was completed\n"
90
+ end
91
+ @test_case.send :fail, fail_message
92
+ end
93
+
94
+ def scenario_stop(stop_proc)
95
+ if stop_proc.kind_of? Proc
96
+ # call the proc, then give em time to stop all its servers
97
+ stop_proc.call
98
+ EventMachine.add_timer 0.1 do
99
+ EventMachine.stop_event_loop
100
+ end
101
+ else
102
+ EventMachine.stop_event_loop
103
+ end
104
+ end
105
+
106
+ def next_step
107
+ @step = @scenario.shift
108
+ end
109
+ end
@@ -0,0 +1,48 @@
1
+ require 'rtunnel'
2
+
3
+ require 'openssl'
4
+ require 'resolv'
5
+ require 'test/unit'
6
+
7
+ class ClientTest < Test::Unit::TestCase
8
+ def setup
9
+ @client = RTunnel::Client.new(:control_address => 'localhost',
10
+ :remote_listen_address => '9199',
11
+ :tunnel_to_address => '4444',
12
+ :private_key => 'test_data/ssh_host_rsa_key',
13
+ :tunnel_timeout => 5)
14
+ @localhost_addr = Resolv.getaddress 'localhost'
15
+ end
16
+
17
+ def test_options
18
+ client = RTunnel::Client
19
+ assert_equal "18.241.3.100:#{RTunnel::DEFAULT_CONTROL_PORT}",
20
+ client.extract_control_address('18.241.3.100')
21
+ assert_equal "18.241.3.100:9199",
22
+ client.extract_control_address('18.241.3.100:9199')
23
+ assert_equal "#{@localhost_addr}:#{RTunnel::DEFAULT_CONTROL_PORT}",
24
+ client.extract_control_address('localhost')
25
+ assert_equal "#{@localhost_addr}:9199",
26
+ client.extract_control_address('localhost:9199')
27
+
28
+ assert_equal "0.0.0.0:9199",
29
+ client.extract_remote_listen_address('9199')
30
+ assert_equal "18.241.3.100:9199",
31
+ client.extract_remote_listen_address('18.241.3.100:9199')
32
+ assert_equal "#{@localhost_addr}:9199",
33
+ client.extract_remote_listen_address('localhost:9199')
34
+
35
+ assert_equal "18.241.3.100:9199",
36
+ client.extract_tunnel_to_address('18.241.3.100:9199')
37
+ assert_equal "#{@localhost_addr}:9199",
38
+ client.extract_tunnel_to_address('9199')
39
+
40
+ assert_equal RTunnel::TUNNEL_TIMEOUT, client.extract_tunnel_timeout(nil)
41
+ assert_equal 29, client.extract_tunnel_timeout(29)
42
+
43
+ assert_equal nil, client.extract_private_key(nil)
44
+ key = client.extract_private_key 'test_data/ssh_host_rsa_key'
45
+ assert_equal OpenSSL::PKey::RSA, key.class
46
+ assert_equal File.read('test_data/ssh_host_rsa_key'), key.to_pem
47
+ end
48
+ end
@@ -0,0 +1,82 @@
1
+ require 'rtunnel'
2
+
3
+ require 'test/unit'
4
+
5
+ require 'test/command_stubs.rb'
6
+ require 'test/protocol_mocks.rb'
7
+
8
+ # Send mock for commands.
9
+ class EmSendCommandsMock < EmSendMock
10
+ include RTunnel::CommandProtocol
11
+ end
12
+
13
+ # Receive mock for commands.
14
+ class EmReceiveCommandsMock < EmReceiveMock
15
+ include RTunnel::CommandProtocol
16
+ object_name :command
17
+ end
18
+
19
+ class CommandProtocolTest < Test::Unit::TestCase
20
+ include CommandStubs
21
+ C = RTunnel::Crypto
22
+
23
+ def setup
24
+ super
25
+ @send_mock = EmSendCommandsMock.new
26
+ end
27
+
28
+ def teardown
29
+ super
30
+ end
31
+
32
+ def commandset_test(names)
33
+ @send_mock.outgoing_command_hasher = @hasher if @hasher
34
+ names.each do |name|
35
+ command = self.send "generate_#{name}".to_sym
36
+ @send_mock.send_command command
37
+ end
38
+ receive_mock = EmReceiveCommandsMock.new([@send_mock.string])
39
+ receive_mock.incoming_command_hasher = C::Hasher.new(@hasher.key) if @hasher
40
+ o_commands = receive_mock.replay.commands
41
+ self.assert_equal names.length, o_commands.length
42
+ names.each_index do |i|
43
+ self.send "verify_#{names[i]}", o_commands[i]
44
+ end
45
+ end
46
+
47
+ CommandStubs.command_names.each do |name|
48
+ define_method "test_#{name}".to_sym do
49
+ commandset_test [name]
50
+ end
51
+
52
+ define_method "test_signed_#{name}".to_sym do
53
+ @hasher = C::Hasher.new
54
+ commandset_test [name]
55
+ end
56
+
57
+ define_method "test_signed_#{name}_has_signature".to_sym do
58
+ sig_send_mock = EmSendCommandsMock.new
59
+ sig_send_mock.outgoing_command_hasher = C::Hasher.new
60
+ outputs = [@send_mock, sig_send_mock].map do |mock|
61
+ mock.send_command self.send("generate_#{name}".to_sym)
62
+ mock.string
63
+ end
64
+ assert outputs.first.length < outputs.last.length,
65
+ "No signature generated"
66
+ end
67
+
68
+ define_method "test_signed_#{name}_enforces_signature".to_sym do
69
+ @send_mock.outgoing_command_hasher = hasher = C::Hasher.new
70
+ @send_mock.send_command self.send("generate_#{name}".to_sym)
71
+ signed_str = @send_mock.string
72
+
73
+ 0.upto(signed_str.length - 1) do |i|
74
+ bad_str = signed_str.dup
75
+ bad_str[i] ^= 0x01
76
+ recv_mock = EmReceiveCommandsMock.new([bad_str])
77
+ recv_mock.incoming_command_hasher = C::Hasher.new hasher.key
78
+ assert_equal [], recv_mock.replay.commands
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ require 'rtunnel'
2
+
3
+ require 'stringio'
4
+ require 'test/unit'
5
+
6
+ require 'test/command_stubs.rb'
7
+
8
+ class CommandsTest < Test::Unit::TestCase
9
+ include CommandStubs
10
+
11
+ def setup
12
+ super
13
+ @str = StringIO.new
14
+ end
15
+
16
+ CommandStubs.command_names.each do |cmd|
17
+ define_method "test_#{cmd}_encode" do
18
+ command = self.send "generate_#{cmd}"
19
+ command.encode @str
20
+ @str.rewind
21
+ decoded_command = RTunnel::Command.decode @str
22
+ self.send "verify_#{cmd}", decoded_command
23
+ assert_equal "", @str.read, "Command #{cmd} did not consume its entire outpt"
24
+ end
25
+
26
+ define_method "test_#{cmd}_to_encoded_str" do
27
+ command = self.send "generate_#{cmd}"
28
+ command.encode @str
29
+ @str.rewind
30
+ assert_equal @str.read, command.to_encoded_str
31
+ end
32
+ end
33
+
34
+ def test_all_encodes
35
+ sequence = CommandStubs.command_test_sequence
36
+ sequence.each { |cmd| self.send("generate_#{cmd}").encode @str }
37
+ @str.rewind
38
+ sequence.each do |cmd|
39
+ command = RTunnel::Command.decode(@str)
40
+ self.send "verify_#{cmd}", command
41
+ end
42
+ assert_equal "", @str.read
43
+ end
44
+
45
+ def test_codes
46
+ # TODO(not_me): it'd be nice to have more than a smoke test here
47
+ assert_equal String, RTunnel::Command.printable_codes.class
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ require 'rtunnel'
2
+
3
+ require 'test/unit'
4
+
5
+ class ConnectionIdTest < Test::Unit::TestCase
6
+ class CidWrapper
7
+ include RTunnel::ConnectionId
8
+ end
9
+
10
+ def setup
11
+ @server = CidWrapper.new
12
+ end
13
+
14
+ def test_ids_are_unique
15
+ n_ids = 1024
16
+ ids = (0...n_ids).map { @server.new_connection_id }
17
+ assert_equal n_ids, ids.uniq.length
18
+ end
19
+
20
+ def test_id_sequences_are_not_trivial
21
+ n_servers = 256
22
+ n_ids = 16
23
+ sequences = (0...n_servers).map { CidWrapper.new }.map do |server|
24
+ (0...n_ids).map { server.new_connection_id }
25
+ end
26
+ 0.upto(n_ids - 1) do |i|
27
+ assert_equal n_servers, sequences.map { |seq| seq[i] }.uniq.length
28
+ end
29
+ end
30
+ end