Index: chrome/browser/sync/tools/chrome_async_socket.cc |
diff --git a/chrome/browser/sync/tools/chrome_async_socket.cc b/chrome/browser/sync/tools/chrome_async_socket.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..52952db3c1b37cdc90fcd9359ffba674e1f63cb9 |
--- /dev/null |
+++ b/chrome/browser/sync/tools/chrome_async_socket.cc |
@@ -0,0 +1,470 @@ |
+// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/sync/tools/chrome_async_socket.h" |
+ |
+#if defined(OS_WIN) |
+#include <winsock2.h> |
+#elif defined(OS_POSIX) |
+#include <arpa/inet.h> |
+#endif |
+ |
+#include <algorithm> |
+#include <cstring> |
+#include <cstdlib> |
+ |
+#include "base/compiler_specific.h" |
+#include "base/logging.h" |
+#include "net/base/address_list.h" |
+#include "net/base/io_buffer.h" |
+#include "net/base/ssl_config_service.h" |
+#include "net/base/sys_addrinfo.h" |
+#include "net/socket/client_socket_factory.h" |
+#include "net/socket/ssl_client_socket.h" |
+#include "net/socket/tcp_client_socket.h" |
+#include "talk/base/socketaddress.h" |
+ |
+namespace sync_tools { |
+ |
+ChromeAsyncSocket::ChromeAsyncSocket( |
+ net::ClientSocketFactory* client_socket_factory, |
+ const net::SSLConfig& ssl_config, |
+ size_t read_buf_size, |
+ size_t write_buf_size, |
+ net::NetLog* net_log) |
+ : connect_callback_(ALLOW_THIS_IN_INITIALIZER_LIST(this), |
+ &ChromeAsyncSocket::ProcessConnectDone), |
+ read_callback_(ALLOW_THIS_IN_INITIALIZER_LIST(this), |
+ &ChromeAsyncSocket::ProcessReadDone), |
+ write_callback_(ALLOW_THIS_IN_INITIALIZER_LIST(this), |
+ &ChromeAsyncSocket::ProcessWriteDone), |
+ ssl_connect_callback_(ALLOW_THIS_IN_INITIALIZER_LIST(this), |
+ &ChromeAsyncSocket::ProcessSSLConnectDone), |
+ client_socket_factory_(client_socket_factory), |
+ ssl_config_(ssl_config), |
+ bound_net_log_( |
+ net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)), |
+ state_(STATE_CLOSED), |
+ error_(ERROR_NONE), |
+ net_error_(net::OK), |
+ scoped_runnable_method_factory_( |
+ ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
+ read_state_(IDLE), |
+ read_buf_(new net::IOBufferWithSize(read_buf_size)), |
+ read_start_(0), |
+ read_end_(0), |
+ write_state_(IDLE), |
+ write_buf_(new net::IOBufferWithSize(write_buf_size)), |
+ write_end_(0) { |
+ DCHECK(client_socket_factory_); |
+ DCHECK_GT(read_buf_size, 0); |
+ DCHECK_GT(write_buf_size, 0); |
+} |
+ |
+ChromeAsyncSocket::~ChromeAsyncSocket() {} |
+ |
+ChromeAsyncSocket::State ChromeAsyncSocket::state() { |
+ return state_; |
+} |
+ |
+ChromeAsyncSocket::Error ChromeAsyncSocket::error() { |
+ return error_; |
+} |
+ |
+int ChromeAsyncSocket::GetError() { |
+ return net_error_; |
+} |
+ |
+bool ChromeAsyncSocket::IsOpen() const { |
+ return (state_ == STATE_OPEN) || (state_ == STATE_TLS_OPEN); |
+} |
+ |
+void ChromeAsyncSocket::DoNonNetError(Error error) { |
+ DCHECK_NE(error, ERROR_NONE); |
+ DCHECK_NE(error, ERROR_WINSOCK); |
+ error_ = error; |
+ net_error_ = net::OK; |
+} |
+ |
+void ChromeAsyncSocket::DoNetError(net::Error net_error) { |
+ error_ = ERROR_WINSOCK; |
+ net_error_ = net_error; |
+} |
+ |
+void ChromeAsyncSocket::DoNetErrorFromStatus(int status) { |
+ DCHECK_LT(status, net::OK); |
+ DoNetError(static_cast<net::Error>(status)); |
+} |
+ |
+namespace { |
+ |
+net::AddressList SocketAddressToAddressList( |
+ const talk_base::SocketAddress& address) { |
+ DCHECK_NE(address.ip(), 0); |
+ // Use malloc() as net::AddressList uses free(). |
+ addrinfo* ai = static_cast<addrinfo*>(std::malloc(sizeof *ai)); |
+ memset(ai, 0, sizeof *ai); |
+ ai->ai_family = AF_INET; |
+ ai->ai_socktype = SOCK_STREAM; |
+ ai->ai_addrlen = sizeof(sockaddr_in); |
+ |
+ sockaddr_in* addr = static_cast<sockaddr_in*>(std::malloc(sizeof *addr)); |
+ memset(addr, 0, sizeof *addr); |
+ addr->sin_family = AF_INET; |
+ addr->sin_addr.s_addr = htonl(address.ip()); |
+ addr->sin_port = htons(address.port()); |
+ ai->ai_addr = reinterpret_cast<sockaddr*>(addr); |
+ |
+ net::AddressList address_list; |
+ address_list.Adopt(ai); |
+ return address_list; |
+} |
+ |
+} // namespace |
+ |
+// STATE_CLOSED -> STATE_CONNECTING |
+ |
+bool ChromeAsyncSocket::Connect(const talk_base::SocketAddress& address) { |
+ if (state_ != STATE_CLOSED) { |
+ LOG(DFATAL) << "Connect() called on non-closed socket"; |
+ DoNonNetError(ERROR_WRONGSTATE); |
+ return false; |
+ } |
+ if (address.ip() == 0) { |
+ DoNonNetError(ERROR_DNS); |
+ return false; |
+ } |
+ |
+ DCHECK_EQ(state_, buzz::AsyncSocket::STATE_CLOSED); |
+ DCHECK_EQ(read_state_, IDLE); |
+ DCHECK_EQ(write_state_, IDLE); |
+ |
+ state_ = STATE_CONNECTING; |
+ |
+ DCHECK(scoped_runnable_method_factory_.empty()); |
+ scoped_runnable_method_factory_.RevokeAll(); |
+ |
+ net::AddressList address_list = SocketAddressToAddressList(address); |
+ transport_socket_.reset( |
+ client_socket_factory_-> |
+ CreateTCPClientSocket(address_list, bound_net_log_.net_log())); |
+ int status = transport_socket_->Connect(&connect_callback_); |
+ if (status != net::ERR_IO_PENDING) { |
+ // We defer execution of ProcessConnectDone instead of calling it |
+ // directly here as the caller may not expect an error/close to |
+ // happen here. This is okay, as from the caller's point of view, |
+ // the connect always happens asynchronously. |
+ MessageLoop* message_loop = MessageLoop::current(); |
+ CHECK(message_loop); |
+ message_loop->PostTask( |
+ FROM_HERE, |
+ scoped_runnable_method_factory_.NewRunnableMethod( |
+ &ChromeAsyncSocket::ProcessConnectDone, status)); |
+ } |
+ return true; |
+} |
+ |
+// STATE_CONNECTING -> STATE_OPEN |
+// read_state_ == IDLE -> read_state_ == POSTED (via PostDoRead()) |
+ |
+void ChromeAsyncSocket::ProcessConnectDone(int status) { |
+ DCHECK_NE(status, net::ERR_IO_PENDING); |
+ DCHECK_EQ(read_state_, IDLE); |
+ DCHECK_EQ(write_state_, IDLE); |
+ DCHECK_EQ(state_, STATE_CONNECTING); |
+ if (status != net::OK) { |
+ DoNetErrorFromStatus(status); |
+ DoClose(); |
+ return; |
+ } |
+ state_ = STATE_OPEN; |
+ PostDoRead(); |
+ // Write buffer should be empty. |
+ DCHECK_EQ(write_end_, 0); |
+ SignalConnected(); |
+} |
+ |
+// read_state_ == IDLE -> read_state_ == POSTED |
+ |
+void ChromeAsyncSocket::PostDoRead() { |
+ DCHECK(IsOpen()); |
+ DCHECK_EQ(read_state_, IDLE); |
+ DCHECK_EQ(read_start_, 0); |
+ DCHECK_EQ(read_end_, 0); |
+ MessageLoop* message_loop = MessageLoop::current(); |
+ CHECK(message_loop); |
+ message_loop->PostTask( |
+ FROM_HERE, |
+ scoped_runnable_method_factory_.NewRunnableMethod( |
+ &ChromeAsyncSocket::DoRead)); |
+ read_state_ = POSTED; |
+} |
+ |
+// read_state_ == POSTED -> read_state_ == PENDING |
+ |
+void ChromeAsyncSocket::DoRead() { |
+ DCHECK(IsOpen()); |
+ DCHECK_EQ(read_state_, POSTED); |
+ DCHECK_EQ(read_start_, 0); |
+ DCHECK_EQ(read_end_, 0); |
+ // Once we call Read(), we cannot call StartTls() until the read |
+ // finishes. This is okay, as StartTls() is called only from a read |
+ // handler (i.e., after a read finishes and before another read is |
+ // done). |
+ int status = |
+ transport_socket_->Read( |
+ read_buf_.get(), read_buf_->size(), &read_callback_); |
+ read_state_ = PENDING; |
+ if (status != net::ERR_IO_PENDING) { |
+ ProcessReadDone(status); |
+ } |
+} |
+ |
+// read_state_ == PENDING -> read_state_ == IDLE |
+ |
+void ChromeAsyncSocket::ProcessReadDone(int status) { |
+ DCHECK_NE(status, net::ERR_IO_PENDING); |
+ DCHECK(IsOpen()); |
+ DCHECK_EQ(read_state_, PENDING); |
+ DCHECK_EQ(read_start_, 0); |
+ DCHECK_EQ(read_end_, 0); |
+ read_state_ = IDLE; |
+ if (status > 0) { |
+ read_end_ = status; |
+ SignalRead(); |
+ } else if (status == 0) { |
+ // Other side closed the connection. |
+ error_ = ERROR_NONE; |
+ net_error_ = net::OK; |
+ DoClose(); |
+ } else { // status < 0 |
+ DoNetErrorFromStatus(status); |
+ DoClose(); |
+ } |
+} |
+ |
+// (maybe) read_state_ == IDLE -> read_state_ == POSTED (via |
+// PostDoRead()) |
+ |
+bool ChromeAsyncSocket::Read(char* data, size_t len, size_t* len_read) { |
+ if (!IsOpen() && (state_ != STATE_TLS_CONNECTING)) { |
+ LOG(DFATAL) << "Read() called on non-open non-tls-connecting socket"; |
+ DoNonNetError(ERROR_WRONGSTATE); |
+ return false; |
+ } |
+ DCHECK_LE(read_start_, read_end_); |
+ if ((state_ == STATE_TLS_CONNECTING) || read_end_ == 0) { |
+ if (state_ == STATE_TLS_CONNECTING) { |
+ DCHECK_EQ(read_state_, IDLE); |
+ DCHECK_EQ(read_end_, 0); |
+ } else { |
+ DCHECK_NE(read_state_, IDLE); |
+ } |
+ *len_read = 0; |
+ return true; |
+ } |
+ DCHECK_EQ(read_state_, IDLE); |
+ *len_read = std::min(len, read_end_ - read_start_); |
+ DCHECK_GT(*len_read, 0); |
+ std::memcpy(data, read_buf_->data() + read_start_, *len_read); |
+ read_start_ += *len_read; |
+ if (read_start_ == read_end_) { |
+ read_start_ = 0; |
+ read_end_ = 0; |
+ // We defer execution of DoRead() here for similar reasons as |
+ // ProcessConnectDone(). |
+ PostDoRead(); |
+ } |
+ return true; |
+} |
+ |
+// (maybe) write_state_ == IDLE -> write_state_ == POSTED (via |
+// PostDoWrite()) |
+ |
+bool ChromeAsyncSocket::Write(const char* data, size_t len) { |
+ if (!IsOpen() && (state_ != STATE_TLS_CONNECTING)) { |
+ LOG(DFATAL) << "Write() called on non-open non-tls-connecting socket"; |
+ DoNonNetError(ERROR_WRONGSTATE); |
+ return false; |
+ } |
+ // TODO(akalin): Avoid this check by modifying the interface to have |
+ // a "ready for writing" signal. |
+ if ((write_buf_->size() - write_end_) < len) { |
+ LOG(DFATAL) << "queueing " << len << " bytes would exceed the " |
+ << "max write buffer size = " << write_buf_->size() |
+ << " by " << (len - write_buf_->size()) << " bytes"; |
+ DoNetError(net::ERR_INSUFFICIENT_RESOURCES); |
+ return false; |
+ } |
+ std::memcpy(write_buf_->data() + write_end_, data, len); |
+ write_end_ += len; |
+ // If we're TLS-connecting, the write buffer will get flushed once |
+ // the TLS-connect finishes. Otherwise, start writing if we're not |
+ // already writing and we have something to write. |
+ if ((state_ != STATE_TLS_CONNECTING) && |
+ (write_state_ == IDLE) && (write_end_ > 0)) { |
+ // We defer execution of DoWrite() here for similar reasons as |
+ // ProcessConnectDone(). |
+ PostDoWrite(); |
+ } |
+ return true; |
+} |
+ |
+// write_state_ == IDLE -> write_state_ == POSTED |
+ |
+void ChromeAsyncSocket::PostDoWrite() { |
+ DCHECK(IsOpen()); |
+ DCHECK_EQ(write_state_, IDLE); |
+ DCHECK_GT(write_end_, 0); |
+ MessageLoop* message_loop = MessageLoop::current(); |
+ CHECK(message_loop); |
+ message_loop->PostTask( |
+ FROM_HERE, |
+ scoped_runnable_method_factory_.NewRunnableMethod( |
+ &ChromeAsyncSocket::DoWrite)); |
+ write_state_ = POSTED; |
+} |
+ |
+// write_state_ == POSTED -> write_state_ == PENDING |
+ |
+void ChromeAsyncSocket::DoWrite() { |
+ DCHECK(IsOpen()); |
+ DCHECK_EQ(write_state_, POSTED); |
+ DCHECK_GT(write_end_, 0); |
+ // Once we call Write(), we cannot call StartTls() until the write |
+ // finishes. This is okay, as StartTls() is called only after we |
+ // have received a reply to a message we sent to the server and |
+ // before we send the next message. |
+ int status = |
+ transport_socket_->Write( |
+ write_buf_.get(), write_end_, &write_callback_); |
+ write_state_ = PENDING; |
+ if (status != net::ERR_IO_PENDING) { |
+ ProcessWriteDone(status); |
+ } |
+} |
+ |
+// write_state_ == PENDING -> write_state_ == IDLE or POSTED (the |
+// latter via PostDoWrite()) |
+ |
+void ChromeAsyncSocket::ProcessWriteDone(int status) { |
+ DCHECK_NE(status, net::ERR_IO_PENDING); |
+ DCHECK(IsOpen()); |
+ DCHECK_EQ(write_state_, PENDING); |
+ DCHECK_GT(write_end_, 0); |
+ write_state_ = IDLE; |
+ if (status < net::OK) { |
+ DoNetErrorFromStatus(status); |
+ DoClose(); |
+ return; |
+ } |
+ if (status > write_end_) { |
+ LOG(DFATAL) << "bytes read = " << status |
+ << " exceeds bytes requested = " << write_end_; |
+ DoNetError(net::ERR_UNEXPECTED); |
+ DoClose(); |
+ return; |
+ } |
+ // TODO(akalin): Figure out a better way to do this; perhaps a queue |
+ // of DrainableIOBuffers. This'll also allow us to not have an |
+ // artificial buffer size limit. |
+ std::memmove(write_buf_->data(), |
+ write_buf_->data() + status, |
+ write_end_ - status); |
+ write_end_ -= status; |
+ if (write_end_ > 0) { |
+ PostDoWrite(); |
+ } |
+} |
+ |
+// * -> STATE_CLOSED |
+ |
+bool ChromeAsyncSocket::Close() { |
+ DoClose(); |
+ return true; |
+} |
+ |
+// (not STATE_CLOSED) -> STATE_CLOSED |
+ |
+void ChromeAsyncSocket::DoClose() { |
+ scoped_runnable_method_factory_.RevokeAll(); |
+ if (transport_socket_.get()) { |
+ transport_socket_->Disconnect(); |
+ } |
+ transport_socket_.reset(); |
+ read_state_ = IDLE; |
+ read_start_ = 0; |
+ read_end_ = 0; |
+ write_state_ = IDLE; |
+ write_end_ = 0; |
+ if (state_ != STATE_CLOSED) { |
+ state_ = STATE_CLOSED; |
+ SignalClosed(); |
+ } |
+ // Reset error variables after SignalClosed() so slots connected |
+ // to it can read it. |
+ error_ = ERROR_NONE; |
+ net_error_ = net::OK; |
+} |
+ |
+// STATE_OPEN -> STATE_TLS_CONNECTING |
+ |
+bool ChromeAsyncSocket::StartTls(const std::string& domain_name) { |
+ if ((state_ != STATE_OPEN) || (read_state_ == PENDING) || |
+ (write_state_ != IDLE)) { |
+ LOG(DFATAL) << "StartTls() called in wrong state"; |
+ DoNonNetError(ERROR_WRONGSTATE); |
+ return false; |
+ } |
+ |
+ state_ = STATE_TLS_CONNECTING; |
+ read_state_ = IDLE; |
+ read_start_ = 0; |
+ read_end_ = 0; |
+ DCHECK_EQ(write_end_, 0); |
+ |
+ // Clear out any posted DoRead() tasks. |
+ scoped_runnable_method_factory_.RevokeAll(); |
+ |
+ DCHECK(transport_socket_.get()); |
+ transport_socket_.reset( |
+ client_socket_factory_->CreateSSLClientSocket( |
+ transport_socket_.release(), domain_name, ssl_config_)); |
+ int status = transport_socket_->Connect(&ssl_connect_callback_); |
+ if (status != net::ERR_IO_PENDING) { |
+ MessageLoop* message_loop = MessageLoop::current(); |
+ CHECK(message_loop); |
+ message_loop->PostTask( |
+ FROM_HERE, |
+ scoped_runnable_method_factory_.NewRunnableMethod( |
+ &ChromeAsyncSocket::ProcessSSLConnectDone, status)); |
+ } |
+ return true; |
+} |
+ |
+// STATE_TLS_CONNECTING -> STATE_TLS_OPEN |
+// read_state_ == IDLE -> read_state_ == POSTED (via PostDoRead()) |
+// (maybe) write_state_ == IDLE -> write_state_ == POSTED (via |
+// PostDoWrite()) |
+ |
+void ChromeAsyncSocket::ProcessSSLConnectDone(int status) { |
+ DCHECK_NE(status, net::ERR_IO_PENDING); |
+ DCHECK_EQ(state_, STATE_TLS_CONNECTING); |
+ DCHECK_EQ(read_state_, IDLE); |
+ DCHECK_EQ(read_start_, 0); |
+ DCHECK_EQ(read_end_, 0); |
+ DCHECK_EQ(write_state_, IDLE); |
+ if (status != net::OK) { |
+ DoNetErrorFromStatus(status); |
+ return; |
+ } |
+ state_ = STATE_TLS_OPEN; |
+ PostDoRead(); |
+ if (write_end_ > 0) { |
+ PostDoWrite(); |
+ } |
+ SignalSSLConnected(); |
+} |
+ |
+} // namespace sync_tools |