rtunnel 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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