21 // Version of RFC 3920 that we implement. |
21 // Version of RFC 3920 that we implement. |
22 Version = "1.0" |
22 Version = "1.0" |
23 |
23 |
24 // Various XML namespaces. |
24 // Various XML namespaces. |
25 NsStreams = "urn:ietf:params:xml:ns:xmpp-streams" |
25 NsStreams = "urn:ietf:params:xml:ns:xmpp-streams" |
26 NsStream = "http://etherx.jabber.org/streams" |
26 NsStream = "http://etherx.jabber.org/streams" |
27 NsTLS = "urn:ietf:params:xml:ns:xmpp-tls" |
27 NsTLS = "urn:ietf:params:xml:ns:xmpp-tls" |
28 NsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" |
28 NsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" |
29 NsBind = "urn:ietf:params:xml:ns:xmpp-bind" |
29 NsBind = "urn:ietf:params:xml:ns:xmpp-bind" |
30 NsSession = "urn:ietf:params:xml:ns:xmpp-session" |
30 NsSession = "urn:ietf:params:xml:ns:xmpp-session" |
31 NsRoster = "jabber:iq:roster" |
31 NsRoster = "jabber:iq:roster" |
32 |
32 |
33 // DNS SRV names |
33 // DNS SRV names |
34 serverSrv = "xmpp-server" |
34 serverSrv = "xmpp-server" |
35 clientSrv = "xmpp-client" |
35 clientSrv = "xmpp-client" |
36 ) |
36 ) |
61 }(idCh) |
61 }(idCh) |
62 } |
62 } |
63 |
63 |
64 // Extensions can add stanza filters and/or new XML element types. |
64 // Extensions can add stanza filters and/or new XML element types. |
65 type Extension struct { |
65 type Extension struct { |
66 StanzaHandlers map[string] func(*xml.Name) interface{} |
66 StanzaHandlers map[string]func(*xml.Name) interface{} |
67 Start func(*Client) |
67 Start func(*Client) |
68 } |
68 } |
69 |
69 |
70 // The client in a client-server XMPP connection. |
70 // The client in a client-server XMPP connection. |
71 type Client struct { |
71 type Client struct { |
72 // This client's unique ID. It's unique within the context of |
72 // This client's unique ID. It's unique within the context of |
73 // this process, so if multiple Client objects exist, each |
73 // this process, so if multiple Client objects exist, each |
74 // will be distinguishable by its Uid. |
74 // will be distinguishable by its Uid. |
75 Uid string |
75 Uid string |
76 // This client's JID. This will be updated asynchronously by |
76 // This client's JID. This will be updated asynchronously by |
77 // the time StartSession() returns. |
77 // the time StartSession() returns. |
78 Jid JID |
78 Jid JID |
79 password string |
79 password string |
80 socket net.Conn |
80 socket net.Conn |
81 socketSync sync.WaitGroup |
81 socketSync sync.WaitGroup |
82 saslExpected string |
82 saslExpected string |
83 authDone bool |
83 authDone bool |
84 handlers chan *stanzaHandler |
84 handlers chan *stanzaHandler |
85 inputControl chan int |
85 inputControl chan int |
86 // Incoming XMPP stanzas from the server will be published on |
86 // Incoming XMPP stanzas from the server will be published on |
87 // this channel. Information which is only used by this |
87 // this channel. Information which is only used by this |
88 // library to set up the XMPP stream will not appear here. |
88 // library to set up the XMPP stream will not appear here. |
89 In <-chan Stanza |
89 In <-chan Stanza |
90 // Outgoing XMPP stanzas to the server should be sent to this |
90 // Outgoing XMPP stanzas to the server should be sent to this |
91 // channel. |
91 // channel. |
92 Out chan<- Stanza |
92 Out chan<- Stanza |
93 xmlOut chan<- interface{} |
93 xmlOut chan<- interface{} |
94 // Features advertised by the remote. This will be updated |
94 // Features advertised by the remote. This will be updated |
95 // asynchronously as new features are received throughout the |
95 // asynchronously as new features are received throughout the |
96 // connection process. It should not be updated once |
96 // connection process. It should not be updated once |
97 // StartSession() returns. |
97 // StartSession() returns. |
98 Features *Features |
98 Features *Features |
99 filterOut chan<- <-chan Stanza |
99 filterOut chan<- <-chan Stanza |
100 filterIn <-chan <-chan Stanza |
100 filterIn <-chan <-chan Stanza |
101 } |
101 } |
102 |
102 |
103 // Connect to the appropriate server and authenticate as the given JID |
103 // Connect to the appropriate server and authenticate as the given JID |
104 // with the given password. This function will return as soon as a TCP |
104 // with the given password. This function will return as soon as a TCP |
105 // connection has been established, but before XMPP stream negotiation |
105 // connection has been established, but before XMPP stream negotiation |
106 // has completed. The negotiation will occur asynchronously, and any |
106 // has completed. The negotiation will occur asynchronously, and any |
107 // send operation to Client.Out will block until negotiation (resource |
107 // send operation to Client.Out will block until negotiation (resource |
108 // binding) is complete. |
108 // binding) is complete. |
109 func NewClient(jid *JID, password string, exts []Extension) (*Client, |
109 func NewClient(jid *JID, password string, exts []Extension) (*Client, |
110 os.Error) { |
110 os.Error) { |
111 // Include the mandatory extensions. |
111 // Include the mandatory extensions. |
112 exts = append(exts, rosterExt) |
112 exts = append(exts, rosterExt) |
113 exts = append(exts, bindExt) |
113 exts = append(exts, bindExt) |
114 |
114 |
115 // Resolve the domain in the JID. |
115 // Resolve the domain in the JID. |
138 if tcp == nil { |
138 if tcp == nil { |
139 return nil, err |
139 return nil, err |
140 } |
140 } |
141 |
141 |
142 cl := new(Client) |
142 cl := new(Client) |
143 cl.Uid = <- Id |
143 cl.Uid = <-Id |
144 cl.password = password |
144 cl.password = password |
145 cl.Jid = *jid |
145 cl.Jid = *jid |
146 cl.socket = tcp |
146 cl.socket = tcp |
147 cl.handlers = make(chan *stanzaHandler, 100) |
147 cl.handlers = make(chan *stanzaHandler, 100) |
148 cl.inputControl = make(chan int) |
148 cl.inputControl = make(chan int) |
149 |
149 |
150 extStanza := make(map[string] func(*xml.Name) interface{}) |
150 extStanza := make(map[string]func(*xml.Name) interface{}) |
151 for _, ext := range(exts) { |
151 for _, ext := range exts { |
152 for k, v := range(ext.StanzaHandlers) { |
152 for k, v := range ext.StanzaHandlers { |
153 extStanza[k] = v |
153 extStanza[k] = v |
154 } |
154 } |
155 } |
155 } |
156 |
156 |
157 // Start the transport handler, initially unencrypted. |
157 // Start the transport handler, initially unencrypted. |
270 // immediately after creating the Client in order to start the |
270 // immediately after creating the Client in order to start the |
271 // session, retrieve the roster, and broadcast an initial |
271 // session, retrieve the roster, and broadcast an initial |
272 // presence. The presence can be as simple as a newly-initialized |
272 // presence. The presence can be as simple as a newly-initialized |
273 // Presence struct. See RFC 3921, Section 3. |
273 // Presence struct. See RFC 3921, Section 3. |
274 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error { |
274 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error { |
275 id := <- Id |
275 id := <-Id |
276 iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested: |
276 iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}} |
277 []interface{}{ Generic{XMLName: xml.Name{Space: |
|
278 NsSession, Local: "session"}}}} |
|
279 ch := make(chan os.Error) |
277 ch := make(chan os.Error) |
280 f := func(st Stanza) bool { |
278 f := func(st Stanza) bool { |
281 if st.GetType() == "error" { |
279 if st.GetType() == "error" { |
282 if Log != nil { |
280 if Log != nil { |
283 Log.Err(fmt.Sprintf("Can't start session: %v", |
281 Log.Err(fmt.Sprintf("Can't start session: %v", |