diff options
author | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-16 09:35:30 +0000 |
---|---|---|
committer | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-16 09:35:30 +0000 |
commit | 3d6ddbadc0c670fa3db69f7173a67da09f9f1a12 (patch) | |
tree | aa1cc86f6426f6607360670e21023969f697f16d /jingle/notifier | |
parent | 387a6ceae16e24b7ed19dc143f97c3378ca343df (diff) | |
download | chromium_src-3d6ddbadc0c670fa3db69f7173a67da09f9f1a12.zip chromium_src-3d6ddbadc0c670fa3db69f7173a67da09f9f1a12.tar.gz chromium_src-3d6ddbadc0c670fa3db69f7173a67da09f9f1a12.tar.bz2 |
Moved ChromeAsyncSocket to jingle/notifier/base.
Fixed some signed/unsigned warnings.
BUG=45612
TEST=existing unittests
Review URL: http://codereview.chromium.org/2857039
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52638 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'jingle/notifier')
-rw-r--r-- | jingle/notifier/base/chrome_async_socket.cc | 479 | ||||
-rw-r--r-- | jingle/notifier/base/chrome_async_socket.h | 226 | ||||
-rw-r--r-- | jingle/notifier/base/chrome_async_socket_unittest.cc | 1015 |
3 files changed, 1720 insertions, 0 deletions
diff --git a/jingle/notifier/base/chrome_async_socket.cc b/jingle/notifier/base/chrome_async_socket.cc new file mode 100644 index 0000000..85929c1 --- /dev/null +++ b/jingle/notifier/base/chrome_async_socket.cc @@ -0,0 +1,479 @@ +// 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 "jingle/notifier/base/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 notifier { + +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_(0U), + read_end_(0U), + write_state_(IDLE), + write_buf_(new net::IOBufferWithSize(write_buf_size)), + write_end_(0U) { + DCHECK(client_socket_factory_); + DCHECK_GT(read_buf_size, 0U); + DCHECK_GT(write_buf_size, 0U); +} + +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(), 0U); + // 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_, 0U); + SignalConnected(); +} + +// read_state_ == IDLE -> read_state_ == POSTED + +void ChromeAsyncSocket::PostDoRead() { + DCHECK(IsOpen()); + DCHECK_EQ(read_state_, IDLE); + DCHECK_EQ(read_start_, 0U); + DCHECK_EQ(read_end_, 0U); + 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_, 0U); + DCHECK_EQ(read_end_, 0U); + // 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_, 0U); + DCHECK_EQ(read_end_, 0U); + read_state_ = IDLE; + if (status > 0) { + read_end_ = static_cast<size_t>(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)) { + // Read() may be called on a closed socket if a previous read + // causes a socket close (e.g., client sends wrong password and + // server terminates connection). + // + // TODO(akalin): Fix handling of this on the libjingle side. + if (state_ != STATE_CLOSED) { + 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_ == 0U) { + if (state_ == STATE_TLS_CONNECTING) { + DCHECK_EQ(read_state_, IDLE); + DCHECK_EQ(read_end_, 0U); + } 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, 0U); + std::memcpy(data, read_buf_->data() + read_start_, *len_read); + read_start_ += *len_read; + if (read_start_ == read_end_) { + read_start_ = 0U; + read_end_ = 0U; + // 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 ((static_cast<size_t>(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_ > 0U)) { + // 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_, 0U); + 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_, 0U); + // 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_, 0U); + write_state_ = IDLE; + if (status < net::OK) { + DoNetErrorFromStatus(status); + DoClose(); + return; + } + size_t written = static_cast<size_t>(status); + if (written > write_end_) { + LOG(DFATAL) << "bytes written = " << written + << " 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() + written, + write_end_ - written); + write_end_ -= written; + if (write_end_ > 0U) { + 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_ = 0U; + read_end_ = 0U; + write_state_ = IDLE; + write_end_ = 0U; + 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_ = 0U; + read_end_ = 0U; + DCHECK_EQ(write_end_, 0U); + + // 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_, 0U); + DCHECK_EQ(read_end_, 0U); + DCHECK_EQ(write_state_, IDLE); + if (status != net::OK) { + DoNetErrorFromStatus(status); + DoClose(); + return; + } + state_ = STATE_TLS_OPEN; + PostDoRead(); + if (write_end_ > 0U) { + PostDoWrite(); + } + SignalSSLConnected(); +} + +} // namespace notifier diff --git a/jingle/notifier/base/chrome_async_socket.h b/jingle/notifier/base/chrome_async_socket.h new file mode 100644 index 0000000..ebf8b38 --- /dev/null +++ b/jingle/notifier/base/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 JINGLE_NOTIFIER_BASE_CHROME_ASYNC_SOCKET_H_ +#define JINGLE_NOTIFIER_BASE_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 notifier { + +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 notifier + +#endif // JINGLE_NOTIFIER_BASE_CHROME_ASYNC_SOCKET_H_ diff --git a/jingle/notifier/base/chrome_async_socket_unittest.cc b/jingle/notifier/base/chrome_async_socket_unittest.cc new file mode 100644 index 0000000..a975d6f --- /dev/null +++ b/jingle/notifier/base/chrome_async_socket_unittest.cc @@ -0,0 +1,1015 @@ +// 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 "jingle/notifier/base/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 notifier { + +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: + 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 == 0U) { + 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 = 10000U; + EXPECT_TRUE(chrome_async_socket_.Read(buf, sizeof(buf), &len_read)); + EXPECT_EQ(0U, len_read); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, WrongRead) { + EXPECT_DEBUG_DEATH({ + async_socket_data_provider_.set_connect_data( + net::MockConnect(true, net::OK)); + EXPECT_TRUE(chrome_async_socket_.Connect(addr_)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CONNECTING); + ExpectNoSignal(); + + 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"); +} + +TEST_F(ChromeAsyncSocketTest, WrongReadClosed) { + 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()); +} + +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, FailedSSLConnect) { + ssl_socket_data_provider_.connect = + net::MockConnect(true, net::ERR_CERT_COMMON_NAME_INVALID), + + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + ExpectReadSignal(); + + EXPECT_TRUE(chrome_async_socket_.StartTls("fakedomain.com")); + message_loop_.RunAllPending(); + ExpectSignalSocketState( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WINSOCK, + net::ERR_CERT_COMMON_NAME_INVALID)); + + EXPECT_TRUE(chrome_async_socket_.Close()); + ExpectClosed(); +} + +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 = 10000U; + EXPECT_TRUE(chrome_async_socket_.Read(buf, sizeof(buf), &len_read)); + EXPECT_EQ(0U, len_read); + + message_loop_.RunAllPending(); + ExpectSSLConnectSignal(); + ExpectSSLReadSignal(); + ExpectNoSignal(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_TLS_OPEN); + + len_read = 10000U; + 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 notifier |