OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** @suppress {duplicate} */ | 7 /** @suppress {duplicate} */ |
8 var remoting = remoting || {}; | 8 var remoting = remoting || {}; |
9 | 9 |
10 /** | 10 /** |
11 * A connection to an XMPP server. | 11 * A connection to an XMPP server. |
12 * | 12 * |
13 * TODO(sergeyu): Chrome provides two APIs for TCP sockets: chrome.socket and | |
14 * chrome.sockets.tcp . chrome.socket is deprecated but it's still used here | |
15 * because TLS support in chrome.sockets.tcp is currently broken, see | |
16 * crbug.com/403076 . | |
17 * | |
13 * @constructor | 18 * @constructor |
14 * @implements {remoting.SignalStrategy} | 19 * @implements {remoting.SignalStrategy} |
15 */ | 20 */ |
16 remoting.XmppConnection = function() { | 21 remoting.XmppConnection = function() { |
17 /** @private */ | 22 /** @private */ |
18 this.server_ = ''; | 23 this.server_ = ''; |
19 /** @private */ | 24 /** @private */ |
20 this.port_ = 0; | 25 this.port_ = 0; |
21 /** @private {?function(remoting.SignalStrategy.State):void} */ | 26 /** @private {?function(remoting.SignalStrategy.State):void} */ |
22 this.onStateChangedCallback_ = null; | 27 this.onStateChangedCallback_ = null; |
23 /** @private {?function(Element):void} */ | 28 /** @private {?function(Element):void} */ |
24 this.onIncomingStanzaCallback_ = null; | 29 this.onIncomingStanzaCallback_ = null; |
25 /** @type {?remoting.TcpSocket} @private */ | 30 /** @private */ |
26 this.socket_ = null; | 31 this.socketId_ = -1; |
27 /** @private */ | 32 /** @private */ |
28 this.state_ = remoting.SignalStrategy.State.NOT_CONNECTED; | 33 this.state_ = remoting.SignalStrategy.State.NOT_CONNECTED; |
29 /** @private */ | 34 /** @private */ |
35 this.readPending_ = false; | |
36 /** @private */ | |
30 this.sendPending_ = false; | 37 this.sendPending_ = false; |
31 /** @private */ | 38 /** @private */ |
32 this.startTlsPending_ = false; | 39 this.startTlsPending_ = false; |
33 /** @private {Array<ArrayBuffer>} */ | 40 /** @private {Array<ArrayBuffer>} */ |
34 this.sendQueue_ = []; | 41 this.sendQueue_ = []; |
35 /** @private {remoting.XmppLoginHandler} */ | 42 /** @private {remoting.XmppLoginHandler} */ |
36 this.loginHandler_ = null; | 43 this.loginHandler_ = null; |
37 /** @private {remoting.XmppStreamParser} */ | 44 /** @private {remoting.XmppStreamParser} */ |
38 this.streamParser_ = null; | 45 this.streamParser_ = null; |
39 /** @private */ | 46 /** @private */ |
40 this.jid_ = ''; | 47 this.jid_ = ''; |
41 /** @private */ | 48 /** @private */ |
42 this.error_ = remoting.Error.NONE; | 49 this.error_ = remoting.Error.none(); |
43 }; | 50 }; |
44 | 51 |
45 /** | 52 /** |
46 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback | 53 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback |
47 */ | 54 */ |
48 remoting.XmppConnection.prototype.setStateChangedCallback = function( | 55 remoting.XmppConnection.prototype.setStateChangedCallback = function( |
49 onStateChangedCallback) { | 56 onStateChangedCallback) { |
50 this.onStateChangedCallback_ = onStateChangedCallback; | 57 this.onStateChangedCallback_ = onStateChangedCallback; |
51 }; | 58 }; |
52 | 59 |
53 remoting.XmppConnection.prototype.setSocketForTests = function( | |
54 /** remoting.TcpSocket */ socket) { | |
55 this.socket_ = socket; | |
56 }; | |
57 | |
58 /** | 60 /** |
59 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on | 61 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on |
60 * incoming messages. | 62 * incoming messages. |
61 */ | 63 */ |
62 remoting.XmppConnection.prototype.setIncomingStanzaCallback = | 64 remoting.XmppConnection.prototype.setIncomingStanzaCallback = |
63 function(onIncomingStanzaCallback) { | 65 function(onIncomingStanzaCallback) { |
64 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | 66 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; |
65 }; | 67 }; |
66 | 68 |
67 /** | 69 /** |
68 * @param {string} server | 70 * @param {string} server |
69 * @param {string} username | 71 * @param {string} username |
70 * @param {string} authToken | 72 * @param {string} authToken |
71 */ | 73 */ |
72 remoting.XmppConnection.prototype.connect = | 74 remoting.XmppConnection.prototype.connect = |
73 function(server, username, authToken) { | 75 function(server, username, authToken) { |
74 base.debug.assert(this.state_ == remoting.SignalStrategy.State.NOT_CONNECTED); | 76 base.debug.assert(this.state_ == remoting.SignalStrategy.State.NOT_CONNECTED); |
75 base.debug.assert(this.onStateChangedCallback_ != null); | 77 base.debug.assert(this.onStateChangedCallback_ != null); |
76 | 78 |
77 this.error_ = remoting.Error.NONE; | 79 this.error_ = remoting.Error.none(); |
78 var hostnameAndPort = server.split(':', 2); | 80 var hostnameAndPort = server.split(':', 2); |
79 this.server_ = hostnameAndPort[0]; | 81 this.server_ = hostnameAndPort[0]; |
80 this.port_ = | 82 this.port_ = |
81 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; | 83 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; |
82 | 84 |
83 // The server name is passed as to attribute in the <stream>. When connecting | 85 // The server name is passed as to attribute in the <stream>. When connecting |
84 // to talk.google.com it affects the certificate the server will use for TLS: | 86 // to talk.google.com it affects the certificate the server will use for TLS: |
85 // talk.google.com uses gmail certificate when specified server is gmail.com | 87 // talk.google.com uses gmail certificate when specified server is gmail.com |
86 // or googlemail.com and google.com cert otherwise. In the same time it | 88 // or googlemail.com and google.com cert otherwise. In the same time it |
87 // doesn't accept talk.google.com as target server. Here we use google.com | 89 // doesn't accept talk.google.com as target server. Here we use google.com |
88 // server name when authenticating to talk.google.com. This ensures that the | 90 // server name when authenticating to talk.google.com. This ensures that the |
89 // server will use google.com cert which will be accepted by the TLS | 91 // server will use google.com cert which will be accepted by the TLS |
90 // implementation in Chrome (TLS API doesn't allow specifying domain other | 92 // implementation in Chrome (TLS API doesn't allow specifying domain other |
91 // than the one that was passed to connect()). | 93 // than the one that was passed to connect()). |
92 var xmppServer = this.server_; | 94 var xmppServer = this.server_; |
93 if (xmppServer == 'talk.google.com') | 95 if (xmppServer == 'talk.google.com') |
94 xmppServer = 'google.com'; | 96 xmppServer = 'google.com'; |
95 | 97 |
96 // <starttls> handshake before starting TLS is not needed when connecting on | 98 // <starttls> handshake before starting TLS is not needed when connecting on |
97 // the HTTPS port. | 99 // the HTTPS port. |
98 var needHandshakeBeforeTls = this.port_ != 443; | 100 var needHandshakeBeforeTls = this.port_ != 443; |
99 | 101 |
100 /** @type {remoting.XmppLoginHandler} */ | 102 /** @type {remoting.XmppLoginHandler} */ |
101 this.loginHandler_ = new remoting.XmppLoginHandler( | 103 this.loginHandler_ = new remoting.XmppLoginHandler( |
102 xmppServer, username, authToken, needHandshakeBeforeTls, | 104 xmppServer, username, authToken, needHandshakeBeforeTls, |
103 this.sendString_.bind(this), this.startTls_.bind(this), | 105 this.sendString_.bind(this), this.startTls_.bind(this), |
104 this.onHandshakeDone_.bind(this), this.onError_.bind(this)); | 106 this.onHandshakeDone_.bind(this), this.onError_.bind(this)); |
107 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this)); | |
105 this.setState_(remoting.SignalStrategy.State.CONNECTING); | 108 this.setState_(remoting.SignalStrategy.State.CONNECTING); |
106 | |
107 if (!this.socket_) { | |
108 this.socket_ = new remoting.TcpSocket(); | |
109 } | |
110 var that = this; | |
111 this.socket_.connect(this.server_, this.port_) | |
112 .then(this.onSocketConnected_.bind(this)) | |
113 .catch(function(error) { | |
114 that.onError_(remoting.Error.NETWORK_FAILURE, | |
115 'Failed to connect to ' + that.server_ + ': ' + error); | |
116 }); | |
117 }; | 109 }; |
118 | 110 |
119 /** @param {string} message */ | 111 /** @param {string} message */ |
120 remoting.XmppConnection.prototype.sendMessage = function(message) { | 112 remoting.XmppConnection.prototype.sendMessage = function(message) { |
121 base.debug.assert(this.state_ == remoting.SignalStrategy.State.CONNECTED); | 113 base.debug.assert(this.state_ == remoting.SignalStrategy.State.CONNECTED); |
122 this.sendString_(message); | 114 this.sendString_(message); |
123 }; | 115 }; |
124 | 116 |
125 /** | 117 /** |
126 * @param {remoting.LogToServer} logToServer The LogToServer instance for the | 118 * @param {remoting.LogToServer} logToServer The LogToServer instance for the |
(...skipping 17 matching lines...) Expand all Loading... | |
144 remoting.XmppConnection.prototype.getJid = function() { | 136 remoting.XmppConnection.prototype.getJid = function() { |
145 return this.jid_; | 137 return this.jid_; |
146 }; | 138 }; |
147 | 139 |
148 /** @return {remoting.SignalStrategy.Type} The signal strategy type. */ | 140 /** @return {remoting.SignalStrategy.Type} The signal strategy type. */ |
149 remoting.XmppConnection.prototype.getType = function() { | 141 remoting.XmppConnection.prototype.getType = function() { |
150 return remoting.SignalStrategy.Type.XMPP; | 142 return remoting.SignalStrategy.Type.XMPP; |
151 }; | 143 }; |
152 | 144 |
153 remoting.XmppConnection.prototype.dispose = function() { | 145 remoting.XmppConnection.prototype.dispose = function() { |
154 base.dispose(this.socket_); | 146 this.closeSocket_(); |
155 this.socket_ = null; | |
156 this.setState_(remoting.SignalStrategy.State.CLOSED); | 147 this.setState_(remoting.SignalStrategy.State.CLOSED); |
157 }; | 148 }; |
158 | 149 |
159 /** @private */ | 150 /** |
160 remoting.XmppConnection.prototype.onSocketConnected_ = function() { | 151 * @param {chrome.socket.CreateInfo} createInfo |
152 * @private | |
153 */ | |
154 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) { | |
161 // Check if connection was destroyed. | 155 // Check if connection was destroyed. |
162 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) { | 156 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) { |
163 return; | 157 return; |
164 } | 158 } |
165 | 159 |
166 this.setState_(remoting.SignalStrategy.State.HANDSHAKE); | 160 this.setState_(remoting.SignalStrategy.State.HANDSHAKE); |
167 this.loginHandler_.start(); | 161 this.loginHandler_.start(); |
168 | 162 |
169 if (!this.startTlsPending_) { | 163 if (!this.startTlsPending_) { |
170 this.socket_.startReceiving(this.onReceive_.bind(this), | 164 this.tryRead_(); |
171 this.onReceiveError_.bind(this)); | |
172 } | 165 } |
173 }; | 166 }; |
174 | 167 |
175 /** | 168 /** |
176 * @param {ArrayBuffer} data | |
177 * @private | 169 * @private |
178 */ | 170 */ |
179 remoting.XmppConnection.prototype.onReceive_ = function(data) { | 171 remoting.XmppConnection.prototype.tryRead_ = function() { |
172 base.debug.assert(!this.readPending_); | |
180 base.debug.assert(this.state_ == remoting.SignalStrategy.State.HANDSHAKE || | 173 base.debug.assert(this.state_ == remoting.SignalStrategy.State.HANDSHAKE || |
181 this.state_ == remoting.SignalStrategy.State.CONNECTED); | 174 this.state_ == remoting.SignalStrategy.State.CONNECTED); |
175 base.debug.assert(!this.startTlsPending_); | |
182 | 176 |
183 if (this.state_ == remoting.SignalStrategy.State.HANDSHAKE) { | 177 this.readPending_ = true; |
184 this.loginHandler_.onDataReceived(data); | 178 chrome.socket.read(this.socketId_, this.onRead_.bind(this)); |
185 } else if (this.state_ == remoting.SignalStrategy.State.CONNECTED) { | |
186 this.streamParser_.appendData(data); | |
187 } | |
188 }; | 179 }; |
189 | 180 |
190 /** | 181 /** |
191 * @param {number} errorCode | 182 * @param {chrome.socket.ReadInfo} readInfo |
192 * @private | 183 * @private |
193 */ | 184 */ |
194 remoting.XmppConnection.prototype.onReceiveError_ = function(errorCode) { | 185 remoting.XmppConnection.prototype.onReceiveError_ = function(errorCode) { |
195 this.onError_(remoting.Error.NETWORK_FAILURE, | 186 this.onError_( |
196 'Failed to receive from XMPP socket: ' + errorCode); | 187 new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), |
188 'Failed to receive from XMPP socket: ' + errorCode); | |
197 }; | 189 }; |
198 | 190 |
199 /** | 191 /** |
200 * @param {string} text | 192 * @param {string} text |
201 * @private | 193 * @private |
202 */ | 194 */ |
203 remoting.XmppConnection.prototype.sendString_ = function(text) { | 195 remoting.XmppConnection.prototype.sendString_ = function(text) { |
204 this.sendBuffer_(base.encodeUtf8(text)); | 196 this.sendBuffer_(base.encodeUtf8(text)); |
205 }; | 197 }; |
206 | 198 |
207 /** | 199 /** |
208 * @param {ArrayBuffer} data | 200 * @param {ArrayBuffer} data |
209 * @private | 201 * @private |
210 */ | 202 */ |
211 remoting.XmppConnection.prototype.sendBuffer_ = function(data) { | 203 remoting.XmppConnection.prototype.sendBuffer_ = function(data) { |
212 this.sendQueue_.push(data); | 204 this.sendQueue_.push(data); |
213 this.flushSendQueue_(); | 205 this.flushSendQueue_(); |
214 }; | 206 }; |
215 | 207 |
216 /** | 208 /** |
217 * @private | 209 * @private |
218 */ | 210 */ |
219 remoting.XmppConnection.prototype.flushSendQueue_ = function() { | 211 remoting.XmppConnection.prototype.flushSendQueue_ = function() { |
220 if (this.sendPending_ || this.sendQueue_.length == 0) { | 212 if (this.sendPending_ || this.sendQueue_.length == 0) { |
221 return; | 213 return; |
222 } | 214 } |
223 | 215 |
224 var that = this; | 216 var data = this.sendQueue_[0] |
225 | |
226 this.sendPending_ = true; | 217 this.sendPending_ = true; |
227 this.socket_.send(this.sendQueue_[0]) | 218 chrome.socket.write(this.socketId_, data, this.onWrite_.bind(this)); |
228 .then(function(/** number */ bytesSent) { | |
229 that.sendPending_ = false; | |
230 that.onSent_(bytesSent); | |
231 }) | |
232 .catch(function(/** number */ error) { | |
233 that.sendPending_ = false; | |
234 that.onError_(remoting.Error.NETWORK_FAILURE, | |
235 'TCP write failed with error ' + error); | |
236 }); | |
237 }; | 219 }; |
238 | 220 |
239 /** | 221 /** |
240 * @param {number} bytesSent | 222 * @param {chrome.socket.WriteInfo} writeInfo |
241 * @private | 223 * @private |
242 */ | 224 */ |
243 remoting.XmppConnection.prototype.onSent_ = function(bytesSent) { | 225 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) { |
244 // Ignore send() result if the socket was closed. | 226 base.debug.assert(this.sendPending_); |
227 this.sendPending_ = false; | |
228 | |
229 // Ignore write() result if the socket was closed. | |
245 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE && | 230 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE && |
246 this.state_ != remoting.SignalStrategy.State.CONNECTED) { | 231 this.state_ != remoting.SignalStrategy.State.CONNECTED) { |
247 return; | 232 return; |
248 } | 233 } |
249 | 234 |
235 if (writeInfo.bytesWritten < 0) { | |
236 this.onError_(remoting.Error.NETWORK_FAILURE, | |
Jamie
2015/03/12 21:40:11
The change (and the next) looks like it's going in
John Williams
2015/03/13 00:26:35
Ugh. Long story. But you are correct.
| |
237 'TCP write failed with error ' + writeInfo.bytesWritten); | |
238 return; | |
239 } | |
240 | |
250 base.debug.assert(this.sendQueue_.length > 0); | 241 base.debug.assert(this.sendQueue_.length > 0); |
251 | 242 |
252 var data = this.sendQueue_[0]; | 243 var data = this.sendQueue_[0] |
253 base.debug.assert(bytesSent <= data.byteLength); | 244 base.debug.assert(writeInfo.bytesWritten <= data.byteLength); |
254 if (bytesSent == data.byteLength) { | 245 if (writeInfo.bytesWritten == data.byteLength) { |
255 this.sendQueue_.shift(); | 246 this.sendQueue_.shift(); |
256 } else { | 247 } else { |
257 this.sendQueue_[0] = data.slice(data.byteLength - bytesSent); | 248 this.sendQueue_[0] = data.slice(data.byteLength - writeInfo.bytesWritten); |
258 } | 249 } |
259 | 250 |
260 this.flushSendQueue_(); | 251 this.flushSendQueue_(); |
261 }; | 252 }; |
262 | 253 |
263 /** | 254 /** |
264 * @private | 255 * @private |
265 */ | 256 */ |
266 remoting.XmppConnection.prototype.startTls_ = function() { | 257 remoting.XmppConnection.prototype.startTls_ = function() { |
258 base.debug.assert(!this.readPending_); | |
267 base.debug.assert(!this.startTlsPending_); | 259 base.debug.assert(!this.startTlsPending_); |
268 | 260 |
269 var that = this; | 261 this.startTlsPending_ = true; |
262 chrome.socket.secure( | |
263 this.socketId_, {}, this.onTlsStarted_.bind(this)); | |
264 } | |
270 | 265 |
271 this.startTlsPending_ = true; | 266 /** |
272 this.socket_.startTls() | 267 * @param {number} resultCode |
273 .then(function() { | 268 * @private |
274 that.startTlsPending_ = false; | 269 */ |
275 that.socket_.startReceiving(that.onReceive_.bind(that), | 270 remoting.XmppConnection.prototype.onTlsStarted_ = function(resultCode) { |
276 that.onReceiveError_.bind(that)); | 271 base.debug.assert(this.startTlsPending_); |
272 this.startTlsPending_ = false; | |
277 | 273 |
278 that.loginHandler_.onTlsStarted(); | 274 if (resultCode < 0) { |
279 }) | 275 this.onError_(remoting.Error.NETWORK_FAILURE, |
280 .catch(function(/** number */ error) { | 276 'Failed to start TLS: ' + resultCode); |
281 that.startTlsPending_ = false; | 277 return; |
282 that.onError_(remoting.Error.NETWORK_FAILURE, | 278 } |
283 'Failed to start TLS: ' + error); | 279 |
284 }); | 280 this.tryRead_(); |
285 } | 281 this.loginHandler_.onTlsStarted(); |
282 }; | |
286 | 283 |
287 /** | 284 /** |
288 * @param {string} jid | 285 * @param {string} jid |
289 * @param {remoting.XmppStreamParser} streamParser | 286 * @param {remoting.XmppStreamParser} streamParser |
290 * @private | 287 * @private |
291 */ | 288 */ |
292 remoting.XmppConnection.prototype.onHandshakeDone_ = | 289 remoting.XmppConnection.prototype.onHandshakeDone_ = |
293 function(jid, streamParser) { | 290 function(jid, streamParser) { |
294 this.jid_ = jid; | 291 this.jid_ = jid; |
295 this.streamParser_ = streamParser; | 292 this.streamParser_ = streamParser; |
(...skipping 10 matching lines...) Expand all Loading... | |
306 if (this.onIncomingStanzaCallback_) { | 303 if (this.onIncomingStanzaCallback_) { |
307 this.onIncomingStanzaCallback_(stanza); | 304 this.onIncomingStanzaCallback_(stanza); |
308 } | 305 } |
309 }; | 306 }; |
310 | 307 |
311 /** | 308 /** |
312 * @param {string} text | 309 * @param {string} text |
313 * @private | 310 * @private |
314 */ | 311 */ |
315 remoting.XmppConnection.prototype.onParserError_ = function(text) { | 312 remoting.XmppConnection.prototype.onParserError_ = function(text) { |
316 this.onError_(remoting.Error.UNEXPECTED, text); | 313 this.onError_(remoting.Error.unexpected(), text); |
317 } | 314 } |
318 | 315 |
319 /** | 316 /** |
320 * @param {!remoting.Error} error | 317 * @param {!remoting.Error} error |
321 * @param {string} text | 318 * @param {string} text |
322 * @private | 319 * @private |
323 */ | 320 */ |
324 remoting.XmppConnection.prototype.onError_ = function(error, text) { | 321 remoting.XmppConnection.prototype.onError_ = function(error, text) { |
325 console.error(text); | 322 console.error(text); |
326 this.error_ = error; | 323 this.error_ = error; |
327 base.dispose(this.socket_); | 324 this.closeSocket_(); |
328 this.socket_ = null; | |
329 this.setState_(remoting.SignalStrategy.State.FAILED); | 325 this.setState_(remoting.SignalStrategy.State.FAILED); |
330 }; | 326 }; |
331 | 327 |
332 /** | 328 /** |
329 * @private | |
330 */ | |
331 remoting.XmppConnection.prototype.closeSocket_ = function() { | |
332 if (this.socketId_ != -1) { | |
333 chrome.socket.destroy(this.socketId_); | |
334 this.socketId_ = -1; | |
335 } | |
336 }; | |
337 | |
338 /** | |
333 * @param {remoting.SignalStrategy.State} newState | 339 * @param {remoting.SignalStrategy.State} newState |
334 * @private | 340 * @private |
335 */ | 341 */ |
336 remoting.XmppConnection.prototype.setState_ = function(newState) { | 342 remoting.XmppConnection.prototype.setState_ = function(newState) { |
337 if (this.state_ != newState) { | 343 if (this.state_ != newState) { |
338 this.state_ = newState; | 344 this.state_ = newState; |
339 this.onStateChangedCallback_(this.state_); | 345 this.onStateChangedCallback_(this.state_); |
340 } | 346 } |
341 }; | 347 }; |
OLD | NEW |