OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "jingle/notifier/base/chrome_async_socket.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cstring> | |
9 #include <cstdlib> | |
10 | |
11 #include "base/basictypes.h" | |
12 #include "base/bind.h" | |
13 #include "base/compiler_specific.h" | |
14 #include "base/logging.h" | |
15 #include "base/message_loop.h" | |
16 #include "jingle/notifier/base/resolving_client_socket_factory.h" | |
17 #include "net/base/address_list.h" | |
18 #include "net/base/host_port_pair.h" | |
19 #include "net/base/io_buffer.h" | |
20 #include "net/base/net_util.h" | |
21 #include "net/base/ssl_config_service.h" | |
22 #include "net/socket/client_socket_handle.h" | |
23 #include "net/socket/ssl_client_socket.h" | |
24 #include "net/socket/tcp_client_socket.h" | |
25 #include "talk/base/socketaddress.h" | |
26 | |
27 namespace notifier { | |
28 | |
29 ChromeAsyncSocket::ChromeAsyncSocket( | |
30 ResolvingClientSocketFactory* resolving_client_socket_factory, | |
31 size_t read_buf_size, | |
32 size_t write_buf_size) | |
33 : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), | |
34 resolving_client_socket_factory_(resolving_client_socket_factory), | |
35 state_(STATE_CLOSED), | |
36 error_(ERROR_NONE), | |
37 net_error_(net::OK), | |
38 read_state_(IDLE), | |
39 read_buf_(new net::IOBufferWithSize(read_buf_size)), | |
40 read_start_(0U), | |
41 read_end_(0U), | |
42 write_state_(IDLE), | |
43 write_buf_(new net::IOBufferWithSize(write_buf_size)), | |
44 write_end_(0U) { | |
45 DCHECK(resolving_client_socket_factory_.get()); | |
46 DCHECK_GT(read_buf_size, 0U); | |
47 DCHECK_GT(write_buf_size, 0U); | |
48 } | |
49 | |
50 ChromeAsyncSocket::~ChromeAsyncSocket() {} | |
51 | |
52 ChromeAsyncSocket::State ChromeAsyncSocket::state() { | |
53 return state_; | |
54 } | |
55 | |
56 ChromeAsyncSocket::Error ChromeAsyncSocket::error() { | |
57 return error_; | |
58 } | |
59 | |
60 int ChromeAsyncSocket::GetError() { | |
61 return net_error_; | |
62 } | |
63 | |
64 bool ChromeAsyncSocket::IsOpen() const { | |
65 return (state_ == STATE_OPEN) || (state_ == STATE_TLS_OPEN); | |
66 } | |
67 | |
68 void ChromeAsyncSocket::DoNonNetError(Error error) { | |
69 DCHECK_NE(error, ERROR_NONE); | |
70 DCHECK_NE(error, ERROR_WINSOCK); | |
71 error_ = error; | |
72 net_error_ = net::OK; | |
73 } | |
74 | |
75 void ChromeAsyncSocket::DoNetError(net::Error net_error) { | |
76 error_ = ERROR_WINSOCK; | |
77 net_error_ = net_error; | |
78 } | |
79 | |
80 void ChromeAsyncSocket::DoNetErrorFromStatus(int status) { | |
81 DCHECK_LT(status, net::OK); | |
82 DoNetError(static_cast<net::Error>(status)); | |
83 } | |
84 | |
85 // STATE_CLOSED -> STATE_CONNECTING | |
86 | |
87 bool ChromeAsyncSocket::Connect(const talk_base::SocketAddress& address) { | |
88 if (state_ != STATE_CLOSED) { | |
89 LOG(DFATAL) << "Connect() called on non-closed socket"; | |
90 DoNonNetError(ERROR_WRONGSTATE); | |
91 return false; | |
92 } | |
93 if (address.hostname().empty() || address.port() == 0) { | |
94 DoNonNetError(ERROR_DNS); | |
95 return false; | |
96 } | |
97 | |
98 DCHECK_EQ(state_, buzz::AsyncSocket::STATE_CLOSED); | |
99 DCHECK_EQ(read_state_, IDLE); | |
100 DCHECK_EQ(write_state_, IDLE); | |
101 | |
102 state_ = STATE_CONNECTING; | |
103 | |
104 DCHECK(!weak_ptr_factory_.HasWeakPtrs()); | |
105 weak_ptr_factory_.InvalidateWeakPtrs(); | |
106 | |
107 net::HostPortPair dest_host_port_pair(address.hostname(), address.port()); | |
108 | |
109 transport_socket_.reset( | |
110 resolving_client_socket_factory_->CreateTransportClientSocket( | |
111 dest_host_port_pair)); | |
112 int status = transport_socket_->Connect( | |
113 base::Bind(&ChromeAsyncSocket::ProcessConnectDone, | |
114 weak_ptr_factory_.GetWeakPtr())); | |
115 if (status != net::ERR_IO_PENDING) { | |
116 // We defer execution of ProcessConnectDone instead of calling it | |
117 // directly here as the caller may not expect an error/close to | |
118 // happen here. This is okay, as from the caller's point of view, | |
119 // the connect always happens asynchronously. | |
120 MessageLoop* message_loop = MessageLoop::current(); | |
121 CHECK(message_loop); | |
122 message_loop->PostTask( | |
123 FROM_HERE, | |
124 base::Bind(&ChromeAsyncSocket::ProcessConnectDone, | |
125 weak_ptr_factory_.GetWeakPtr(), status)); | |
126 } | |
127 return true; | |
128 } | |
129 | |
130 // STATE_CONNECTING -> STATE_OPEN | |
131 // read_state_ == IDLE -> read_state_ == POSTED (via PostDoRead()) | |
132 | |
133 void ChromeAsyncSocket::ProcessConnectDone(int status) { | |
134 DCHECK_NE(status, net::ERR_IO_PENDING); | |
135 DCHECK_EQ(read_state_, IDLE); | |
136 DCHECK_EQ(write_state_, IDLE); | |
137 DCHECK_EQ(state_, STATE_CONNECTING); | |
138 if (status != net::OK) { | |
139 DoNetErrorFromStatus(status); | |
140 DoClose(); | |
141 return; | |
142 } | |
143 state_ = STATE_OPEN; | |
144 PostDoRead(); | |
145 // Write buffer should be empty. | |
146 DCHECK_EQ(write_end_, 0U); | |
147 SignalConnected(); | |
148 } | |
149 | |
150 // read_state_ == IDLE -> read_state_ == POSTED | |
151 | |
152 void ChromeAsyncSocket::PostDoRead() { | |
153 DCHECK(IsOpen()); | |
154 DCHECK_EQ(read_state_, IDLE); | |
155 DCHECK_EQ(read_start_, 0U); | |
156 DCHECK_EQ(read_end_, 0U); | |
157 MessageLoop* message_loop = MessageLoop::current(); | |
158 CHECK(message_loop); | |
159 message_loop->PostTask( | |
160 FROM_HERE, | |
161 base::Bind(&ChromeAsyncSocket::DoRead, | |
162 weak_ptr_factory_.GetWeakPtr())); | |
163 read_state_ = POSTED; | |
164 } | |
165 | |
166 // read_state_ == POSTED -> read_state_ == PENDING | |
167 | |
168 void ChromeAsyncSocket::DoRead() { | |
169 DCHECK(IsOpen()); | |
170 DCHECK_EQ(read_state_, POSTED); | |
171 DCHECK_EQ(read_start_, 0U); | |
172 DCHECK_EQ(read_end_, 0U); | |
173 // Once we call Read(), we cannot call StartTls() until the read | |
174 // finishes. This is okay, as StartTls() is called only from a read | |
175 // handler (i.e., after a read finishes and before another read is | |
176 // done). | |
177 int status = | |
178 transport_socket_->Read( | |
179 read_buf_.get(), read_buf_->size(), | |
180 base::Bind(&ChromeAsyncSocket::ProcessReadDone, | |
181 weak_ptr_factory_.GetWeakPtr())); | |
182 read_state_ = PENDING; | |
183 if (status != net::ERR_IO_PENDING) { | |
184 ProcessReadDone(status); | |
185 } | |
186 } | |
187 | |
188 // read_state_ == PENDING -> read_state_ == IDLE | |
189 | |
190 void ChromeAsyncSocket::ProcessReadDone(int status) { | |
191 DCHECK_NE(status, net::ERR_IO_PENDING); | |
192 DCHECK(IsOpen()); | |
193 DCHECK_EQ(read_state_, PENDING); | |
194 DCHECK_EQ(read_start_, 0U); | |
195 DCHECK_EQ(read_end_, 0U); | |
196 read_state_ = IDLE; | |
197 if (status > 0) { | |
198 read_end_ = static_cast<size_t>(status); | |
199 SignalRead(); | |
200 } else if (status == 0) { | |
201 // Other side closed the connection. | |
202 error_ = ERROR_NONE; | |
203 net_error_ = net::OK; | |
204 DoClose(); | |
205 } else { // status < 0 | |
206 DoNetErrorFromStatus(status); | |
207 DoClose(); | |
208 } | |
209 } | |
210 | |
211 // (maybe) read_state_ == IDLE -> read_state_ == POSTED (via | |
212 // PostDoRead()) | |
213 | |
214 bool ChromeAsyncSocket::Read(char* data, size_t len, size_t* len_read) { | |
215 if (!IsOpen() && (state_ != STATE_TLS_CONNECTING)) { | |
216 // Read() may be called on a closed socket if a previous read | |
217 // causes a socket close (e.g., client sends wrong password and | |
218 // server terminates connection). | |
219 // | |
220 // TODO(akalin): Fix handling of this on the libjingle side. | |
221 if (state_ != STATE_CLOSED) { | |
222 LOG(DFATAL) << "Read() called on non-open non-tls-connecting socket"; | |
223 } | |
224 DoNonNetError(ERROR_WRONGSTATE); | |
225 return false; | |
226 } | |
227 DCHECK_LE(read_start_, read_end_); | |
228 if ((state_ == STATE_TLS_CONNECTING) || read_end_ == 0U) { | |
229 if (state_ == STATE_TLS_CONNECTING) { | |
230 DCHECK_EQ(read_state_, IDLE); | |
231 DCHECK_EQ(read_end_, 0U); | |
232 } else { | |
233 DCHECK_NE(read_state_, IDLE); | |
234 } | |
235 *len_read = 0; | |
236 return true; | |
237 } | |
238 DCHECK_EQ(read_state_, IDLE); | |
239 *len_read = std::min(len, read_end_ - read_start_); | |
240 DCHECK_GT(*len_read, 0U); | |
241 std::memcpy(data, read_buf_->data() + read_start_, *len_read); | |
242 read_start_ += *len_read; | |
243 if (read_start_ == read_end_) { | |
244 read_start_ = 0U; | |
245 read_end_ = 0U; | |
246 // We defer execution of DoRead() here for similar reasons as | |
247 // ProcessConnectDone(). | |
248 PostDoRead(); | |
249 } | |
250 return true; | |
251 } | |
252 | |
253 // (maybe) write_state_ == IDLE -> write_state_ == POSTED (via | |
254 // PostDoWrite()) | |
255 | |
256 bool ChromeAsyncSocket::Write(const char* data, size_t len) { | |
257 if (!IsOpen() && (state_ != STATE_TLS_CONNECTING)) { | |
258 LOG(DFATAL) << "Write() called on non-open non-tls-connecting socket"; | |
259 DoNonNetError(ERROR_WRONGSTATE); | |
260 return false; | |
261 } | |
262 // TODO(akalin): Avoid this check by modifying the interface to have | |
263 // a "ready for writing" signal. | |
264 if ((static_cast<size_t>(write_buf_->size()) - write_end_) < len) { | |
265 LOG(DFATAL) << "queueing " << len << " bytes would exceed the " | |
266 << "max write buffer size = " << write_buf_->size() | |
267 << " by " << (len - write_buf_->size()) << " bytes"; | |
268 DoNetError(net::ERR_INSUFFICIENT_RESOURCES); | |
269 return false; | |
270 } | |
271 std::memcpy(write_buf_->data() + write_end_, data, len); | |
272 write_end_ += len; | |
273 // If we're TLS-connecting, the write buffer will get flushed once | |
274 // the TLS-connect finishes. Otherwise, start writing if we're not | |
275 // already writing and we have something to write. | |
276 if ((state_ != STATE_TLS_CONNECTING) && | |
277 (write_state_ == IDLE) && (write_end_ > 0U)) { | |
278 // We defer execution of DoWrite() here for similar reasons as | |
279 // ProcessConnectDone(). | |
280 PostDoWrite(); | |
281 } | |
282 return true; | |
283 } | |
284 | |
285 // write_state_ == IDLE -> write_state_ == POSTED | |
286 | |
287 void ChromeAsyncSocket::PostDoWrite() { | |
288 DCHECK(IsOpen()); | |
289 DCHECK_EQ(write_state_, IDLE); | |
290 DCHECK_GT(write_end_, 0U); | |
291 MessageLoop* message_loop = MessageLoop::current(); | |
292 CHECK(message_loop); | |
293 message_loop->PostTask( | |
294 FROM_HERE, | |
295 base::Bind(&ChromeAsyncSocket::DoWrite, | |
296 weak_ptr_factory_.GetWeakPtr())); | |
297 write_state_ = POSTED; | |
298 } | |
299 | |
300 // write_state_ == POSTED -> write_state_ == PENDING | |
301 | |
302 void ChromeAsyncSocket::DoWrite() { | |
303 DCHECK(IsOpen()); | |
304 DCHECK_EQ(write_state_, POSTED); | |
305 DCHECK_GT(write_end_, 0U); | |
306 // Once we call Write(), we cannot call StartTls() until the write | |
307 // finishes. This is okay, as StartTls() is called only after we | |
308 // have received a reply to a message we sent to the server and | |
309 // before we send the next message. | |
310 int status = | |
311 transport_socket_->Write( | |
312 write_buf_.get(), write_end_, | |
313 base::Bind(&ChromeAsyncSocket::ProcessWriteDone, | |
314 weak_ptr_factory_.GetWeakPtr())); | |
315 write_state_ = PENDING; | |
316 if (status != net::ERR_IO_PENDING) { | |
317 ProcessWriteDone(status); | |
318 } | |
319 } | |
320 | |
321 // write_state_ == PENDING -> write_state_ == IDLE or POSTED (the | |
322 // latter via PostDoWrite()) | |
323 | |
324 void ChromeAsyncSocket::ProcessWriteDone(int status) { | |
325 DCHECK_NE(status, net::ERR_IO_PENDING); | |
326 DCHECK(IsOpen()); | |
327 DCHECK_EQ(write_state_, PENDING); | |
328 DCHECK_GT(write_end_, 0U); | |
329 write_state_ = IDLE; | |
330 if (status < net::OK) { | |
331 DoNetErrorFromStatus(status); | |
332 DoClose(); | |
333 return; | |
334 } | |
335 size_t written = static_cast<size_t>(status); | |
336 if (written > write_end_) { | |
337 LOG(DFATAL) << "bytes written = " << written | |
338 << " exceeds bytes requested = " << write_end_; | |
339 DoNetError(net::ERR_UNEXPECTED); | |
340 DoClose(); | |
341 return; | |
342 } | |
343 // TODO(akalin): Figure out a better way to do this; perhaps a queue | |
344 // of DrainableIOBuffers. This'll also allow us to not have an | |
345 // artificial buffer size limit. | |
346 std::memmove(write_buf_->data(), | |
347 write_buf_->data() + written, | |
348 write_end_ - written); | |
349 write_end_ -= written; | |
350 if (write_end_ > 0U) { | |
351 PostDoWrite(); | |
352 } | |
353 } | |
354 | |
355 // * -> STATE_CLOSED | |
356 | |
357 bool ChromeAsyncSocket::Close() { | |
358 DoClose(); | |
359 return true; | |
360 } | |
361 | |
362 // (not STATE_CLOSED) -> STATE_CLOSED | |
363 | |
364 void ChromeAsyncSocket::DoClose() { | |
365 weak_ptr_factory_.InvalidateWeakPtrs(); | |
366 if (transport_socket_.get()) { | |
367 transport_socket_->Disconnect(); | |
368 } | |
369 transport_socket_.reset(); | |
370 read_state_ = IDLE; | |
371 read_start_ = 0U; | |
372 read_end_ = 0U; | |
373 write_state_ = IDLE; | |
374 write_end_ = 0U; | |
375 if (state_ != STATE_CLOSED) { | |
376 state_ = STATE_CLOSED; | |
377 SignalClosed(); | |
378 } | |
379 // Reset error variables after SignalClosed() so slots connected | |
380 // to it can read it. | |
381 error_ = ERROR_NONE; | |
382 net_error_ = net::OK; | |
383 } | |
384 | |
385 // STATE_OPEN -> STATE_TLS_CONNECTING | |
386 | |
387 bool ChromeAsyncSocket::StartTls(const std::string& domain_name) { | |
388 if ((state_ != STATE_OPEN) || (read_state_ == PENDING) || | |
389 (write_state_ != IDLE)) { | |
390 LOG(DFATAL) << "StartTls() called in wrong state"; | |
391 DoNonNetError(ERROR_WRONGSTATE); | |
392 return false; | |
393 } | |
394 | |
395 state_ = STATE_TLS_CONNECTING; | |
396 read_state_ = IDLE; | |
397 read_start_ = 0U; | |
398 read_end_ = 0U; | |
399 DCHECK_EQ(write_end_, 0U); | |
400 | |
401 // Clear out any posted DoRead() tasks. | |
402 weak_ptr_factory_.InvalidateWeakPtrs(); | |
403 | |
404 DCHECK(transport_socket_.get()); | |
405 scoped_ptr<net::ClientSocketHandle> socket_handle( | |
406 new net::ClientSocketHandle()); | |
407 socket_handle->set_socket(transport_socket_.release()); | |
408 transport_socket_.reset( | |
409 resolving_client_socket_factory_->CreateSSLClientSocket( | |
410 socket_handle.release(), net::HostPortPair(domain_name, 443))); | |
411 int status = transport_socket_->Connect( | |
412 base::Bind(&ChromeAsyncSocket::ProcessSSLConnectDone, | |
413 weak_ptr_factory_.GetWeakPtr())); | |
414 if (status != net::ERR_IO_PENDING) { | |
415 MessageLoop* message_loop = MessageLoop::current(); | |
416 CHECK(message_loop); | |
417 message_loop->PostTask( | |
418 FROM_HERE, | |
419 base::Bind(&ChromeAsyncSocket::ProcessSSLConnectDone, | |
420 weak_ptr_factory_.GetWeakPtr(), status)); | |
421 } | |
422 return true; | |
423 } | |
424 | |
425 // STATE_TLS_CONNECTING -> STATE_TLS_OPEN | |
426 // read_state_ == IDLE -> read_state_ == POSTED (via PostDoRead()) | |
427 // (maybe) write_state_ == IDLE -> write_state_ == POSTED (via | |
428 // PostDoWrite()) | |
429 | |
430 void ChromeAsyncSocket::ProcessSSLConnectDone(int status) { | |
431 DCHECK_NE(status, net::ERR_IO_PENDING); | |
432 DCHECK_EQ(state_, STATE_TLS_CONNECTING); | |
433 DCHECK_EQ(read_state_, IDLE); | |
434 DCHECK_EQ(read_start_, 0U); | |
435 DCHECK_EQ(read_end_, 0U); | |
436 DCHECK_EQ(write_state_, IDLE); | |
437 if (status != net::OK) { | |
438 DoNetErrorFromStatus(status); | |
439 DoClose(); | |
440 return; | |
441 } | |
442 state_ = STATE_TLS_OPEN; | |
443 PostDoRead(); | |
444 if (write_end_ > 0U) { | |
445 PostDoWrite(); | |
446 } | |
447 SignalSSLConnected(); | |
448 } | |
449 | |
450 } // namespace notifier | |
OLD | NEW |