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 +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +43 -0
- data/Rakefile +8 -0
- data/lib/telnet_q.rb +4 -0
- data/lib/telnet_q/base.rb +186 -0
- data/lib/telnet_q/sm.rb +167 -0
- data/lib/telnet_q/socket_q.rb +57 -0
- data/lib/telnet_q/version.rb +3 -0
- data/telnet_q.gemspec +21 -0
- data/test/base_test.rb +122 -0
- data/test/socket_test.rb +175 -0
- data/test/test_helper.rb +2 -0
- metadata +71 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
data/lib/telnet_q.rb
ADDED
@@ -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
|
data/lib/telnet_q/sm.rb
ADDED
@@ -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
|
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
|
data/test/socket_test.rb
ADDED
@@ -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
|
+
|
data/test/test_helper.rb
ADDED
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
|