Class Babylon::ClientConnection
In: lib/babylon/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 or port has been specified. In any case, we give priority to the specified host and port.

[Source]

    # File lib/babylon/client_connection.rb, line 21
21:     def self.connect(params, handler = nil)
22:       return super(params, handler) if params["host"] && params["port"]
23: 
24:       begin
25:         srv = []
26:         Resolv::DNS.open { |dns|
27:           # If ruby version is too old and SRV is unknown, this will raise a NameError
28:           # which is caught below
29:           host_from_jid = params["jid"].split("/").first.split("@").last
30:           Babylon.logger.debug {
31:             "RESOLVING: _xmpp-client._tcp.#{host_from_jid} (SRV)"
32:           }
33:           srv = dns.getresources("_xmpp-client._tcp.#{host_from_jid}", Resolv::DNS::Resource::IN::SRV)
34:         }
35:         # Sort SRV records: lowest priority first, highest weight first
36:         srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
37:         # And now, for each record, let's try to connect.
38:         srv.each { |record|
39:           begin
40:             params["host"] = record.target.to_s
41:             params["port"] = Integer(record.port)
42:             super(params, handler)
43:             # Success
44:             break
45:           rescue NotConnected
46:             # Try next SRV record
47:           end
48:         }
49:       rescue NameError
50:         Babylon.logger.debug {
51:           "Resolv::DNS does not support SRV records. Please upgrade to ruby-1.8.3 or later! \n#{$!} : #{$!.backtrace.join("\n")}"
52:         }
53:       end
54:     end

Creates a new ClientConnection and waits for data in the stream

[Source]

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

Namespace of the client

[Source]

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

Builds the stream stanza for this client

[Source]

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

[Validate]