diff options
-rw-r--r-- | chrome/browser/sync/tools/chrome_async_socket.cc | 470 | ||||
-rw-r--r-- | chrome/browser/sync/tools/chrome_async_socket.h | 226 | ||||
-rw-r--r-- | chrome/browser/sync/tools/chrome_async_socket_unittest.cc | 981 | ||||
-rw-r--r-- | chrome/browser/sync/tools/sync_listen_notifications.cc | 48 | ||||
-rw-r--r-- | chrome/browser/sync/tools/sync_tools.gyp | 49 |
5 files changed, 1758 insertions, 16 deletions
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 0000000..52952db --- /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 diff --git a/chrome/browser/sync/tools/chrome_async_socket.h b/chrome/browser/sync/tools/chrome_async_socket.h new file mode 100644 index 0000000..63e1c3b --- /dev/null +++ b/chrome/browser/sync/tools/chrome_async_socket.h @@ -0,0 +1,226 @@ +// 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. +// +// An implementation of buzz::AsyncSocket that uses Chrome sockets. + +#ifndef CHROME_BROWSER_SYNC_TOOLS_CHROME_ASYNC_SOCKET_H_ +#define CHROME_BROWSER_SYNC_TOOLS_CHROME_ASYNC_SOCKET_H_ + +#if !defined(FEATURE_ENABLE_SSL) +#error ChromeAsyncSocket expects FEATURE_ENABLE_SSL to be defined +#endif + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/ssl_config_service.h" +#include "talk/xmpp/asyncsocket.h" + +namespace net { +class ClientSocket; +class ClientSocketFactory; +class IOBufferWithSize; +} // namespace net + +namespace sync_tools { + +class ChromeAsyncSocket : public buzz::AsyncSocket { + public: + // Does not take ownership of |client_socket_factory| or |net_log|. + // |net_log| may be NULL. + 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); + + // Does not raise any signals. + virtual ~ChromeAsyncSocket(); + + // buzz::AsyncSocket implementation. + + // The current state (see buzz::AsyncSocket::State; all but + // STATE_CLOSING is used). + virtual State state(); + + // The last generated error. Errors are generated when the main + // functions below return false or when SignalClosed is raised due + // to an asynchronous error. + virtual Error error(); + + // GetError() (which is of type net::Error) != net::OK only when + // error() == ERROR_WINSOCK. + virtual int GetError(); + + // Tries to connect to the given address. + // + // If state() is not STATE_CLOSED, sets error to ERROR_WRONGSTATE + // and returns false. + // + // If |address| is not resolved, sets error to ERROR_DNS and returns + // false. + // + // Otherwise, starts the connection process and returns true. + // SignalConnected will be raised when the connection is successful; + // otherwise, SignalClosed will be raised with a net error set. + virtual bool Connect(const talk_base::SocketAddress& address); + + // Tries to read at most |len| bytes into |data|. + // + // If state() is not STATE_TLS_CONNECTING, STATE_OPEN, or + // STATE_TLS_OPEN, sets error to ERROR_WRONGSTATE and returns false. + // + // Otherwise, fills in |len_read| with the number of bytes read and + // returns true. If this is called when state() is + // STATE_TLS_CONNECTING, reads 0 bytes. (We have to handle this + // case because StartTls() is called during a slot connected to + // SignalRead after parsing the final non-TLS reply from the server + // [see XmppClient::Private::OnSocketRead()].) + virtual bool Read(char* data, size_t len, size_t* len_read); + + // Queues up |len| bytes of |data| for writing. + // + // If state() is not STATE_TLS_CONNECTING, STATE_OPEN, or + // STATE_TLS_OPEN, sets error to ERROR_WRONGSTATE and returns false. + // + // If the given data is too big for the internal write buffer, sets + // error to ERROR_WINSOCK/net::ERR_INSUFFICIENT_RESOURCES and + // returns false. + // + // Otherwise, queues up the data and returns true. If this is + // called when state() == STATE_TLS_CONNECTING, the data is will be + // sent only after the TLS connection succeeds. (See StartTls() + // below for why this happens.) + // + // Note that there's no guarantee that the data will actually be + // sent; however, it is guaranteed that the any data sent will be + // sent in FIFO order. + virtual bool Write(const char* data, size_t len); + + // If the socket is not already closed, closes the socket and raises + // SignalClosed. Always returns true. + virtual bool Close(); + + // Tries to change to a TLS connection with the given domain name. + // + // If state() is not STATE_OPEN or there are pending reads or + // writes, sets error to ERROR_WRONGSTATE and returns false. (In + // practice, this means that StartTls() can only be called from a + // slot connected to SignalRead.) + // + // Otherwise, starts the TLS connection process and returns true. + // SignalSSLConnected will be raised when the connection is + // successful; otherwise, SignalClosed will be raised with a net + // error set. + virtual bool StartTls(const std::string& domain_name); + + // Signal behavior: + // + // SignalConnected: raised whenever the connect initiated by a call + // to Connect() is complete. + // + // SignalSSLConnected: raised whenever the connect initiated by a + // call to StartTls() is complete. Not actually used by + // XmppClient. (It just assumes that if SignalRead is raised after a + // call to StartTls(), the connection has been successfully + // upgraded.) + // + // SignalClosed: raised whenever the socket is closed, either due to + // an asynchronous error, the other side closing the connection, or + // when Close() is called. + // + // SignalRead: raised whenever the next call to Read() will succeed + // with a non-zero |len_read| (assuming nothing else happens in the + // meantime). + // + // SignalError: not used. + + private: + enum AsyncIOState { + // An I/O op is not in progress. + IDLE, + // A function has been posted to do the I/O. + POSTED, + // An async I/O operation is pending. + PENDING, + }; + + bool IsOpen() const; + + // Error functions. + void DoNonNetError(Error error); + void DoNetError(net::Error net_error); + void DoNetErrorFromStatus(int status); + + // Connection functions. + void ProcessConnectDone(int status); + + // Read loop functions. + void PostDoRead(); + void DoRead(); + void ProcessReadDone(int status); + + // Write loop functions. + void PostDoWrite(); + void DoWrite(); + void ProcessWriteDone(int status); + + // SSL/TLS connection functions. + void ProcessSSLConnectDone(int status); + + // Close functions. + void DoClose(); + + // Callbacks passed to |transport_socket_|. + net::CompletionCallbackImpl<ChromeAsyncSocket> connect_callback_; + net::CompletionCallbackImpl<ChromeAsyncSocket> read_callback_; + net::CompletionCallbackImpl<ChromeAsyncSocket> write_callback_; + net::CompletionCallbackImpl<ChromeAsyncSocket> ssl_connect_callback_; + + // Weak pointer. + net::ClientSocketFactory* const client_socket_factory_; + const net::SSLConfig ssl_config_; + net::BoundNetLog bound_net_log_; + + // buzz::AsyncSocket state. + buzz::AsyncSocket::State state_; + buzz::AsyncSocket::Error error_; + net::Error net_error_; + + // Used by read/write loops. + ScopedRunnableMethodFactory<ChromeAsyncSocket> + scoped_runnable_method_factory_; + + // NULL iff state() == STATE_CLOSED. + // + // TODO(akalin): Use ClientSocketPool. + scoped_ptr<net::ClientSocket> transport_socket_; + + // State for the read loop. |read_start_| <= |read_end_| <= + // |read_buf_->size()|. There's a read in flight (i.e., + // |read_state_| != IDLE) iff |read_end_| == 0. + AsyncIOState read_state_; + scoped_refptr<net::IOBufferWithSize> read_buf_; + size_t read_start_, read_end_; + + // State for the write loop. |write_end_| <= |write_buf_->size()|. + // There's a write in flight (i.e., |write_state_| != IDLE) iff + // |write_end_| > 0. + AsyncIOState write_state_; + scoped_refptr<net::IOBufferWithSize> write_buf_; + size_t write_end_; + + DISALLOW_COPY_AND_ASSIGN(ChromeAsyncSocket); +}; + +} // namespace sync_tools + +#endif // CHROME_BROWSER_SYNC_TOOLS_CHROME_ASYNC_SOCKET_H_ diff --git a/chrome/browser/sync/tools/chrome_async_socket_unittest.cc b/chrome/browser/sync/tools/chrome_async_socket_unittest.cc new file mode 100644 index 0000000..225a215 --- /dev/null +++ b/chrome/browser/sync/tools/chrome_async_socket_unittest.cc @@ -0,0 +1,981 @@ +// 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" + +#include <deque> +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "net/base/ssl_config_service.h" +#include "net/base/capturing_net_log.h" +#include "net/base/net_errors.h" +#include "net/socket/socket_test_util.h" +#include "talk/base/sigslot.h" +#include "talk/base/socketaddress.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sync_tools { + +namespace { + +// Data provider that handles reads/writes for ChromeAsyncSocket. +class AsyncSocketDataProvider : public net::SocketDataProvider { + public: + AsyncSocketDataProvider() : has_pending_read_(false) {} + + virtual ~AsyncSocketDataProvider() { + EXPECT_TRUE(writes_.empty()); + EXPECT_TRUE(reads_.empty()); + } + + // If there's no read, sets the "has pending read" flag. Otherwise, + // pops the next read. + virtual net::MockRead GetNextRead() { + if (reads_.empty()) { + DCHECK(!has_pending_read_); + has_pending_read_ = true; + const net::MockRead pending_read(false, net::ERR_IO_PENDING); + return pending_read; + } + net::MockRead mock_read = reads_.front(); + reads_.pop_front(); + return mock_read; + } + + // Simply pops the next write and, if applicable, compares it to + // |data|. + virtual net::MockWriteResult OnWrite(const std::string& data) { + DCHECK(!writes_.empty()); + net::MockWrite mock_write = writes_.front(); + writes_.pop_front(); + if (mock_write.result != net::OK) { + return net::MockWriteResult(mock_write.async, mock_write.result); + } + std::string expected_data(mock_write.data, mock_write.data_len); + EXPECT_EQ(expected_data, data); + if (expected_data != data) { + return net::MockWriteResult(false, net::ERR_UNEXPECTED); + } + return net::MockWriteResult(mock_write.async, data.size()); + } + + // We ignore resets so we can pre-load the socket data provider with + // read/write events. + virtual void Reset() {} + + // If there is a pending read, completes it with the given read. + // Otherwise, queues up the given read. + void AddRead(const net::MockRead& mock_read) { + DCHECK_NE(mock_read.result, net::ERR_IO_PENDING); + if (has_pending_read_) { + socket()->OnReadComplete(mock_read); + has_pending_read_ = false; + return; + } + reads_.push_back(mock_read); + } + + // Simply queues up the given write. + void AddWrite(const net::MockWrite& mock_write) { + writes_.push_back(mock_write); + } + + private: + std::deque<net::MockRead> reads_; + bool has_pending_read_; + + std::deque<net::MockWrite> writes_; + + DISALLOW_COPY_AND_ASSIGN(AsyncSocketDataProvider); +}; + +class ChromeAsyncSocketTest + : public testing::Test, + public sigslot::has_slots<> { + protected: + // TODO(akalin): test SSL states other than connection success. + ChromeAsyncSocketTest() + : ssl_socket_data_provider_(true, net::OK), + capturing_net_log_(net::CapturingNetLog::kUnbounded), + chrome_async_socket_(&mock_client_socket_factory_, + ssl_config_, 14, 20, &capturing_net_log_), + addr_(0xaabbccdd, 35) {} + + virtual ~ChromeAsyncSocketTest() {} + + virtual void SetUp() { + mock_client_socket_factory_.AddSocketDataProvider( + &async_socket_data_provider_); + mock_client_socket_factory_.AddSSLSocketDataProvider( + &ssl_socket_data_provider_); + + chrome_async_socket_.SignalConnected.connect( + this, &ChromeAsyncSocketTest::OnConnect); + chrome_async_socket_.SignalSSLConnected.connect( + this, &ChromeAsyncSocketTest::OnSSLConnect); + chrome_async_socket_.SignalClosed.connect( + this, &ChromeAsyncSocketTest::OnClose); + chrome_async_socket_.SignalRead.connect( + this, &ChromeAsyncSocketTest::OnRead); + chrome_async_socket_.SignalError.connect( + this, &ChromeAsyncSocketTest::OnError); + } + + virtual void TearDown() { + // Run any tasks that we forgot to pump. + message_loop_.RunAllPending(); + ExpectClosed(); + ExpectNoSignal(); + chrome_async_socket_.SignalConnected.disconnect(this); + chrome_async_socket_.SignalSSLConnected.disconnect(this); + chrome_async_socket_.SignalClosed.disconnect(this); + chrome_async_socket_.SignalRead.disconnect(this); + chrome_async_socket_.SignalError.disconnect(this); + } + + enum Signal { + SIGNAL_CONNECT, + SIGNAL_SSL_CONNECT, + SIGNAL_CLOSE, + SIGNAL_READ, + SIGNAL_ERROR, + }; + + // Helper struct that records the state at the time of a signal. + + struct SignalSocketState { + SignalSocketState() + : signal(SIGNAL_ERROR), + state(ChromeAsyncSocket::STATE_CLOSED), + error(ChromeAsyncSocket::ERROR_NONE), + net_error(net::OK) {} + + SignalSocketState( + Signal signal, + ChromeAsyncSocket::State state, + ChromeAsyncSocket::Error error, + net::Error net_error) + : signal(signal), + state(state), + error(error), + net_error(net_error) {} + + bool IsEqual(const SignalSocketState& other) const { + return + (signal == other.signal) && + (state == other.state) && + (error == other.error) && + (net_error == other.net_error); + } + + static SignalSocketState FromAsyncSocket( + Signal signal, + buzz::AsyncSocket* async_socket) { + return SignalSocketState(signal, + async_socket->state(), + async_socket->error(), + static_cast<net::Error>( + async_socket->GetError())); + } + + static SignalSocketState NoError( + Signal signal, buzz::AsyncSocket::State state) { + return SignalSocketState(signal, state, + buzz::AsyncSocket::ERROR_NONE, + net::OK); + } + + Signal signal; + ChromeAsyncSocket::State state; + ChromeAsyncSocket::Error error; + net::Error net_error; + }; + + void CaptureSocketState(Signal signal) { + signal_socket_states_.push_back( + SignalSocketState::FromAsyncSocket(signal, &chrome_async_socket_)); + } + + void OnConnect() { + CaptureSocketState(SIGNAL_CONNECT); + } + + void OnSSLConnect() { + CaptureSocketState(SIGNAL_SSL_CONNECT); + } + + void OnClose() { + CaptureSocketState(SIGNAL_CLOSE); + } + + void OnRead() { + CaptureSocketState(SIGNAL_READ); + } + + void OnError() { + ADD_FAILURE(); + } + + // State expect functions. + + void ExpectState(ChromeAsyncSocket::State state, + ChromeAsyncSocket::Error error, + net::Error net_error) { + EXPECT_EQ(state, chrome_async_socket_.state()); + EXPECT_EQ(error, chrome_async_socket_.error()); + EXPECT_EQ(net_error, chrome_async_socket_.GetError()); + } + + void ExpectNonErrorState(ChromeAsyncSocket::State state) { + ExpectState(state, ChromeAsyncSocket::ERROR_NONE, net::OK); + } + + void ExpectErrorState(ChromeAsyncSocket::State state, + ChromeAsyncSocket::Error error) { + ExpectState(state, error, net::OK); + } + + void ExpectClosed() { + ExpectNonErrorState(ChromeAsyncSocket::STATE_CLOSED); + } + + // Signal expect functions. + + void ExpectNoSignal() { + if (!signal_socket_states_.empty()) { + ADD_FAILURE() << signal_socket_states_.front().signal; + } + } + + void ExpectSignalSocketState( + SignalSocketState expected_signal_socket_state) { + if (signal_socket_states_.empty()) { + ADD_FAILURE() << expected_signal_socket_state.signal; + return; + } + EXPECT_TRUE(expected_signal_socket_state.IsEqual( + signal_socket_states_.front())) + << signal_socket_states_.front().signal; + signal_socket_states_.pop_front(); + } + + void ExpectReadSignal() { + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_READ, ChromeAsyncSocket::STATE_OPEN)); + } + + void ExpectSSLConnectSignal() { + ExpectSignalSocketState( + SignalSocketState::NoError(SIGNAL_SSL_CONNECT, + ChromeAsyncSocket::STATE_TLS_OPEN)); + } + + void ExpectSSLReadSignal() { + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_READ, ChromeAsyncSocket::STATE_TLS_OPEN)); + } + + // Open/close utility functions. + + void DoOpenClosed() { + ExpectClosed(); + async_socket_data_provider_.set_connect_data( + net::MockConnect(false, net::OK)); + EXPECT_TRUE(chrome_async_socket_.Connect(addr_)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CONNECTING); + + message_loop_.RunAllPending(); + // We may not necessarily be open; may have been other events + // queued up. + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CONNECT, ChromeAsyncSocket::STATE_OPEN)); + } + + void DoCloseOpened(SignalSocketState expected_signal_socket_state) { + // We may be in an error state, so just compare state(). + EXPECT_EQ(ChromeAsyncSocket::STATE_OPEN, chrome_async_socket_.state()); + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectSignalSocketState(expected_signal_socket_state); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CLOSED); + } + + void DoCloseOpenedNoError() { + DoCloseOpened( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); + } + + void DoSSLOpenClosed() { + const char kDummyData[] = "dummy_data"; + async_socket_data_provider_.AddRead(net::MockRead(kDummyData)); + DoOpenClosed(); + ExpectReadSignal(); + EXPECT_EQ(kDummyData, DrainRead(1)); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + message_loop_.RunAllPending(); + ExpectSSLConnectSignal(); + ExpectNoSignal(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_OPEN); + } + + void DoSSLCloseOpened(SignalSocketState expected_signal_socket_state) { + // We may be in an error state, so just compare state(). + EXPECT_EQ(ChromeAsyncSocket::STATE_TLS_OPEN, + chrome_async_socket_.state()); + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectSignalSocketState(expected_signal_socket_state); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CLOSED); + } + + void DoSSLCloseOpenedNoError() { + DoSSLCloseOpened( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); + } + + // Read utility fucntions. + + std::string DrainRead(size_t buf_size) { + std::string read; + scoped_array<char> buf(new char[buf_size]); + size_t len_read; + while (true) { + bool success = + chrome_async_socket_.Read(buf.get(), buf_size, &len_read); + if (!success) { + ADD_FAILURE(); + break; + } + if (len_read == 0) { + break; + } + read.append(buf.get(), len_read); + } + return read; + } + + // ChromeAsyncSocket expects a message loop. + MessageLoop message_loop_; + + net::MockClientSocketFactory mock_client_socket_factory_; + AsyncSocketDataProvider async_socket_data_provider_; + net::SSLSocketDataProvider ssl_socket_data_provider_; + + net::CapturingNetLog capturing_net_log_; + net::SSLConfig ssl_config_; + ChromeAsyncSocket chrome_async_socket_; + std::deque<SignalSocketState> signal_socket_states_; + const talk_base::SocketAddress addr_; + + private: + DISALLOW_COPY_AND_ASSIGN(ChromeAsyncSocketTest); +}; + +TEST_F(ChromeAsyncSocketTest, InitialState) { + ExpectClosed(); + ExpectNoSignal(); +} + +TEST_F(ChromeAsyncSocketTest, EmptyClose) { + ExpectClosed(); + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); +} + +TEST_F(ChromeAsyncSocketTest, ImmediateConnectAndClose) { + DoOpenClosed(); + + ExpectNonErrorState(ChromeAsyncSocket::STATE_OPEN); + + DoCloseOpenedNoError(); +} + +// After this, no need to test immediate successful connect and +// Close() so thoroughly. + +TEST_F(ChromeAsyncSocketTest, DoubleClose) { + DoOpenClosed(); + + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); + + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); +} + +TEST_F(ChromeAsyncSocketTest, UnresolvedConnect) { + const talk_base::SocketAddress unresolved_addr(0, 0); + EXPECT_FALSE(chrome_async_socket_.Connect(unresolved_addr)); + ExpectErrorState(ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_DNS); + + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); +} + +TEST_F(ChromeAsyncSocketTest, DoubleConnect) { + EXPECT_DEBUG_DEATH({ + DoOpenClosed(); + + EXPECT_FALSE(chrome_async_socket_.Connect(addr_)); + ExpectErrorState(ChromeAsyncSocket::STATE_OPEN, + ChromeAsyncSocket::ERROR_WRONGSTATE); + + DoCloseOpened( + SignalSocketState(SIGNAL_CLOSE, + ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE, + net::OK)); + }, "non-closed socket"); +} + +TEST_F(ChromeAsyncSocketTest, ImmediateConnectCloseBeforeRead) { + DoOpenClosed(); + + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); + + message_loop_.RunAllPending(); +} + +TEST_F(ChromeAsyncSocketTest, HangingConnect) { + EXPECT_TRUE(chrome_async_socket_.Connect(addr_)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CONNECTING); + ExpectNoSignal(); + + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); +} + +TEST_F(ChromeAsyncSocketTest, PendingConnect) { + async_socket_data_provider_.set_connect_data( + net::MockConnect(true, net::OK)); + EXPECT_TRUE(chrome_async_socket_.Connect(addr_)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CONNECTING); + ExpectNoSignal(); + + message_loop_.RunAllPending(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_OPEN); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CONNECT, ChromeAsyncSocket::STATE_OPEN)); + ExpectNoSignal(); + + message_loop_.RunAllPending(); + + DoCloseOpenedNoError(); +} + +// After this no need to test successful pending connect so +// thoroughly. + +TEST_F(ChromeAsyncSocketTest, PendingConnectCloseBeforeRead) { + async_socket_data_provider_.set_connect_data( + net::MockConnect(true, net::OK)); + EXPECT_TRUE(chrome_async_socket_.Connect(addr_)); + + message_loop_.RunAllPending(); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CONNECT, ChromeAsyncSocket::STATE_OPEN)); + + DoCloseOpenedNoError(); + + message_loop_.RunAllPending(); +} + +TEST_F(ChromeAsyncSocketTest, PendingConnectError) { + async_socket_data_provider_.set_connect_data( + net::MockConnect(true, net::ERR_TIMED_OUT)); + EXPECT_TRUE(chrome_async_socket_.Connect(addr_)); + + message_loop_.RunAllPending(); + + ExpectSignalSocketState( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, net::ERR_TIMED_OUT)); +} + +// After this we can assume Connect() and Close() work as expected. + +TEST_F(ChromeAsyncSocketTest, EmptyRead) { + DoOpenClosed(); + + char buf[4096]; + size_t len_read = 10000; + EXPECT_TRUE(chrome_async_socket_.Read(buf, sizeof(buf), &len_read)); + EXPECT_EQ(0, len_read); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, WrongRead) { + EXPECT_DEBUG_DEATH({ + char buf[4096]; + size_t len_read; + EXPECT_FALSE(chrome_async_socket_.Read(buf, sizeof(buf), &len_read)); + ExpectErrorState(ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE); + EXPECT_TRUE(chrome_async_socket_.Close()); + }, "non-open"); +} + +const char kReadData[] = "mydatatoread"; + +TEST_F(ChromeAsyncSocketTest, Read) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + + ExpectReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunAllPending(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, ReadTwice) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + + ExpectReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunAllPending(); + + const char kReadData2[] = "mydatatoread2"; + async_socket_data_provider_.AddRead(net::MockRead(kReadData2)); + + ExpectReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData2, DrainRead(1)); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, ReadError) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + + ExpectReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunAllPending(); + + async_socket_data_provider_.AddRead( + net::MockRead(false, net::ERR_TIMED_OUT)); + + ExpectSignalSocketState( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, net::ERR_TIMED_OUT)); +} + +TEST_F(ChromeAsyncSocketTest, ReadEmpty) { + async_socket_data_provider_.AddRead(net::MockRead("")); + DoOpenClosed(); + + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); +} + +TEST_F(ChromeAsyncSocketTest, PendingRead) { + DoOpenClosed(); + + ExpectNoSignal(); + + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_READ, ChromeAsyncSocket::STATE_OPEN)); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunAllPending(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, PendingEmptyRead) { + DoOpenClosed(); + + ExpectNoSignal(); + + async_socket_data_provider_.AddRead(net::MockRead("")); + + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED)); +} + +TEST_F(ChromeAsyncSocketTest, PendingReadError) { + DoOpenClosed(); + + ExpectNoSignal(); + + async_socket_data_provider_.AddRead( + net::MockRead(true, net::ERR_TIMED_OUT)); + + ExpectSignalSocketState( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, net::ERR_TIMED_OUT)); +} + +// After this we can assume non-SSL Read() works as expected. + +TEST_F(ChromeAsyncSocketTest, WrongWrite) { + EXPECT_DEBUG_DEATH({ + std::string data("foo"); + EXPECT_FALSE(chrome_async_socket_.Write(data.data(), data.size())); + ExpectErrorState(ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE); + EXPECT_TRUE(chrome_async_socket_.Close()); + }, "non-open"); +} + +const char kWriteData[] = "mydatatowrite"; + +TEST_F(ChromeAsyncSocketTest, SyncWrite) { + async_socket_data_provider_.AddWrite( + net::MockWrite(false, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(false, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(false, kWriteData + 8, arraysize(kWriteData) - 8)); + DoOpenClosed(); + + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 3, 5)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunAllPending(); + + ExpectNoSignal(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, AsyncWrite) { + DoOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData + 8, arraysize(kWriteData) - 8)); + + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 3, 5)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunAllPending(); + + ExpectNoSignal(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, AsyncWriteError) { + DoOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(true, net::ERR_TIMED_OUT)); + + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 3, 5)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunAllPending(); + + ExpectSignalSocketState( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, net::ERR_TIMED_OUT)); +} + +TEST_F(ChromeAsyncSocketTest, LargeWrite) { + EXPECT_DEBUG_DEATH({ + DoOpenClosed(); + + std::string large_data(100, 'x'); + EXPECT_FALSE(chrome_async_socket_.Write(large_data.data(), + large_data.size())); + ExpectState(ChromeAsyncSocket::STATE_OPEN, + ChromeAsyncSocket::ERROR_WINSOCK, + net::ERR_INSUFFICIENT_RESOURCES); + DoCloseOpened( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, + net::ERR_INSUFFICIENT_RESOURCES)); + }, "exceed the max write buffer"); +} + +TEST_F(ChromeAsyncSocketTest, LargeAccumulatedWrite) { + EXPECT_DEBUG_DEATH({ + DoOpenClosed(); + + std::string data(15, 'x'); + EXPECT_TRUE(chrome_async_socket_.Write(data.data(), data.size())); + EXPECT_FALSE(chrome_async_socket_.Write(data.data(), data.size())); + ExpectState(ChromeAsyncSocket::STATE_OPEN, + ChromeAsyncSocket::ERROR_WINSOCK, + net::ERR_INSUFFICIENT_RESOURCES); + DoCloseOpened( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, + net::ERR_INSUFFICIENT_RESOURCES)); + }, "exceed the max write buffer"); +} + +// After this we can assume non-SSL I/O works as expected. + +TEST_F(ChromeAsyncSocketTest, HangingSSLConnect) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + ExpectReadSignal(); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + ExpectNoSignal(); + + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_CONNECTING); + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectSignalSocketState( + SignalSocketState::NoError(SIGNAL_CLOSE, + ChromeAsyncSocket::STATE_CLOSED)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CLOSED); +} + +TEST_F(ChromeAsyncSocketTest, ImmediateSSLConnect) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + ExpectReadSignal(); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + message_loop_.RunAllPending(); + ExpectSSLConnectSignal(); + ExpectNoSignal(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_OPEN); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, DoubleSSLConnect) { + EXPECT_DEBUG_DEATH({ + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + ExpectReadSignal(); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + message_loop_.RunAllPending(); + ExpectSSLConnectSignal(); + ExpectNoSignal(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_OPEN); + + EXPECT_FALSE(chrome_async_socket_.StartTls("fakedomain.com")); + + DoSSLCloseOpened( + SignalSocketState(SIGNAL_CLOSE, + ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE, + net::OK)); + }, "wrong state"); +} + +TEST_F(ChromeAsyncSocketTest, ReadDuringSSLConnecting) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + ExpectReadSignal(); + EXPECT_EQ(kReadData, DrainRead(1)); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + ExpectNoSignal(); + + // Shouldn't do anything. + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + + char buf[4096]; + size_t len_read = 10000; + EXPECT_TRUE(chrome_async_socket_.Read(buf, sizeof(buf), &len_read)); + EXPECT_EQ(0, len_read); + + message_loop_.RunAllPending(); + ExpectSSLConnectSignal(); + ExpectSSLReadSignal(); + ExpectNoSignal(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_OPEN); + + len_read = 10000; + EXPECT_TRUE(chrome_async_socket_.Read(buf, sizeof(buf), &len_read)); + EXPECT_EQ(kReadData, std::string(buf, len_read)); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, WriteDuringSSLConnecting) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + ExpectReadSignal(); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + ExpectNoSignal(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_CONNECTING); + + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData, 3)); + + // Shouldn't do anything. + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + + // TODO(akalin): Figure out how to test that the write happens + // *after* the SSL connect. + + message_loop_.RunAllPending(); + ExpectSSLConnectSignal(); + ExpectNoSignal(); + + message_loop_.RunAllPending(); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, SSLConnectDuringPendingRead) { + EXPECT_DEBUG_DEATH({ + DoOpenClosed(); + + EXPECT_FALSE(chrome_async_socket_.StartTls("fakedomain.com")); + + DoCloseOpened( + SignalSocketState(SIGNAL_CLOSE, + ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE, + net::OK)); + }, "wrong state"); +} + +TEST_F(ChromeAsyncSocketTest, SSLConnectDuringPostedWrite) { + EXPECT_DEBUG_DEATH({ + DoOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData, 3)); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + + EXPECT_FALSE(chrome_async_socket_.StartTls("fakedomain.com")); + + message_loop_.RunAllPending(); + + DoCloseOpened( + SignalSocketState(SIGNAL_CLOSE, + ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE, + net::OK)); + }, "wrong state"); +} + +// After this we can assume SSL connect works as expected. + +TEST_F(ChromeAsyncSocketTest, SSLRead) { + DoSSLOpenClosed(); + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + message_loop_.RunAllPending(); + + ExpectSSLReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunAllPending(); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, SSLSyncWrite) { + async_socket_data_provider_.AddWrite( + net::MockWrite(false, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(false, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(false, kWriteData + 8, arraysize(kWriteData) - 8)); + DoSSLOpenClosed(); + + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 3, 5)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunAllPending(); + + ExpectNoSignal(); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, SSLAsyncWrite) { + DoSSLOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(true, kWriteData + 8, arraysize(kWriteData) - 8)); + + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData, 3)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 3, 5)); + message_loop_.RunAllPending(); + EXPECT_TRUE(chrome_async_socket_.Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunAllPending(); + + ExpectNoSignal(); + + DoSSLCloseOpenedNoError(); +} + +} // namespace + +} // namespace sync_tools diff --git a/chrome/browser/sync/tools/sync_listen_notifications.cc b/chrome/browser/sync/tools/sync_listen_notifications.cc index f7cf9e2..d2c972a 100644 --- a/chrome/browser/sync/tools/sync_listen_notifications.cc +++ b/chrome/browser/sync/tools/sync_listen_notifications.cc @@ -17,13 +17,15 @@ #include "chrome/browser/sync/notifier/chrome_invalidation_client.h" #include "chrome/browser/sync/notifier/chrome_system_resources.h" #include "chrome/browser/sync/sync_constants.h" +#include "chrome/browser/sync/tools/chrome_async_socket.h" #include "chrome/common/chrome_switches.h" -#include "google/cacheinvalidation/invalidation-client.h" #include "jingle/notifier/base/task_pump.h" #include "jingle/notifier/communicator/xmpp_socket_adapter.h" #include "jingle/notifier/listener/listen_task.h" #include "jingle/notifier/listener/notification_constants.h" #include "jingle/notifier/listener/subscribe_task.h" +#include "net/base/ssl_config_service.h" +#include "net/socket/client_socket_factory.h" #include "talk/base/cryptstring.h" #include "talk/base/logging.h" #include "talk/base/sigslot.h" @@ -81,7 +83,8 @@ class XmppNotificationClient : public sigslot::has_slots<> { } // Connect with the given XMPP settings and run until disconnected. - void Run(const buzz::XmppClientSettings& xmpp_client_settings) { + void Run(const buzz::XmppClientSettings& xmpp_client_settings, + bool use_chrome_async_socket) { CHECK(!xmpp_client_); xmpp_client_settings_ = xmpp_client_settings; xmpp_client_ = new buzz::XmppClient(&task_pump_); @@ -93,17 +96,26 @@ class XmppNotificationClient : public sigslot::has_slots<> { xmpp_client_->SignalStateChange.connect( this, &XmppNotificationClient::OnXmppClientStateChange); - notifier::XmppSocketAdapter* xmpp_socket_adapter = - new notifier::XmppSocketAdapter(xmpp_client_settings_, false); - CHECK(xmpp_socket_adapter); - // Transfers ownership of xmpp_socket_adapter. + net::SSLConfig ssl_config; + buzz::AsyncSocket* buzz_async_socket = + use_chrome_async_socket ? + static_cast<buzz::AsyncSocket*>( + new sync_tools::ChromeAsyncSocket( + net::ClientSocketFactory::GetDefaultFactory(), + ssl_config, 4096, 64 * 1024, NULL)) : + static_cast<buzz::AsyncSocket*>( + new notifier::XmppSocketAdapter(xmpp_client_settings_, false)); + CHECK(buzz_async_socket); + // Transfers ownership of buzz_async_socket. buzz::XmppReturnStatus connect_status = xmpp_client_->Connect(xmpp_client_settings_, "", - xmpp_socket_adapter, NULL); + buzz_async_socket, NULL); CHECK_EQ(connect_status, buzz::XMPP_RETURN_OK); xmpp_client_->Start(); - MessageLoop::current()->PostTask( - FROM_HERE, NewRunnableFunction(&PumpAuxiliaryLoops)); + if (!use_chrome_async_socket) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableFunction(&PumpAuxiliaryLoops)); + } MessageLoop::current()->Run(); DCHECK(!xmpp_client_); } @@ -265,7 +277,8 @@ int main(int argc, char* argv[]) { if (email.empty()) { printf("Usage: %s --email=foo@bar.com [--password=mypassword] " "[--server=talk.google.com] [--port=5222] [--allow-plain] " - "[--disable-tls] [--use-cache-invalidation] [--use-ssl-tcp]\n", + "[--disable-tls] [--use-cache-invalidation] [--use-ssl-tcp] " + "[--use-chrome-async-socket]\n", argv[0]); return -1; } @@ -310,8 +323,12 @@ int main(int argc, char* argv[]) { insecure_crypt_string.password() = password; xmpp_client_settings.set_pass( talk_base::CryptString(insecure_crypt_string)); - xmpp_client_settings.set_server( - talk_base::SocketAddress(server, port)); + talk_base::SocketAddress addr(server, port); + if (!addr.ResolveIP()) { + LOG(ERROR) << "Could not resolve " << addr.ToString(); + return -1; + } + xmpp_client_settings.set_server(addr); // Set up message loops and socket servers. talk_base::PhysicalSocketServer physical_socket_server; @@ -329,8 +346,13 @@ int main(int argc, char* argv[]) { } else { delegate = &legacy_notifier_delegate; } + // TODO(akalin): Revert the move of all switches in this file into + // chrome_switches.h. + bool use_chrome_async_socket = + command_line.HasSwitch("use-chrome-async-socket"); XmppNotificationClient xmpp_notification_client(delegate); - xmpp_notification_client.Run(xmpp_client_settings); + xmpp_notification_client.Run(xmpp_client_settings, + use_chrome_async_socket); return 0; } diff --git a/chrome/browser/sync/tools/sync_tools.gyp b/chrome/browser/sync/tools/sync_tools.gyp index 6059ea8..0205024 100644 --- a/chrome/browser/sync/tools/sync_tools.gyp +++ b/chrome/browser/sync/tools/sync_tools.gyp @@ -3,11 +3,24 @@ # found in the LICENSE file. { - 'defines': [ - 'FEATURE_ENABLE_SSL', - ], 'targets': [ { + 'target_name': 'chrome_async_socket', + 'type': '<(library)', + 'sources': [ + 'chrome_async_socket.cc', + 'chrome_async_socket.h', + ], + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/third_party/libjingle/libjingle.gyp:libjingle', + ], + 'export_dependent_settings': [ + '<(DEPTH)/third_party/libjingle/libjingle.gyp:libjingle', + ], + }, + { 'target_name': 'sync_listen_notifications', 'type': 'executable', 'sources': [ @@ -19,6 +32,7 @@ '<(DEPTH)/chrome/browser/sync/sync_constants.h', ], 'dependencies': [ + 'chrome_async_socket', '<(DEPTH)/base/base.gyp:base', '<(DEPTH)/chrome/chrome.gyp:common_constants', '<(DEPTH)/chrome/chrome.gyp:sync_notifier', @@ -26,6 +40,35 @@ '<(DEPTH)/third_party/libjingle/libjingle.gyp:libjingle', ], }, + { + 'target_name': 'chrome_async_socket_unit_tests', + 'type': 'executable', + 'sources': [ + # TODO(akalin): Write our own test suite and runner. + '<(DEPTH)/base/test/run_all_unittests.cc', + '<(DEPTH)/base/test/test_suite.h', + 'chrome_async_socket_unittest.cc', + ], + 'dependencies': [ + 'chrome_async_socket', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/net/net.gyp:net_test_support', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + # TODO(akalin): Remove this once we have our own test suite and + # runner. + 'conditions': [ + ['OS == "linux" or OS == "freebsd" or OS == "openbsd" or OS == "solaris"', { + 'dependencies': [ + # Needed to handle the #include chain: + # base/test/test_suite.h + # gtk/gtk.h + '<(DEPTH)/build/linux/system.gyp:gtk', + ], + }], + ], + }, ], } |