telnet_q 0.1

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.
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