Class Skates::ClientConnection
In: lib/skates/client_connection.rb
Parent: XmppConnection

ClientConnection is in charge of the XMPP connection for a Regular XMPP Client. So far, SASL Plain authenticationonly is supported Upon stanza reception, and depending on the status (connected… etc), this component will handle or forward the stanzas.

Methods

Attributes

binding_iq_id  [R] 
session_iq_id  [R] 

Public Class methods

Connects the ClientConnection based on SRV records for the jid‘s domain, if no host has been provided. It will not resolve if params["host"] is an IP. And it will always use

[Source]

    # File lib/skates/client_connection.rb, line 22
22:     def self.connect(params, handler = nil)
23:       params["host"] ||= params["jid"].split("/").first.split("@").last 
24:       super(params, handler)
25:     end

Creates a new ClientConnection and waits for data in the stream

[Source]

    # File lib/skates/client_connection.rb, line 13
13:     def initialize(params)
14:       super(params)
15:       @state = :wait_for_stream
16:     end

Resolution for clients, based on SRV records

[Source]

    # File lib/skates/client_connection.rb, line 29
29:     def self.resolve(host, &block)
30:       Resolv::DNS.open { |dns|
31:         # If ruby version is too old and SRV is unknown, this will raise a NameError
32:         # which is caught below
33:         Skates.logger.debug {
34:           "RESOLVING: #{srv_for_host(host)} (SRV)"
35:         }
36:         begin
37:           srv = dns.getresources("_xmpp-client._tcp.#{host}", Resolv::DNS::Resource::IN::SRV)
38:           # Sort SRV records: lowest priority first, highest weight first
39:           srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
40:           # And now, for each record, let's try to connect.
41:           srv.each { |record|
42:             ip    = record.target.to_s
43:             port  = Integer(record.port)
44:             break if block.call({"host" => ip, "port" => port}) 
45:           }
46:           block.call(false) # bleh, we couldn't resolve to any valid. Too bad.
47:         rescue NameError
48:           Skates.logger.debug {
49:             "Resolv::DNS does not support SRV records. Please upgrade to ruby-1.8.3 or later! \n#{$!} : #{$!.backtrace.join("\n")}"
50:           }
51:         end
52:       }
53:     end

Public Instance methods

Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream to establish the XMPP connection itself. We use a "tweak" here to send only the starting tag of stream:stream

[Source]

    # File lib/skates/client_connection.rb, line 74
74:     def connection_completed
75:       super
76:       send_xml(stream_stanza)
77:     end

Called upon stanza reception Marked as connected when the client has been SASLed, authenticated, biund to a resource and when the session has been created

[Source]

     # File lib/skates/client_connection.rb, line 82
 82:     def receive_stanza(stanza)
 83:         case @state
 84:         when :connected
 85:           super # Can be dispatched
 86: 
 87:         when :wait_for_stream_authenticated
 88:           if stanza.name == "stream:stream" && stanza.attributes['id']
 89:             @state = :wait_for_bind
 90:           end
 91: 
 92:         when :wait_for_stream
 93:           if stanza.name == "stream:stream" && stanza.attributes['id']
 94:             @state = :wait_for_auth_mechanisms
 95:           end
 96: 
 97:         when :wait_for_auth_mechanisms
 98:           if stanza.name == "stream:features"
 99:             if stanza.at("starttls") # we shall start tls
100:               doc = Nokogiri::XML::Document.new
101:               starttls = Nokogiri::XML::Node.new("starttls", doc)
102:               doc.add_child(starttls)
103:               starttls["xmlns"] = "urn:ietf:params:xml:ns:xmpp-tls"
104:               send_xml(starttls.to_s)
105:               @state = :wait_for_proceed
106:             elsif stanza.at("mechanisms") # tls is ok
107:               if stanza.at("mechanisms").children.map() { |m| m.text }.include? "PLAIN"
108:                 doc = Nokogiri::XML::Document.new
109:                 auth = Nokogiri::XML::Node.new("auth", doc)
110:                 doc.add_child(auth)
111:                 auth['mechanism'] = "PLAIN"
112:                 auth["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"
113:                 auth.content = Base64::encode64([jid, jid.split("@").first, @password].join("\000")).gsub(/\s/, '')
114:                 send_xml(auth.to_s)
115:                 @state = :wait_for_success
116:               end
117:             end
118:           end
119: 
120:         when :wait_for_success
121:           if stanza.name == "success" # Yay! Success
122:             @state = :wait_for_stream_authenticated
123:             @parser.reset
124:             send_xml(stream_stanza)
125:           elsif stanza.name == "failure"
126:             if stanza.at("bad-auth") || stanza.at("not-authorized")
127:               raise AuthenticationError
128:             else
129:             end
130:           else
131:             # Hum Failure...
132:           end
133: 
134:         when :wait_for_bind
135:           if stanza.name == "stream:features"
136:             if stanza.at("bind")
137:               doc = Nokogiri::XML::Document.new
138:               # Let's build the binding_iq
139:               @binding_iq_id = Integer(rand(10000000))
140:               iq = Nokogiri::XML::Node.new("iq", doc)
141:               doc.add_child(iq)
142:               iq["type"] = "set"
143:               iq["id"] = binding_iq_id.to_s
144:               bind = Nokogiri::XML::Node.new("bind", doc)
145:               bind["xmlns"] = "urn:ietf:params:xml:ns:xmpp-bind"
146:               iq.add_child(bind)
147:               resource = Nokogiri::XML::Node.new("resource", doc)
148:               if jid.split("/").size == 2 
149:                 resource.content = (@jid.split("/").last)
150:               else
151:                 resource.content = "skates_client_#{binding_iq_id}"
152:               end
153:               bind.add_child(resource)
154:               send_xml(iq.to_s)
155:               @state = :wait_for_confirmed_binding
156:             end
157:           end
158: 
159:         when :wait_for_confirmed_binding
160:           if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) ==  binding_iq_id
161:             if stanza.at("jid")
162:               @jid = stanza.at("jid").text
163:             end
164:             # And now, we must initiate the session
165:             @session_iq_id = Integer(rand(10000))
166:             doc = Nokogiri::XML::Document.new
167:             iq = Nokogiri::XML::Node.new("iq", doc)
168:             doc.add_child(iq)
169:             iq["type"] = "set"
170:             iq["id"] = session_iq_id.to_s
171:             session = Nokogiri::XML::Node.new("session", doc)
172:             session["xmlns"] = "urn:ietf:params:xml:ns:xmpp-session"
173:             iq.add_child(session)
174:             send_xml(iq.to_s)
175:             @state = :wait_for_confirmed_session
176:           end
177: 
178:         when :wait_for_confirmed_session
179:           if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == session_iq_id
180:             # And now, send a presence!
181:             doc = Nokogiri::XML::Document.new
182:             presence = Nokogiri::XML::Node.new("presence", doc)
183:             send_xml(presence.to_s)
184:             begin
185:               @handler.on_connected(self) if @handler and @handler.respond_to?("on_connected")
186:             rescue
187:               Skates.logger.error {
188:                 "on_connected failed : #{$!}\n#{$!.backtrace.join("\n")}"
189:               }
190:             end
191:             @state = :connected
192:           end
193: 
194:         when :wait_for_proceed
195:           start_tls() # starting TLS
196:           @state = :wait_for_stream
197:           @parser.reset
198:           send_xml stream_stanza
199:         end
200:     end

Namespace of the client

[Source]

     # File lib/skates/client_connection.rb, line 204
204:     def stream_namespace
205:       "jabber:client"
206:     end

Builds the stream stanza for this client

[Source]

    # File lib/skates/client_connection.rb, line 57
57:     def stream_stanza
58:       doc = Nokogiri::XML::Document.new
59:       stream = Nokogiri::XML::Node.new("stream:stream", doc)
60:       doc.add_child(stream)
61:       stream["xmlns"] = stream_namespace
62:       stream["xmlns:stream"] = "http://etherx.jabber.org/streams"
63:       stream["to"] = jid.split("/").first.split("@").last
64:       stream["version"] = "1.0"
65:       paste_content_here = Nokogiri::XML::Node.new("paste_content_here", doc)
66:       stream.add_child(paste_content_here)
67:       doc.to_xml.split('<paste_content_here/>').first
68:     end

[Validate]