summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/sync/tools/chrome_async_socket.cc470
-rw-r--r--chrome/browser/sync/tools/chrome_async_socket.h226
-rw-r--r--chrome/browser/sync/tools/chrome_async_socket_unittest.cc981
-rw-r--r--chrome/browser/sync/tools/sync_listen_notifications.cc48
-rw-r--r--chrome/browser/sync/tools/sync_tools.gyp49
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',
+ ],
+ }],
+ ],
+ },
],
}