22 |
22 |
23 const ( |
23 const ( |
24 // Version of RFC 3920 that we implement. |
24 // Version of RFC 3920 that we implement. |
25 Version = "1.0" |
25 Version = "1.0" |
26 |
26 |
|
27 // BUG(cjyar) These should be public. |
27 // Various XML namespaces. |
28 // Various XML namespaces. |
28 nsStreams = "urn:ietf:params:xml:ns:xmpp-streams" |
29 nsStreams = "urn:ietf:params:xml:ns:xmpp-streams" |
29 nsStream = "http://etherx.jabber.org/streams" |
30 nsStream = "http://etherx.jabber.org/streams" |
30 nsTLS = "urn:ietf:params:xml:ns:xmpp-tls" |
31 nsTLS = "urn:ietf:params:xml:ns:xmpp-tls" |
31 nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" |
32 nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" |
32 nsBind = "urn:ietf:params:xml:ns:xmpp-bind" |
33 nsBind = "urn:ietf:params:xml:ns:xmpp-bind" |
33 nsSession = "urn:ietf:params:xml:ns:xmpp-session" |
34 nsSession = "urn:ietf:params:xml:ns:xmpp-session" |
|
35 NsRoster = "jabber:iq:roster" |
34 |
36 |
35 // DNS SRV names |
37 // DNS SRV names |
36 serverSrv = "xmpp-server" |
38 serverSrv = "xmpp-server" |
37 clientSrv = "xmpp-client" |
39 clientSrv = "xmpp-client" |
38 |
40 |
51 password string |
53 password string |
52 socket net.Conn |
54 socket net.Conn |
53 socketSync sync.WaitGroup |
55 socketSync sync.WaitGroup |
54 saslExpected string |
56 saslExpected string |
55 authDone bool |
57 authDone bool |
56 idMutex sync.Mutex |
|
57 nextId int64 |
|
58 handlers chan *stanzaHandler |
58 handlers chan *stanzaHandler |
59 inputControl chan int |
59 inputControl chan int |
60 // This channel may be used as a convenient way to generate a |
60 // This channel may be used as a convenient way to generate a |
61 // unique id for an iq, message, or presence stanza. |
61 // unique id for an iq, message, or presence stanza. |
62 Id <-chan string |
62 Id <-chan string |
71 // Features advertised by the remote. This will be updated |
71 // Features advertised by the remote. This will be updated |
72 // asynchronously as new features are received throughout the |
72 // asynchronously as new features are received throughout the |
73 // connection process. It should not be updated once |
73 // connection process. It should not be updated once |
74 // StartSession() returns. |
74 // StartSession() returns. |
75 Features *Features |
75 Features *Features |
|
76 roster map[string] *RosterItem |
76 } |
77 } |
77 var _ io.Closer = &Client{} |
78 var _ io.Closer = &Client{} |
78 |
79 |
79 // Connect to the appropriate server and authenticate as the given JID |
80 // Connect to the appropriate server and authenticate as the given JID |
80 // with the given password. This function will return as soon as a TCP |
81 // with the given password. This function will return as soon as a TCP |
112 |
113 |
113 cl := new(Client) |
114 cl := new(Client) |
114 cl.password = password |
115 cl.password = password |
115 cl.Jid = *jid |
116 cl.Jid = *jid |
116 cl.socket = tcp |
117 cl.socket = tcp |
117 cl.handlers = make(chan *stanzaHandler, 1) |
118 cl.handlers = make(chan *stanzaHandler, 100) |
118 cl.inputControl = make(chan int) |
119 cl.inputControl = make(chan int) |
119 idCh := make(chan string) |
120 idCh := make(chan string) |
120 cl.Id = idCh |
121 cl.Id = idCh |
121 |
122 |
122 // Start the unique id generator. |
123 // Start the unique id generator. |
246 // traffic from the app. |
247 // traffic from the app. |
247 func (cl *Client) bindDone() { |
248 func (cl *Client) bindDone() { |
248 cl.inputControl <- 1 |
249 cl.inputControl <- 1 |
249 } |
250 } |
250 |
251 |
251 // Start an XMPP session. This should typically be done immediately |
252 // Start an XMPP session. A typical XMPP client should call this |
252 // after creating the new Client. Once the session has been |
253 // immediately after creating the Client in order to start the |
253 // established, pr will be sent as an initial presence; nil means |
254 // session, retrieve the roster, and broadcast an initial |
254 // don't send initial presence. The initial presence can be a |
255 // presence. The presence can be as simple as a newly-initialized |
255 // newly-initialized Presence struct. See RFC 3921, Section 3. |
256 // Presence struct. See RFC 3921, Section 3. |
256 func (cl *Client) StartSession(pr *Presence) os.Error { |
257 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error { |
257 id := <- cl.Id |
258 id := <- cl.Id |
258 iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any: |
259 iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any: |
259 &Generic{XMLName: xml.Name{Space: nsSession, Local: |
260 &Generic{XMLName: xml.Name{Space: nsSession, Local: |
260 "session"}}} |
261 "session"}}} |
261 ch := make(chan os.Error) |
262 ch := make(chan os.Error) |
263 if st.XType() == "error" { |
264 if st.XType() == "error" { |
264 log.Printf("Can't start session: %v", st) |
265 log.Printf("Can't start session: %v", st) |
265 ch <- st.XError() |
266 ch <- st.XError() |
266 return false |
267 return false |
267 } |
268 } |
268 if pr != nil { |
|
269 cl.Out <- pr |
|
270 } |
|
271 ch <- nil |
269 ch <- nil |
272 return false |
270 return false |
273 } |
271 } |
274 cl.HandleStanza(id, f) |
272 cl.HandleStanza(id, f) |
275 cl.Out <- iq |
273 cl.Out <- iq |
|
274 |
276 // Now wait until the callback is called. |
275 // Now wait until the callback is called. |
277 return <-ch |
276 if err := <- ch ; err != nil { |
278 } |
277 return err |
|
278 } |
|
279 if getRoster { |
|
280 err := cl.fetchRoster() |
|
281 if err != nil { |
|
282 return err |
|
283 } |
|
284 } |
|
285 if pr != nil { |
|
286 cl.Out <- pr |
|
287 } |
|
288 return nil |
|
289 } |
|
290 |
|
291 // Synchronously fetch this entity's roster from the server and cache |
|
292 // that information. |
|
293 func (cl *Client) fetchRoster() os.Error { |
|
294 iq := &Iq{From: cl.Jid.String(), Id: <- cl.Id, Type: "get", |
|
295 Query: &RosterQuery{XMLName: xml.Name{Local: "query", |
|
296 Space: NsRoster}}} |
|
297 ch := make(chan os.Error) |
|
298 f := func(st Stanza) bool { |
|
299 iq, ok := st.(*Iq) |
|
300 if !ok { |
|
301 ch <- os.NewError(fmt.Sprintf( |
|
302 "Roster query result not iq: %v", st)) |
|
303 return false |
|
304 } |
|
305 if iq.Type == "error" { |
|
306 ch <- iq.Error |
|
307 return false |
|
308 } |
|
309 q := iq.Query |
|
310 if q == nil { |
|
311 ch <- os.NewError(fmt.Sprintf( |
|
312 "Roster query result nil query: %v", |
|
313 iq)) |
|
314 return false |
|
315 } |
|
316 cl.roster = make(map[string] *RosterItem, len(q.Item)) |
|
317 for _, item := range(q.Item) { |
|
318 cl.roster[item.Jid] = &item |
|
319 } |
|
320 ch <- nil |
|
321 return false |
|
322 } |
|
323 cl.HandleStanza(iq.Id, f) |
|
324 cl.Out <- iq |
|
325 // Wait for f to complete. |
|
326 return <- ch |
|
327 } |
|
328 |
|
329 // BUG(cjyar) The roster isn't actually updated when things change. |
|
330 |
|
331 // Returns the current roster of other entities which this one has a |
|
332 // relationship with. Changes to the roster will be signaled by an |
|
333 // appropriate Iq appearing on Client.In. See RFC 3921, Section 7.4. |
|
334 func (cl *Client) Roster() map[string] *RosterItem { |
|
335 r := make(map[string] *RosterItem) |
|
336 for key, val := range(cl.roster) { |
|
337 r[key] = val |
|
338 } |
|
339 return r |
|
340 } |