telnet_q 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in telnet_q.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,43 @@
1
+ == telnet_q - The Q Method of Implementing TELNET Option Negotiation (RFC 1143)
2
+
3
+ gem install telnet_q
4
+
5
+ telnet_q provides a RFC 1143-compliant state machine for performing TELNET option negotiation.
6
+
7
+ ---
8
+
9
+ ===Contributors
10
+
11
+ * Dwayne Litzenberger - dlitz@patientway.com
12
+
13
+ ---
14
+
15
+ ===Issues?
16
+
17
+ Lighthouse: https://github.com/dlitz/telnet_q/issues
18
+
19
+ ---
20
+
21
+ ===License
22
+
23
+ (The MIT License)
24
+
25
+ Copyright © 2011 Infonium Inc.
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
28
+ this software and associated documentation files (the ‘Software’), to deal in
29
+ the Software without restriction, including without limitation the rights to
30
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
31
+ of the Software, and to permit persons to whom the Software is furnished to do
32
+ so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ t.libs = %w( lib test )
8
+ end
data/lib/telnet_q.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'telnet_q/version'
2
+ require 'telnet_q/base'
3
+ require 'telnet_q/sm'
4
+ require 'telnet_q/socket_q'
@@ -0,0 +1,186 @@
1
+ require 'set'
2
+
3
+ module TelnetQ
4
+ # This class implements RFC 1143 - The Q Method of implementing TELNET option negotiation.
5
+ #
6
+ # There are two sides to the conversation: "us" and "him". "us" refers to
7
+ # the local party; "him" refers to the remote party. (This is the terminology
8
+ # used in the RFC.)
9
+ #
10
+ # Telnet options are integers between 0 and 255. The current list of options
11
+ # is available at: http://www.iana.org/assignments/telnet-options
12
+ #
13
+ # Options:
14
+ #
15
+ # [:us]
16
+ # The list of options the local party should initially enable. When the
17
+ # connection is established, the local party sends a WILL request for each
18
+ # of these options. Note that if the remote party sends DONT in response
19
+ # to our request, the option will remain disabled.
20
+ # [:him]
21
+ # The list of options we want the remote party should initially enable.
22
+ # When the connection is established, the local party sends a DO request
23
+ # for each of these options. Note that if the remote party sends WONT
24
+ # in response to our request, the option will remain disabled.
25
+ #
26
+ # Example usage:
27
+ #
28
+ # TelnetQ::Base.new(:us => [0,3], :him => [0,1,3])
29
+ #
30
+ class Base
31
+ def initialize(options={})
32
+ @state_machines = {
33
+ :us => {},
34
+ :him => {},
35
+ }
36
+ @supported_options = {
37
+ :us => Set.new(options[:us] || []),
38
+ :him => Set.new(options[:him].uniq || []),
39
+ }
40
+ end
41
+
42
+ def start
43
+ initial_requests
44
+ end
45
+
46
+ def inspect
47
+ us = @state_machines[:us].keys.sort.map{|opt| "#{opt}:#{@state_machines[:us][opt].state}"}.join(" ")
48
+ him = @state_machines[:him].keys.sort.map{|opt| "#{opt}:#{@state_machines[:him][opt].state}"}.join(" ")
49
+ "#<#{self.class.name} us(#{us}) him(#{him})>"
50
+ end
51
+
52
+ # Invoke this when a TELNET negotiation message is received.
53
+ #
54
+ # Example usage:
55
+ # tq = TelnetQ::Base.new(...)
56
+ # tq.received_message(:do, 0) # Remote party requesting that we enable binary transmission on our side
57
+ # tq.received_message(:will, 0) # Remote party requesting that to enable binary transmission on his side.
58
+ def received_message(verb, option)
59
+ case verb
60
+ when :do, :dont
61
+ party = :us
62
+ when :will, :wont
63
+ party = :him
64
+ else
65
+ raise ArgumentError.new("invalid verb #{verb.inspect}")
66
+ end
67
+ sm = state_machine(party, option)
68
+ sm.received(verb)
69
+ nil
70
+ end
71
+
72
+ # Check whether the option is currently enabled
73
+ def enabled?(party, option)
74
+ sm = state_machine(party, option)
75
+ sm.state == :yes
76
+ end
77
+
78
+ # Ask the specified party enable this option.
79
+ #
80
+ # party may be one of :us or :them.
81
+ def request(party, option)
82
+ sm = state_machine(party, option)
83
+ @supported_options[party] << option
84
+ sm.ask_enable unless sm.state == :yes
85
+ nil
86
+ end
87
+
88
+ # Ask the specified party to disable this option.
89
+ #
90
+ # party may be one of :us or :them.
91
+ def forbid(party, option)
92
+ sm = state_machine(party, option)
93
+ @supported_options[party].delete(option)
94
+ sm.ask_disable unless sm.state == :no
95
+ nil
96
+ end
97
+
98
+ protected
99
+
100
+ # Callback invoked when the remote party requests that a currently-disabled
101
+ # option be enabled.
102
+ #
103
+ # The "party" parameter is as follows:
104
+ # [:us]
105
+ # Received a request for the local party to enable the option. (Remote sent DO).
106
+ # [:him]
107
+ # Received a request for the remote party enable the option. (Remote sent WILL).
108
+ #
109
+ # Return true if we allow the change, or false if we forbid it.
110
+ #
111
+ # You may override this in your subclass.
112
+ def option_supported?(party, option)
113
+ @supported_options[party].include?(option)
114
+ end
115
+
116
+ # Callback invoked when the specified option has been negotiated.
117
+ #
118
+ # Override this in your subclass.
119
+ def option_negotiated(party, option, enabled)
120
+ #puts "#{party == :us ? "We" : "He"} #{enabled ? "enabled" : "disabled"} option #{option}"
121
+ nil
122
+ end
123
+
124
+ # Callback: Send the specified message to the remote party.
125
+ #
126
+ # Override this in your subclass.
127
+ def send_message(verb, option)
128
+ #puts "SEND #{verb.to_s.upcase} #{option.inspect}"
129
+ nil
130
+ end
131
+
132
+ # Callback: Invoked when there is an error negotiating the specified option.
133
+ #
134
+ # Override this in your subclass.
135
+ def error(msg, party, option)
136
+ $stderr.puts "Error negotiating option #{party}#{option}: #{msg}"
137
+ nil
138
+ end
139
+
140
+ private
141
+
142
+ def state_machine(party, option)
143
+ party = parse_party(party)
144
+ option = parse_option(option)
145
+
146
+ unless @state_machines[party][option]
147
+ case party
148
+ when :him
149
+ sm = SM::HisOptionState.new(:supported_proc => lambda{ option_supported?(:him, option) },
150
+ :send_proc => lambda{ |verb| send_message(verb, option) },
151
+ :negotiated_proc => lambda{ |enabled| option_negotiated(:him, option, enabled) },
152
+ :error_proc => lambda{ |msg| error(msg, :him, option) })
153
+ @state_machines[party][option] = sm
154
+ when :us
155
+ sm = SM::MyOptionState.new(:supported_proc => lambda{ option_supported?(:us, option) },
156
+ :send_proc => lambda{ |verb| send_message(verb, option) },
157
+ :negotiated_proc => lambda{ |enabled| option_negotiated(:us, option, enabled) },
158
+ :error_proc => lambda{ |msg| error(msg, :us, option) })
159
+ @state_machines[party][option] = sm
160
+ else
161
+ raise "BUG"
162
+ end
163
+ end
164
+ @state_machines[party][option]
165
+ end
166
+
167
+ def parse_option(option)
168
+ raise TypeError.new("option must be an integer") unless option.is_a?(Integer)
169
+ option
170
+ end
171
+
172
+ def parse_party(party)
173
+ raise ArgumentError.new("party must be :us or :him") unless [:us, :him].include?(party)
174
+ party
175
+ end
176
+
177
+ def initial_requests
178
+ # Set the initial options
179
+ [:us, :him].each do |party|
180
+ @supported_options[party].each do |option|
181
+ request(party, option)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,167 @@
1
+ module TelnetQ
2
+ module SM
3
+ # Implements the state machine described in Section 7 of RFC 1143.
4
+ #
5
+ # NOTE: This code is awful, so consider this entire API private and subject
6
+ # to change!
7
+ class HisOptionState
8
+
9
+ def initialize(options={}, &block)
10
+ @state = :no
11
+ @supported_proc = options[:supported_proc]
12
+ @send_proc = options[:send_proc]
13
+ @error_proc = options[:error_proc]
14
+ @negotiated_proc = options[:negotiated_proc]
15
+ end
16
+
17
+ # Is he allowed to enable this option?
18
+ def supported?
19
+ !!@supported_proc.call
20
+ end
21
+
22
+ attr_reader :state
23
+
24
+ def state=(v)
25
+ raise ArgumentError.new unless [:no, :yes, :wantno, :wantyes, :wantno_opposite, :wantyes_opposite].include?(v)
26
+ old_state = @state
27
+ @state = v
28
+ if v != old_state
29
+ if v == :yes
30
+ negotiated(true)
31
+ elsif v == :no
32
+ negotiated(false)
33
+ end
34
+ end
35
+ v
36
+ end
37
+
38
+ alias him state
39
+ alias him= state
40
+
41
+ def received(which)
42
+ raise ArgumentError.new("invalid which=#{which.inspect}") unless [:will, :wont, :do, :dont].include?(which)
43
+ send("received_#{which}")
44
+ end
45
+
46
+ # Received WILL for this option
47
+ def received_will
48
+ case state
49
+ when :no
50
+ if supported?
51
+ self.state = :yes
52
+ send_do
53
+ else
54
+ send_dont
55
+ end
56
+ when :yes
57
+ # Ignore; already enabled
58
+ when :wantno
59
+ error("DONT answered by WILL.")
60
+ self.state = :no
61
+ when :wantno_opposite
62
+ error("DONT answered by WILL.")
63
+ self.state = :yes
64
+ when :wantyes
65
+ self.state = :yes
66
+ when :wantyes_opposite
67
+ self.state = :wantno
68
+ send_dont
69
+ else
70
+ raise "BUG: Illegal state #{state.inspect}"
71
+ end
72
+ end
73
+
74
+ def received_wont
75
+ case state
76
+ when :no
77
+ # Ignore; already disabled
78
+ when :yes
79
+ self.state = :no
80
+ send_dont
81
+ when :wantno
82
+ self.state = :no
83
+ when :wantno_opposite
84
+ self.state = :wantyes
85
+ send_do
86
+ when :wantyes
87
+ self.state = :no
88
+ when :wantyes_opposite
89
+ self.state = :no
90
+ else
91
+ raise "BUG: Illegal state #{state.inspect}"
92
+ end
93
+ end
94
+
95
+ # If we decide to ask him to enable
96
+ def ask_enable
97
+ case state
98
+ when :no
99
+ self.state = :wantyes
100
+ send_do
101
+ when :yes
102
+ error("Already enabled.")
103
+ when :wantno
104
+ self.state = :wantno_opposite
105
+ when :wantno_opposite
106
+ error("Already queued an enable request.")
107
+ when :wantyes
108
+ error("Already negotiating for enable.")
109
+ when :wantyes_opposite
110
+ self.state = :wantyes
111
+ else
112
+ raise "BUG: Illegal state #{state.inspect}"
113
+ end
114
+ end
115
+
116
+ def ask_disable
117
+ case state
118
+ when :no
119
+ error("Already disabled.")
120
+ when :yes
121
+ self.state = :wantno
122
+ send_dont
123
+ when :wantno
124
+ error("Already negotiating for disable.")
125
+ when :wantno_opposite
126
+ self.state = :wantno
127
+ when :wantyes
128
+ self.state = :wantyes_opposite
129
+ when :wantyes_opposite
130
+ error("Already queued a disable request.")
131
+ else
132
+ raise "BUG: Illegal state #{state.inspect}"
133
+ end
134
+ end
135
+
136
+ def send_do; send_option(:do); end
137
+ def send_dont; send_option(:dont); end
138
+
139
+ def send_option(cmd)
140
+ raise ArgumentError.new("invalid send-option #{cmd.inspect}") unless [:will, :wont, :do, :dont].include?(cmd)
141
+ @send_proc.call(cmd)
142
+ end
143
+
144
+ def error(msg)
145
+ @error_proc.call(msg)
146
+ end
147
+
148
+ def negotiated(enabled)
149
+ @negotiated_proc.call(enabled)
150
+ end
151
+ end
152
+
153
+ # Same, but for negotiating our options
154
+ class MyOptionState < HisOptionState
155
+ def send_will; send_option(:will); end
156
+ def send_wont; send_option(:wont); end
157
+ alias send_do send_will
158
+ alias send_dont send_wont
159
+
160
+ alias received_do received_will
161
+ alias received_dont received_wont
162
+
163
+ alias us state
164
+ alias us= state=
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,57 @@
1
+ require 'telnet_q/base'
2
+
3
+ module TelnetQ
4
+ class SocketQ < Base
5
+
6
+ VERB_NAMES = {
7
+ 251 => :will,
8
+ 252 => :wont,
9
+ 253 => :do,
10
+ 254 => :dont,
11
+ }.freeze
12
+
13
+ VERB_NUMS = VERB_NAMES.invert.freeze
14
+
15
+ def initialize(connection, options={})
16
+ @connection = connection
17
+ super(options)
18
+ end
19
+
20
+ # Invoke this when a TELNET negotiation message is received.
21
+ #
22
+ # Example usage:
23
+ # sq = TelnetQ::SocketQ.new(connection, ...)
24
+ # sq.received_raw_message("\377\375\000") # Remote party requesting that we enable binary transmission on our side
25
+ # sq.received_raw_message("\377\373\000") # Remote party requesting that to enable binary transmission on his side.
26
+ def received_raw_message(raw)
27
+ verb, option = parse_raw_telnet_option_message(raw)
28
+ received_message(verb, option)
29
+ end
30
+
31
+ protected
32
+
33
+ def send_message(verb, option)
34
+ send_raw_message(generate_raw_telnet_option_message(verb, option))
35
+ end
36
+
37
+ def send_raw_message(raw)
38
+ @connection.write(raw)
39
+ end
40
+
41
+ # Translate e.g. "\377\375\000" -> [:do, 0]
42
+ def parse_raw_telnet_option_message(raw)
43
+ iac, v, option = raw.unpack("CCC")
44
+ verb = VERB_NAMES[v]
45
+ raise ArgumentError.new("illegal option") unless raw.length == 3 and iac == 255 and verb
46
+ [verb, option]
47
+ end
48
+
49
+ # Translate e.g. [:do, 0] -> "\377\375\000"
50
+ def generate_raw_telnet_option_message(verb, option)
51
+ vn = VERB_NUMS[verb]
52
+ raise ArgumentError.new("unsupported verb: #{verb.inspect}") unless vn
53
+ raise ArgumentError.new("illegal option: #{option.inspect}") unless option.is_a?(Integer) and (0..255).include?(option)
54
+ [255, vn, option].pack("CCC")
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module TelnetQ
2
+ VERSION = '0.1'
3
+ end
data/telnet_q.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "telnet_q/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "telnet_q"
7
+ s.version = TelnetQ::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dwayne Litzenberger"]
10
+ s.email = ["dlitz@patientway.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{The Q Method of Implementing TELNET Option Negotiation (RFC 1143)}
13
+ s.description = %q{telnet_q implements D. J. Bernstein's "Q Method" of implementing TELNET option negotiation, as described in RFC 1143.}
14
+
15
+ s.rubyforge_project = "telnet_q"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,122 @@
1
+ require 'test_helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+ class TQBase < TelnetQ::Base
5
+ def initialize(test_events, *args)
6
+ @test_events = test_events
7
+ super(*args)
8
+ end
9
+
10
+ attr_accessor :other
11
+ attr_accessor :name
12
+
13
+ def received_message(verb, option)
14
+ @test_events << "#{name}: Received #{verb.to_s.upcase} #{option.inspect}"
15
+ super
16
+ end
17
+
18
+ def send_message(verb, option)
19
+ @test_events << "#{name}: Sent #{verb.to_s.upcase} #{option.inspect}"
20
+ @other.received_message(verb, option)
21
+ end
22
+
23
+ # Always allow option 3 (for both parties)
24
+ def option_supported?(party, option)
25
+ (option == 3) or super
26
+ end
27
+
28
+ def option_negotiated(party, option, enabled)
29
+ @test_events << "#{name}: #{party == :us ? "We" : "They"} #{enabled ? "enabled" : "disabled"} option #{option}"
30
+ end
31
+ end
32
+
33
+ def test_option_not_supported_him
34
+ events = []
35
+
36
+ alice = TQBase.new(events, :us => [], :him => [0])
37
+ bob = TQBase.new(events, :us => [], :him => [])
38
+ alice.name = "alice"
39
+ bob.name = "bob"
40
+ alice.other = bob
41
+ bob.other = alice
42
+
43
+ alice.start
44
+ bob.start
45
+
46
+ assert_equal [
47
+ "alice: Sent DO 0",
48
+ "bob: Received DO 0",
49
+ "bob: Sent WONT 0",
50
+ "alice: Received WONT 0",
51
+ "alice: They disabled option 0",
52
+ ], events
53
+ end
54
+
55
+ def test_option_not_supported_us
56
+ events = []
57
+
58
+ alice = TQBase.new(events, :us => [0], :him => [])
59
+ bob = TQBase.new(events, :us => [], :him => [])
60
+ alice.name = "alice"
61
+ bob.name = "bob"
62
+ alice.other = bob
63
+ bob.other = alice
64
+
65
+ alice.start
66
+ bob.start
67
+
68
+ assert_equal [
69
+ "alice: Sent WILL 0",
70
+ "bob: Received WILL 0",
71
+ "bob: Sent DONT 0",
72
+ "alice: Received DONT 0",
73
+ "alice: We disabled option 0",
74
+ ], events
75
+ end
76
+
77
+ def test_option_supported_him
78
+ events = []
79
+
80
+ alice = TQBase.new(events, :us => [], :him => [3])
81
+ bob = TQBase.new(events, :us => [], :him => [])
82
+ alice.name = "alice"
83
+ bob.name = "bob"
84
+ alice.other = bob
85
+ bob.other = alice
86
+
87
+ alice.start
88
+ bob.start
89
+
90
+ assert_equal [
91
+ "alice: Sent DO 3",
92
+ "bob: Received DO 3",
93
+ "bob: We enabled option 3",
94
+ "bob: Sent WILL 3",
95
+ "alice: Received WILL 3",
96
+ "alice: They enabled option 3",
97
+ ], events
98
+ end
99
+
100
+ def test_option_supported_us
101
+ events = []
102
+
103
+ alice = TQBase.new(events, :us => [3], :him => [])
104
+ bob = TQBase.new(events, :us => [], :him => [])
105
+ alice.name = "alice"
106
+ bob.name = "bob"
107
+ alice.other = bob
108
+ bob.other = alice
109
+
110
+ alice.start
111
+ bob.start
112
+
113
+ assert_equal [
114
+ "alice: Sent WILL 3",
115
+ "bob: Received WILL 3",
116
+ "bob: They enabled option 3",
117
+ "bob: Sent DO 3",
118
+ "alice: Received DO 3",
119
+ "alice: We enabled option 3",
120
+ ], events
121
+ end
122
+ end
@@ -0,0 +1,175 @@
1
+ require 'test_helper'
2
+ require 'socket'
3
+
4
+ class SocketTest < Test::Unit::TestCase
5
+ class TQSocketQ < TelnetQ::SocketQ
6
+ def initialize(test_events, *args)
7
+ @test_events = test_events
8
+ super(*args)
9
+ end
10
+
11
+ attr_accessor :name
12
+
13
+ def received_message(verb, option)
14
+ @test_events << "#{name}: Received #{verb.to_s.upcase} #{option.inspect}"
15
+ super
16
+ end
17
+
18
+ def send_message(verb, option)
19
+ @test_events << "#{name}: Sent #{verb.to_s.upcase} #{option.inspect}"
20
+ super
21
+ end
22
+
23
+ # Always allow option 3 (for both parties)
24
+ def option_supported?(party, option)
25
+ (option == 3) or super
26
+ end
27
+
28
+ def option_negotiated(party, option, enabled)
29
+ @test_events << "#{name}: #{party == :us ? "We" : "They"} #{enabled ? "enabled" : "disabled"} option #{option}"
30
+ end
31
+ end
32
+
33
+ def test_option_not_supported_him
34
+ alice_socket, bob_socket = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
35
+
36
+ events = []
37
+
38
+ alice = TQSocketQ.new(events, alice_socket, :us => [], :him => [0])
39
+ bob = TQSocketQ.new(events, bob_socket, :us => [], :him => [])
40
+ alice.name = "alice"
41
+ bob.name = "bob"
42
+
43
+ alice.start
44
+ bob.start
45
+
46
+ until events.length >= 5
47
+ rr, ww, ee = select([alice_socket, bob_socket], nil, nil)
48
+ if rr.include?(alice_socket)
49
+ alice.received_raw_message(alice_socket.read(3))
50
+ end
51
+ if rr.include?(bob_socket)
52
+ bob.received_raw_message(bob_socket.read(3))
53
+ end
54
+ end
55
+
56
+ assert_equal [
57
+ "alice: Sent DO 0",
58
+ "bob: Received DO 0",
59
+ "bob: Sent WONT 0",
60
+ "alice: Received WONT 0",
61
+ "alice: They disabled option 0",
62
+ ], events
63
+ ensure
64
+ alice_socket.close if alice_socket
65
+ bob_socket.close if bob_socket
66
+ end
67
+
68
+ def test_option_not_supported_us
69
+ alice_socket, bob_socket = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
70
+
71
+ events = []
72
+
73
+ alice = TQSocketQ.new(events, alice_socket, :us => [0], :him => [])
74
+ bob = TQSocketQ.new(events, bob_socket, :us => [], :him => [])
75
+ alice.name = "alice"
76
+ bob.name = "bob"
77
+
78
+ alice.start
79
+ bob.start
80
+
81
+ until events.length >= 5
82
+ rr, ww, ee = select([alice_socket, bob_socket], nil, nil)
83
+ if rr.include?(alice_socket)
84
+ alice.received_raw_message(alice_socket.read(3))
85
+ end
86
+ if rr.include?(bob_socket)
87
+ bob.received_raw_message(bob_socket.read(3))
88
+ end
89
+ end
90
+
91
+ assert_equal [
92
+ "alice: Sent WILL 0",
93
+ "bob: Received WILL 0",
94
+ "bob: Sent DONT 0",
95
+ "alice: Received DONT 0",
96
+ "alice: We disabled option 0",
97
+ ], events
98
+ ensure
99
+ alice_socket.close if alice_socket
100
+ bob_socket.close if bob_socket
101
+ end
102
+
103
+ def test_option_supported_him
104
+ alice_socket, bob_socket = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
105
+
106
+ events = []
107
+
108
+ alice = TQSocketQ.new(events, alice_socket, :us => [], :him => [3])
109
+ bob = TQSocketQ.new(events, bob_socket, :us => [], :him => [])
110
+ alice.name = "alice"
111
+ bob.name = "bob"
112
+
113
+ alice.start
114
+ bob.start
115
+
116
+ until events.length >= 5
117
+ rr, ww, ee = select([alice_socket, bob_socket], nil, nil)
118
+ if rr.include?(alice_socket)
119
+ alice.received_raw_message(alice_socket.read(3))
120
+ end
121
+ if rr.include?(bob_socket)
122
+ bob.received_raw_message(bob_socket.read(3))
123
+ end
124
+ end
125
+
126
+ assert_equal [
127
+ "alice: Sent DO 3",
128
+ "bob: Received DO 3",
129
+ "bob: We enabled option 3",
130
+ "bob: Sent WILL 3",
131
+ "alice: Received WILL 3",
132
+ "alice: They enabled option 3",
133
+ ], events
134
+ ensure
135
+ alice_socket.close if alice_socket
136
+ bob_socket.close if bob_socket
137
+ end
138
+
139
+ def test_option_supported_us
140
+ alice_socket, bob_socket = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
141
+
142
+ events = []
143
+
144
+ alice = TQSocketQ.new(events, alice_socket, :us => [3], :him => [])
145
+ bob = TQSocketQ.new(events, bob_socket, :us => [], :him => [])
146
+ alice.name = "alice"
147
+ bob.name = "bob"
148
+
149
+ alice.start
150
+ bob.start
151
+
152
+ until events.length >= 5
153
+ rr, ww, ee = select([alice_socket, bob_socket], nil, nil)
154
+ if rr.include?(alice_socket)
155
+ alice.received_raw_message(alice_socket.read(3))
156
+ end
157
+ if rr.include?(bob_socket)
158
+ bob.received_raw_message(bob_socket.read(3))
159
+ end
160
+ end
161
+
162
+ assert_equal [
163
+ "alice: Sent WILL 3",
164
+ "bob: Received WILL 3",
165
+ "bob: They enabled option 3",
166
+ "bob: Sent DO 3",
167
+ "alice: Received DO 3",
168
+ "alice: We enabled option 3",
169
+ ], events
170
+ ensure
171
+ alice_socket.close if alice_socket
172
+ bob_socket.close if bob_socket
173
+ end
174
+ end
175
+
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require 'telnet_q'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: telnet_q
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "0.1"
6
+ platform: ruby
7
+ authors:
8
+ - Dwayne Litzenberger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-16 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: telnet_q implements D. J. Bernstein's "Q Method" of implementing TELNET option negotiation, as described in RFC 1143.
18
+ email:
19
+ - dlitz@patientway.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - .gitignore
28
+ - Gemfile
29
+ - README.rdoc
30
+ - Rakefile
31
+ - lib/telnet_q.rb
32
+ - lib/telnet_q/base.rb
33
+ - lib/telnet_q/sm.rb
34
+ - lib/telnet_q/socket_q.rb
35
+ - lib/telnet_q/version.rb
36
+ - telnet_q.gemspec
37
+ - test/base_test.rb
38
+ - test/socket_test.rb
39
+ - test/test_helper.rb
40
+ has_rdoc: true
41
+ homepage: ""
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ requirements: []
62
+
63
+ rubyforge_project: telnet_q
64
+ rubygems_version: 1.5.1
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: The Q Method of Implementing TELNET Option Negotiation (RFC 1143)
68
+ test_files:
69
+ - test/base_test.rb
70
+ - test/socket_test.rb
71
+ - test/test_helper.rb