sprsquish-blather 0.1 → 0.2.3

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 (73) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +110 -0
  4. data/examples/drb_client.rb +5 -0
  5. data/examples/echo.rb +18 -0
  6. data/ext/extconf.rb +65 -0
  7. data/ext/push_parser.c +231 -0
  8. data/lib/blather/client.rb +219 -44
  9. data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
  10. data/lib/blather/core_ext/libxml.rb +28 -0
  11. data/lib/blather/errors/sasl_error.rb +87 -0
  12. data/lib/blather/errors/stanza_error.rb +262 -0
  13. data/lib/blather/errors/stream_error.rb +253 -0
  14. data/lib/blather/errors.rb +48 -0
  15. data/lib/blather/{core/jid.rb → jid.rb} +15 -26
  16. data/lib/blather/{core/roster.rb → roster.rb} +22 -0
  17. data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
  18. data/lib/blather/stanza/iq/disco.rb +11 -0
  19. data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
  20. data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
  21. data/lib/blather/stanza/iq/query.rb +51 -0
  22. data/lib/blather/stanza/iq/roster.rb +90 -0
  23. data/lib/blather/stanza/iq.rb +38 -0
  24. data/lib/blather/stanza/message.rb +58 -0
  25. data/lib/blather/stanza/presence/status.rb +78 -0
  26. data/lib/blather/stanza/presence/subscription.rb +72 -0
  27. data/lib/blather/stanza/presence.rb +45 -0
  28. data/lib/blather/stanza.rb +101 -0
  29. data/lib/blather/stream/client.rb +26 -0
  30. data/lib/blather/stream/component.rb +34 -0
  31. data/lib/blather/stream/parser.rb +70 -0
  32. data/lib/blather/stream/resource.rb +48 -0
  33. data/lib/blather/stream/sasl.rb +173 -0
  34. data/lib/blather/stream/session.rb +36 -0
  35. data/lib/blather/stream/stream_handler.rb +39 -0
  36. data/lib/blather/stream/tls.rb +33 -0
  37. data/lib/blather/stream.rb +249 -0
  38. data/lib/blather/xmpp_node.rb +199 -0
  39. data/lib/blather.rb +40 -41
  40. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  41. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  42. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  43. data/spec/blather/errors/stream_error_spec.rb +114 -0
  44. data/spec/blather/errors_spec.rb +40 -0
  45. data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
  46. data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
  47. data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
  48. data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
  49. data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
  50. data/spec/blather/stanza/iq/query_spec.rb +34 -0
  51. data/spec/blather/stanza/iq/roster_spec.rb +123 -0
  52. data/spec/blather/stanza/iq_spec.rb +40 -0
  53. data/spec/blather/stanza/message_spec.rb +52 -0
  54. data/spec/blather/stanza/presence/status_spec.rb +102 -0
  55. data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
  56. data/spec/blather/stanza/presence_spec.rb +53 -0
  57. data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
  58. data/spec/blather/stream/client_spec.rb +787 -0
  59. data/spec/blather/stream/component_spec.rb +86 -0
  60. data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
  61. data/spec/build_safe.rb +20 -0
  62. data/spec/spec_helper.rb +7 -17
  63. metadata +79 -59
  64. data/CHANGELOG +0 -1
  65. data/blather.gemspec +0 -73
  66. data/lib/blather/callback.rb +0 -24
  67. data/lib/blather/core/errors.rb +0 -24
  68. data/lib/blather/core/stanza.rb +0 -90
  69. data/lib/blather/core/stream.rb +0 -179
  70. data/lib/blather/core/xmpp_node.rb +0 -95
  71. data/lib/blather/extensions/last_activity.rb +0 -57
  72. data/lib/blather/extensions/version.rb +0 -85
  73. data/spec/blather/core/stream_spec.rb +0 -263
@@ -0,0 +1,45 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ ##
5
+ # Base Presence stanza
6
+ class Presence < Stanza
7
+ VALID_TYPES = [:unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe, :error]
8
+
9
+ register :presence
10
+
11
+ ##
12
+ # Creates a class based on the presence type
13
+ # either a Status or Subscription object is created based
14
+ # on the type attribute.
15
+ # If neither is found it instantiates a Presence object
16
+ def self.import(node)
17
+ klass = case node['type']
18
+ when nil, 'unavailable' then Status
19
+ when /subscribe/ then Subscription
20
+ else self
21
+ end
22
+ klass.new.inherit(node)
23
+ end
24
+
25
+ ##
26
+ # Ensure element_name is "presence" for all subclasses
27
+ def initialize
28
+ super :presence
29
+ end
30
+
31
+ VALID_TYPES.each do |valid_type|
32
+ define_method("#{valid_type}?") { self.type == valid_type }
33
+ end
34
+
35
+ ##
36
+ # Ensures type is one of :unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe or :error
37
+ def type=(type)
38
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
39
+ super
40
+ end
41
+
42
+ end
43
+
44
+ end #Stanza
45
+ end
@@ -0,0 +1,101 @@
1
+ module Blather
2
+ ##
3
+ # Base XMPP Stanza
4
+ class Stanza < XMPPNode
5
+ @@last_id = 0
6
+ @@handler_list = []
7
+
8
+ class_inheritable_array :handler_heirarchy
9
+
10
+ ##
11
+ # Registers a callback onto the callback heirarchy stack
12
+ #
13
+ # Thanks to help from ActiveSupport every class
14
+ # that inherits Stanza can register a callback for itself
15
+ # which is added to a list and iterated over when looking for
16
+ # a callback to use
17
+ def self.register(type, name = nil, ns = nil)
18
+ @@handler_list << type
19
+ self.handler_heirarchy ||= []
20
+ self.handler_heirarchy.unshift type
21
+
22
+ name = name || self.name || type
23
+ super name, ns
24
+ end
25
+
26
+ def self.handler_list
27
+ @@handler_list
28
+ end
29
+
30
+ ##
31
+ # Helper method that creates a unique ID for stanzas
32
+ def self.next_id
33
+ @@last_id += 1
34
+ 'blather%04x' % @@last_id
35
+ end
36
+
37
+ ##
38
+ # Creates a new stanza with the same name as the node
39
+ # then inherits all the node's attributes and properties
40
+ def self.import(node)
41
+ self.new(node.element_name).inherit(node)
42
+ end
43
+
44
+ ##
45
+ # Automatically set the stanza's ID
46
+ # and attach it to a document so XPath searching works
47
+ def initialize(name = nil)
48
+ super
49
+ XML::Document.new.root = self
50
+ self.name = name.to_s if name
51
+ self.id = self.class.next_id
52
+ end
53
+
54
+ ##
55
+ # Helper method to ask the object if it's an error
56
+ def error?
57
+ self.type == :error
58
+ end
59
+
60
+ ##
61
+ # Copies itself then swaps from and to
62
+ # then returns the new stanza
63
+ def reply
64
+ self.copy(true).reply!
65
+ end
66
+
67
+ ##
68
+ # Swaps from and to
69
+ def reply!
70
+ self.to, self.from = self.from, self.to
71
+ self
72
+ end
73
+
74
+ attribute_accessor :id, :to_sym => false
75
+
76
+ attribute_writer :to, :from
77
+
78
+ ##
79
+ # returns:: JID created from the "to" value of the stanza
80
+ def to
81
+ JID.new(attributes[:to]) if attributes[:to]
82
+ end
83
+
84
+ ##
85
+ # returns:: JID created from the "from" value of the stanza
86
+ def from
87
+ JID.new(attributes[:from]) if attributes[:from]
88
+ end
89
+
90
+ attribute_accessor :type
91
+
92
+ ##
93
+ # Transform the stanza into a stanza error
94
+ # <tt>err_name_or_class</tt> can be the name of the error or the error class to use
95
+ # <tt>type</tt>, <tt>text</tt>, <tt>extras</tt> are the same as for StanzaError#new
96
+ def as_error(err_name_or_class, type, text = nil, extras = [])
97
+ klass = (err_name_or_class.is_a?(Class) ? err_name_or_class : StanzaError.class_from_registration(err_name_or_class))
98
+ klass.new self, type, text, extras
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,26 @@
1
+ module Blather
2
+ class Stream
3
+
4
+ class Client < Stream
5
+ LANG = 'en'
6
+ VERSION = '1.0'
7
+ NAMESPACE = 'jabber:client'
8
+
9
+ protected
10
+ def start
11
+ @parser = Parser.new self
12
+ start_stream = <<-STREAM
13
+ <stream:stream
14
+ to='#{@to}'
15
+ xmlns='#{NAMESPACE}'
16
+ xmlns:stream='http://etherx.jabber.org/streams'
17
+ version='#{VERSION}'
18
+ xml:lang='#{LANG}'
19
+ >
20
+ STREAM
21
+ send start_stream.gsub(/\s+/, ' ')
22
+ end
23
+ end #Client
24
+
25
+ end #Stream
26
+ end #Blather
@@ -0,0 +1,34 @@
1
+ module Blather
2
+ class Stream
3
+
4
+ class Component < Stream
5
+ NAMESPACE = 'jabber:component:accept'
6
+
7
+ def receive(node) # :nodoc:
8
+ if node.element_name == 'handshake'
9
+ @client.stream_started(self)
10
+ else
11
+ super
12
+ end
13
+
14
+ if node.element_name == 'stream:stream'
15
+ send("<handshake>#{Digest::SHA1.hexdigest(@node['id']+@pass)}</handshake>")
16
+ end
17
+ end
18
+
19
+ protected
20
+ def start
21
+ @parser = Parser.new self
22
+ start_stream = <<-STREAM
23
+ <stream:stream
24
+ to='#{@jid}'
25
+ xmlns='#{NAMESPACE}'
26
+ xmlns:stream='http://etherx.jabber.org/streams'
27
+ >
28
+ STREAM
29
+ send start_stream.gsub(/\s+/, ' ')
30
+ end
31
+ end #Client
32
+
33
+ end #Stream
34
+ end #Blather
@@ -0,0 +1,70 @@
1
+ module Blather # :nodoc:
2
+ class Stream # :nodoc:
3
+
4
+ class Parser # :nodoc:
5
+ STREAM_REGEX = %r{(/)?stream:stream}.freeze
6
+ ERROR_REGEX = /^<(stream:[a-z]+)/.freeze
7
+
8
+ @@debug = !false
9
+ def self.debug; @@debug; end
10
+ def self.debug=(debug); @@debug = debug; end
11
+
12
+ include XML::SaxParser::Callbacks
13
+
14
+ def initialize(receiver)
15
+ @receiver = receiver
16
+ @current = nil
17
+ @parser = XML::SaxPushParser.new self
18
+ end
19
+
20
+ def receive_data(string)
21
+ LOG.debug "PARSING: (#{string})" if @@debug
22
+ @parser.receive string
23
+ end
24
+
25
+ def on_start_element_ns(elem, attrs, prefix, uri, namespaces)
26
+ LOG.debug "START ELEM: (#{{:elem => elem, :attrs => attrs, :prefix => prefix, :uri => uri, :ns => namespaces}.inspect})" if @@debug
27
+ elem = "#{"#{prefix}:" if prefix}#{elem}"
28
+ e = XMPPNode.new elem
29
+ XML::Namespace.new(e, prefix, uri)
30
+ attrs.each { |k,v| e.attributes[k] = v if k }
31
+
32
+ if elem == 'stream:stream'
33
+ @receiver.receive e
34
+
35
+ elsif !@receiver.stopped?
36
+ @current << e if @current
37
+ @current = e
38
+
39
+ end
40
+ end
41
+
42
+ def on_characters(chars = '')
43
+ LOG.debug "CHARS: #{chars}" if @@debug
44
+ @current << XML::Node.new_text(chars) if @current
45
+ end
46
+
47
+ def on_end_element_ns(elem, prefix, uri)
48
+ LOG.debug "END ELEM: #{{:elem => elem, :prefix => prefix, :uri => uri}.inspect}" if @@debug
49
+
50
+ if !@current && "#{prefix}:#{elem}" =~ STREAM_REGEX
51
+ @receiver.receive XMPPNode.new('stream:end')
52
+
53
+ elsif @current.parent?
54
+ @current = @current.parent
55
+
56
+ else
57
+ c, @current = @current, nil
58
+ XML::Document.new.root = c
59
+ @receiver.receive c
60
+
61
+ end
62
+ end
63
+
64
+ def on_error(msg)
65
+ raise ParseError.new(msg.to_s)
66
+ end
67
+ end #Parser
68
+
69
+ end #Stream
70
+ end #Blather
@@ -0,0 +1,48 @@
1
+ module Blather # :nodoc:
2
+ class Stream # :nodoc:
3
+
4
+ class Resource < StreamHandler # :nodoc:
5
+ def initialize(stream, jid)
6
+ super stream
7
+ @jid = jid
8
+ end
9
+
10
+ private
11
+ ##
12
+ # Respond to the bind request
13
+ # If @jid has a resource set already request it from the server
14
+ def bind
15
+ response = Stanza::Iq.new :set
16
+ @id = response.id
17
+
18
+ binder = XMPPNode.new('bind')
19
+ binder.namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
20
+
21
+ binder << XMPPNode.new('resource', @jid.resource) if @jid.resource
22
+
23
+ response << binder
24
+ @stream.send response
25
+ end
26
+
27
+ ##
28
+ # Process the result from the server
29
+ # Sets the sends the JID (now bound to a resource)
30
+ # back to the stream
31
+ def result
32
+ LOG.debug "RESOURE NODE #{@node}"
33
+ # ensure this is a response to our original request
34
+ if @id == @node['id']
35
+ @jid = JID.new @node.find_first('bind/jid').content
36
+ success @jid
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Server returned an error
42
+ def error
43
+ failure StanzaError.import(@node)
44
+ end
45
+ end #Resource
46
+
47
+ end #Stream
48
+ end #Blather
@@ -0,0 +1,173 @@
1
+ module Blather # :nodoc:
2
+ class Stream # :nodoc:
3
+
4
+ class SASL < StreamHandler # :nodoc:
5
+ class UnknownMechanism < BlatherError
6
+ handler_heirarchy ||= []
7
+ handler_heirarchy << :unknown_mechanism
8
+ end
9
+
10
+ SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
11
+
12
+ def initialize(stream, jid, pass = nil)
13
+ super stream
14
+ @jid = jid
15
+ @pass = pass
16
+ @mechanism_idx = 0
17
+ @mechanisms = []
18
+ end
19
+
20
+ def set_mechanism
21
+ mod = case (mechanism = @mechanisms[@mechanism_idx].content)
22
+ when 'DIGEST-MD5' then DigestMD5
23
+ when 'PLAIN' then Plain
24
+ when 'ANONYMOUS' then Anonymous
25
+ else
26
+ # Send a failure node and kill the stream
27
+ @stream.send "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><invalid-mechanism/></failure>"
28
+ @failure.call UnknownMechanism.new("Unknown SASL mechanism (#{mechanism})")
29
+ return false
30
+ end
31
+
32
+ extend mod
33
+ true
34
+ end
35
+
36
+ ##
37
+ # Handle incoming nodes
38
+ # Cycle through possible mechanisms until we either
39
+ # run out of them or none work
40
+ def handle(node)
41
+ if node.element_name == 'failure'
42
+ if @mechanisms[@mechanism_idx += 1]
43
+ set_mechanism
44
+ authenticate
45
+ else
46
+ failure node
47
+ end
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ protected
54
+ def failure(node = nil)
55
+ @failure.call SASLError.import(node)
56
+ end
57
+
58
+ ##
59
+ # Base64 Encoder
60
+ def b64(str)
61
+ [str].pack('m').gsub(/\s/,'')
62
+ end
63
+
64
+ ##
65
+ # Builds a standard auth node
66
+ def auth_node(mechanism, content = nil)
67
+ node = XMPPNode.new 'auth', content
68
+ node['xmlns'] = SASL_NS
69
+ node['mechanism'] = mechanism
70
+ node
71
+ end
72
+
73
+ ##
74
+ # Respond to the <mechanisms> node sent by the server
75
+ def mechanisms
76
+ @mechanisms = @node.children
77
+ authenticate if set_mechanism
78
+ end
79
+
80
+ ##
81
+ # Digest MD5 authentication
82
+ module DigestMD5 # :nodoc:
83
+ ##
84
+ # Lets the server know we're going to try DigestMD5 authentication
85
+ def authenticate
86
+ @stream.send auth_node('DIGEST-MD5')
87
+ end
88
+
89
+ ##
90
+ # Receive the challenge command.
91
+ def challenge
92
+ decode_challenge
93
+ respond
94
+ end
95
+
96
+ private
97
+ ##
98
+ # Decodes digest strings 'foo=bar,baz="faz"'
99
+ # into {'foo' => 'bar', 'baz' => 'faz'}
100
+ def decode_challenge
101
+ text = @node.content.unpack('m').first
102
+ res = {}
103
+
104
+ text.split(',').each do |statement|
105
+ key, value = statement.split('=')
106
+ res[key] = value.delete('"') unless key.empty?
107
+ end
108
+ LOG.debug "CHALLENGE DECODE: #{res.inspect}"
109
+
110
+ @nonce ||= res['nonce']
111
+ @realm ||= res['realm']
112
+ end
113
+
114
+ ##
115
+ # Builds the properly encoded challenge response
116
+ def generate_response
117
+ a1 = "#{d("#{@response[:username]}:#{@response[:realm]}:#{@pass}")}:#{@response[:nonce]}:#{@response[:cnonce]}"
118
+ a2 = "AUTHENTICATE:#{@response[:'digest-uri']}"
119
+ h("#{h(a1)}:#{@response[:nonce]}:#{@response[:nc]}:#{@response[:cnonce]}:#{@response[:qop]}:#{h(a2)}")
120
+ end
121
+
122
+ ##
123
+ # Send challenge response
124
+ def respond
125
+ node = XMPPNode.new 'response'
126
+ node['xmlns'] = SASL_NS
127
+
128
+ unless @initial_response_sent
129
+ @initial_response_sent = true
130
+ @response = {
131
+ :nonce => @nonce,
132
+ :charset => 'utf-8',
133
+ :username => @jid.node,
134
+ :realm => @realm || @jid.domain,
135
+ :cnonce => h(Time.new.to_f.to_s),
136
+ :nc => '00000001',
137
+ :qop => 'auth',
138
+ :'digest-uri' => "xmpp/#{@jid.domain}",
139
+ }
140
+ @response[:response] = generate_response
141
+ @response.each { |k,v| @response[k] = "\"#{v}\"" unless [:nc, :qop, :response, :charset].include?(k) }
142
+
143
+ LOG.debug "CHALLENGE RESPOSNE: #{@response.inspect}"
144
+ LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
145
+
146
+ # order is to simplify testing
147
+ # Ruby 1.9 eliminates the need for this with ordered hashes
148
+ order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri', :response]
149
+ node.content = b64(order.map { |k| v = @response[k]; "#{k}=#{v}" } * ',')
150
+ end
151
+
152
+ @stream.send node
153
+ end
154
+
155
+ def d(s); Digest::MD5.digest(s); end
156
+ def h(s); Digest::MD5.hexdigest(s); end
157
+ end #DigestMD5
158
+
159
+ module Plain # :nodoc:
160
+ def authenticate
161
+ @stream.send auth_node('PLAIN', b64("#{@jid.stripped}\x00#{@jid.node}\x00#{@pass}"))
162
+ end
163
+ end #Plain
164
+
165
+ module Anonymous # :nodoc:
166
+ def authenticate
167
+ @stream.send auth_node('ANONYMOUS', b64(@jid.node))
168
+ end
169
+ end #Anonymous
170
+ end #SASL
171
+
172
+ end #Stream
173
+ end
@@ -0,0 +1,36 @@
1
+ module Blather # :nodoc:
2
+ class Stream # :nodoc:
3
+
4
+ class Session < StreamHandler # :nodoc:
5
+ def initialize(stream, to)
6
+ super stream
7
+ @to = to
8
+ end
9
+
10
+ private
11
+ ##
12
+ # Send a start session command
13
+ def session
14
+ response = Stanza::Iq.new :set
15
+ response.to = @to
16
+ sess = XMPPNode.new 'session'
17
+ sess['xmlns'] = 'urn:ietf:params:xml:ns:xmpp-session'
18
+ response << sess
19
+ @stream.send response
20
+ end
21
+
22
+ ##
23
+ # The server should respond with a <result> node if all is well
24
+ def result
25
+ success
26
+ end
27
+
28
+ ##
29
+ # Server returned an error.
30
+ def error
31
+ failure StanzaError.import(@node)
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ module Blather # :nodoc:
2
+ class Stream # :nodoc:
3
+
4
+ class StreamHandler # :nodoc:
5
+ def on_success(&block); @success = block; end
6
+ def on_failure(&block); @failure = block; end
7
+
8
+ def initialize(stream)
9
+ @stream = stream
10
+ end
11
+
12
+ def handle(node)
13
+ @node = node
14
+ method = @node.element_name == 'iq' ? @node['type'] : @node.element_name
15
+ if self.respond_to?(method, true)
16
+ self.__send__ method
17
+ else
18
+ @failure.call UnknownResponse.new(@node)
19
+ end
20
+ end
21
+
22
+ protected
23
+ ##
24
+ # Handle error response from the server
25
+ def error
26
+ failure
27
+ end
28
+
29
+ def success(message_back = nil)
30
+ @success.call message_back
31
+ end
32
+
33
+ def failure(err = nil)
34
+ @failure.call err
35
+ end
36
+ end #StreamHandler
37
+
38
+ end #Stream
39
+ end #Blather
@@ -0,0 +1,33 @@
1
+ module Blather # :nodoc:
2
+ class Stream # :nodoc:
3
+
4
+ # TLS negotiation invovles 3 node types:
5
+ # * starttls -- Server asking for TLS to be started
6
+ # * proceed -- Server saying it's ready for a TLS connection to be started
7
+ # * failure -- Failed TLS negotiation. Failure results in a closed connection.
8
+ # so there's no message to pass back to the tream
9
+ class TLS < StreamHandler # :nodoc:
10
+ private
11
+ ##
12
+ # After receiving <starttls> from the server send one
13
+ # back to let it know we're ready to start TLS
14
+ def starttls
15
+ @stream.send "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
16
+ end
17
+
18
+ ##
19
+ # Server's ready for TLS, so start it up
20
+ def proceed
21
+ @stream.start_tls
22
+ success
23
+ end
24
+
25
+ ##
26
+ # Negotiations failed
27
+ def failure
28
+ super StreamError::TLSFailure.new
29
+ end
30
+ end #TLS
31
+
32
+ end #Stream
33
+ end #Blather