42 // syslog.Writer, so the app can control how much time we |
42 // syslog.Writer, so the app can control how much time we |
43 // spend generating log messages, as well as where they go. |
43 // spend generating log messages, as well as where they go. |
44 debug = false |
44 debug = false |
45 ) |
45 ) |
46 |
46 |
|
47 // This channel may be used as a convenient way to generate a unique |
|
48 // id for an iq, message, or presence stanza. |
|
49 var Id <-chan string |
|
50 |
|
51 func init() { |
|
52 // Start the unique id generator. |
|
53 idCh := make(chan string) |
|
54 Id = idCh |
|
55 go func(ch chan<- string) { |
|
56 id := int64(1) |
|
57 for { |
|
58 str := fmt.Sprintf("id_%d", id) |
|
59 ch <- str |
|
60 id++ |
|
61 } |
|
62 }(idCh) |
|
63 } |
|
64 |
47 // The client in a client-server XMPP connection. |
65 // The client in a client-server XMPP connection. |
48 type Client struct { |
66 type Client struct { |
|
67 // This client's unique ID. It's unique within the context of |
|
68 // this process, so if multiple Client objects exist, each |
|
69 // will be distinguishable by its Uid. |
|
70 Uid string |
49 // This client's JID. This will be updated asynchronously by |
71 // This client's JID. This will be updated asynchronously by |
50 // the time StartSession() returns. |
72 // the time StartSession() returns. |
51 Jid JID |
73 Jid JID |
52 password string |
74 password string |
53 socket net.Conn |
75 socket net.Conn |
54 socketSync sync.WaitGroup |
76 socketSync sync.WaitGroup |
55 saslExpected string |
77 saslExpected string |
56 authDone bool |
78 authDone bool |
57 handlers chan *stanzaHandler |
79 handlers chan *stanzaHandler |
58 inputControl chan int |
80 inputControl chan int |
59 // This channel may be used as a convenient way to generate a |
|
60 // unique id for an iq, message, or presence stanza. |
|
61 Id <-chan string |
|
62 // Incoming XMPP stanzas from the server will be published on |
81 // Incoming XMPP stanzas from the server will be published on |
63 // this channel. Information which is only used by this |
82 // this channel. Information which is only used by this |
64 // library to set up the XMPP stream will not appear here. |
83 // library to set up the XMPP stream will not appear here. |
65 In <-chan Stanza |
84 In <-chan Stanza |
66 // Outgoing XMPP stanzas to the server should be sent to this |
85 // Outgoing XMPP stanzas to the server should be sent to this |
70 // Features advertised by the remote. This will be updated |
89 // Features advertised by the remote. This will be updated |
71 // asynchronously as new features are received throughout the |
90 // asynchronously as new features are received throughout the |
72 // connection process. It should not be updated once |
91 // connection process. It should not be updated once |
73 // StartSession() returns. |
92 // StartSession() returns. |
74 Features *Features |
93 Features *Features |
75 roster map[string] *RosterItem |
|
76 filterOut chan<- <-chan Stanza |
94 filterOut chan<- <-chan Stanza |
77 filterIn <-chan <-chan Stanza |
95 filterIn <-chan <-chan Stanza |
78 } |
96 } |
79 var _ io.Closer = &Client{} |
97 var _ io.Closer = &Client{} |
|
98 |
|
99 // BUG(cjyar) Replace extStanza with a generalized extension interface |
|
100 // that handles starting filters, registering extended stanzes, and |
|
101 // anything else an extension has to do. |
80 |
102 |
81 // Connect to the appropriate server and authenticate as the given JID |
103 // Connect to the appropriate server and authenticate as the given JID |
82 // 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 |
83 // connection has been established, but before XMPP stream negotiation |
105 // connection has been established, but before XMPP stream negotiation |
84 // has completed. The negotiation will occur asynchronously, and any |
106 // has completed. The negotiation will occur asynchronously, and any |
112 if tcp == nil { |
134 if tcp == nil { |
113 return nil, err |
135 return nil, err |
114 } |
136 } |
115 |
137 |
116 cl := new(Client) |
138 cl := new(Client) |
|
139 cl.Uid = <- Id |
117 cl.password = password |
140 cl.password = password |
118 cl.Jid = *jid |
141 cl.Jid = *jid |
119 cl.socket = tcp |
142 cl.socket = tcp |
120 cl.handlers = make(chan *stanzaHandler, 100) |
143 cl.handlers = make(chan *stanzaHandler, 100) |
121 cl.inputControl = make(chan int) |
144 cl.inputControl = make(chan int) |
122 idCh := make(chan string) |
|
123 cl.Id = idCh |
|
124 |
145 |
125 if extStanza == nil { |
146 if extStanza == nil { |
126 extStanza = make(map[string] func(*xml.Name) interface{}) |
147 extStanza = make(map[string] func(*xml.Name) interface{}) |
127 } |
148 } |
128 extStanza[NsRoster] = newRosterQuery |
149 extStanza[NsRoster] = newRosterQuery |
129 extStanza[NsBind] = newBind |
150 extStanza[NsBind] = newBind |
130 |
|
131 // Start the unique id generator. |
|
132 go makeIds(idCh) |
|
133 |
151 |
134 // Start the transport handler, initially unencrypted. |
152 // Start the transport handler, initially unencrypted. |
135 tlsr, tlsw := cl.startTransport() |
153 tlsr, tlsw := cl.startTransport() |
136 |
154 |
137 // Start the reader and writers that convert to and from XML. |
155 // Start the reader and writers that convert to and from XML. |
144 clOut := cl.startStreamWriter(cl.xmlOut) |
162 clOut := cl.startStreamWriter(cl.xmlOut) |
145 |
163 |
146 // Start the manager for the filters that can modify what the |
164 // Start the manager for the filters that can modify what the |
147 // app sees. |
165 // app sees. |
148 clIn := cl.startFilter(stIn) |
166 clIn := cl.startFilter(stIn) |
149 cl.startRosterFilter() |
167 startRosterFilter(cl) |
150 |
168 |
151 // Initial handshake. |
169 // Initial handshake. |
152 hsOut := &stream{To: jid.Domain, Version: Version} |
170 hsOut := &stream{To: jid.Domain, Version: Version} |
153 cl.xmlOut <- hsOut |
171 cl.xmlOut <- hsOut |
154 |
172 |
257 f2(ch) |
275 f2(ch) |
258 } |
276 } |
259 } |
277 } |
260 } |
278 } |
261 |
279 |
262 func makeIds(ch chan<- string) { |
|
263 id := int64(1) |
|
264 for { |
|
265 str := fmt.Sprintf("id_%d", id) |
|
266 ch <- str |
|
267 id++ |
|
268 } |
|
269 } |
|
270 |
|
271 // bindDone is called when we've finished resource binding (and all |
280 // bindDone is called when we've finished resource binding (and all |
272 // the negotiations that precede it). Now we can start accepting |
281 // the negotiations that precede it). Now we can start accepting |
273 // traffic from the app. |
282 // traffic from the app. |
274 func (cl *Client) bindDone() { |
283 func (cl *Client) bindDone() { |
275 cl.inputControl <- 1 |
284 cl.inputControl <- 1 |
279 // immediately after creating the Client in order to start the |
288 // immediately after creating the Client in order to start the |
280 // session, retrieve the roster, and broadcast an initial |
289 // session, retrieve the roster, and broadcast an initial |
281 // presence. The presence can be as simple as a newly-initialized |
290 // presence. The presence can be as simple as a newly-initialized |
282 // Presence struct. See RFC 3921, Section 3. |
291 // Presence struct. See RFC 3921, Section 3. |
283 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error { |
292 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error { |
284 id := <- cl.Id |
293 id := <- Id |
285 iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any: |
294 iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any: |
286 &Generic{XMLName: xml.Name{Space: NsSession, Local: |
295 &Generic{XMLName: xml.Name{Space: NsSession, Local: |
287 "session"}}} |
296 "session"}}} |
288 ch := make(chan os.Error) |
297 ch := make(chan os.Error) |
289 f := func(st Stanza) bool { |
298 f := func(st Stanza) bool { |