diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/jingle | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/jingle')
109 files changed, 11908 insertions, 0 deletions
diff --git a/chromium/jingle/DEPS b/chromium/jingle/DEPS new file mode 100644 index 00000000000..f12bdb03fce --- /dev/null +++ b/chromium/jingle/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+net", + "+third_party/libjingle", +] diff --git a/chromium/jingle/OWNERS b/chromium/jingle/OWNERS new file mode 100644 index 00000000000..8d4af2209bc --- /dev/null +++ b/chromium/jingle/OWNERS @@ -0,0 +1,6 @@ +ajwong@chromium.org +akalin@chromium.org +hclam@chromium.org +sanjeevr@chromium.org +sergeyu@chromium.org +zea@chromium.org diff --git a/chromium/jingle/PRESUBMIT.py b/chromium/jingle/PRESUBMIT.py new file mode 100644 index 00000000000..1ed6464075e --- /dev/null +++ b/chromium/jingle/PRESUBMIT.py @@ -0,0 +1,16 @@ +# Copyright (c) 2012 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. + +"""Chromium presubmit script for src/jingle. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details on the presubmit API built into gcl. +""" + +def GetPreferredTrySlaves(): + return [ + 'linux_rel:sync_integration_tests', + 'mac_rel:sync_integration_tests', + 'win_rel:sync_integration_tests', + ] diff --git a/chromium/jingle/glue/DEPS b/chromium/jingle/glue/DEPS new file mode 100644 index 00000000000..708fdc5a74d --- /dev/null +++ b/chromium/jingle/glue/DEPS @@ -0,0 +1,4 @@ +# Needed by logging_unittest.cc since it tests the overrides. +include_rules = [ + "+third_party/libjingle/overrides", +]
\ No newline at end of file diff --git a/chromium/jingle/glue/channel_socket_adapter.cc b/chromium/jingle/glue/channel_socket_adapter.cc new file mode 100644 index 00000000000..0f68d502c73 --- /dev/null +++ b/chromium/jingle/glue/channel_socket_adapter.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2012 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/glue/channel_socket_adapter.h" + +#include <limits> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "third_party/libjingle/source/talk/p2p/base/transportchannel.h" + +namespace jingle_glue { + +TransportChannelSocketAdapter::TransportChannelSocketAdapter( + cricket::TransportChannel* channel) + : message_loop_(base::MessageLoop::current()), + channel_(channel), + closed_error_code_(net::OK) { + DCHECK(channel_); + + channel_->SignalReadPacket.connect( + this, &TransportChannelSocketAdapter::OnNewPacket); + channel_->SignalWritableState.connect( + this, &TransportChannelSocketAdapter::OnWritableState); + channel_->SignalDestroyed.connect( + this, &TransportChannelSocketAdapter::OnChannelDestroyed); +} + +TransportChannelSocketAdapter::~TransportChannelSocketAdapter() { + if (!destruction_callback_.is_null()) + destruction_callback_.Run(); +} + +void TransportChannelSocketAdapter::SetOnDestroyedCallback( + const base::Closure& callback) { + destruction_callback_ = callback; +} + +int TransportChannelSocketAdapter::Read( + net::IOBuffer* buf, + int buffer_size, + const net::CompletionCallback& callback) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + DCHECK(buf); + DCHECK(!callback.is_null()); + CHECK(read_callback_.is_null()); + + if (!channel_) { + DCHECK(closed_error_code_ != net::OK); + return closed_error_code_; + } + + read_callback_ = callback; + read_buffer_ = buf; + read_buffer_size_ = buffer_size; + + return net::ERR_IO_PENDING; +} + +int TransportChannelSocketAdapter::Write( + net::IOBuffer* buffer, + int buffer_size, + const net::CompletionCallback& callback) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + DCHECK(buffer); + DCHECK(!callback.is_null()); + CHECK(write_callback_.is_null()); + + if (!channel_) { + DCHECK(closed_error_code_ != net::OK); + return closed_error_code_; + } + + int result; + if (channel_->writable()) { + result = channel_->SendPacket(buffer->data(), buffer_size); + if (result < 0) { + result = net::MapSystemError(channel_->GetError()); + + // If the underlying socket returns IO pending where it shouldn't we + // pretend the packet is dropped and return as succeeded because no + // writeable callback will happen. + if (result == net::ERR_IO_PENDING) + result = net::OK; + } + } else { + // Channel is not writable yet. + result = net::ERR_IO_PENDING; + write_callback_ = callback; + write_buffer_ = buffer; + write_buffer_size_ = buffer_size; + } + + return result; +} + +bool TransportChannelSocketAdapter::SetReceiveBufferSize(int32 size) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + return channel_->SetOption(talk_base::Socket::OPT_RCVBUF, size) == 0; +} + +bool TransportChannelSocketAdapter::SetSendBufferSize(int32 size) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + return channel_->SetOption(talk_base::Socket::OPT_SNDBUF, size) == 0; +} + +void TransportChannelSocketAdapter::Close(int error_code) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + + if (!channel_) // Already closed. + return; + + DCHECK(error_code != net::OK); + closed_error_code_ = error_code; + channel_->SignalReadPacket.disconnect(this); + channel_->SignalDestroyed.disconnect(this); + channel_ = NULL; + + if (!read_callback_.is_null()) { + net::CompletionCallback callback = read_callback_; + read_callback_.Reset(); + read_buffer_ = NULL; + callback.Run(error_code); + } + + if (!write_callback_.is_null()) { + net::CompletionCallback callback = write_callback_; + write_callback_.Reset(); + write_buffer_ = NULL; + callback.Run(error_code); + } +} + +void TransportChannelSocketAdapter::OnNewPacket( + cricket::TransportChannel* channel, + const char* data, + size_t data_size, + int flags) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + DCHECK_EQ(channel, channel_); + if (!read_callback_.is_null()) { + DCHECK(read_buffer_.get()); + CHECK_LT(data_size, static_cast<size_t>(std::numeric_limits<int>::max())); + + if (read_buffer_size_ < static_cast<int>(data_size)) { + LOG(WARNING) << "Data buffer is smaller than the received packet. " + << "Dropping the data that doesn't fit."; + data_size = read_buffer_size_; + } + + memcpy(read_buffer_->data(), data, data_size); + + net::CompletionCallback callback = read_callback_; + read_callback_.Reset(); + read_buffer_ = NULL; + + callback.Run(data_size); + } else { + LOG(WARNING) + << "Data was received without a callback. Dropping the packet."; + } +} + +void TransportChannelSocketAdapter::OnWritableState( + cricket::TransportChannel* channel) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + // Try to send the packet if there is a pending write. + if (!write_callback_.is_null()) { + int result = channel_->SendPacket(write_buffer_->data(), + write_buffer_size_); + if (result < 0) + result = net::MapSystemError(channel_->GetError()); + + if (result != net::ERR_IO_PENDING) { + net::CompletionCallback callback = write_callback_; + write_callback_.Reset(); + write_buffer_ = NULL; + callback.Run(result); + } + } +} + +void TransportChannelSocketAdapter::OnChannelDestroyed( + cricket::TransportChannel* channel) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + DCHECK_EQ(channel, channel_); + Close(net::ERR_CONNECTION_ABORTED); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/channel_socket_adapter.h b/chromium/jingle/glue/channel_socket_adapter.h new file mode 100644 index 00000000000..baf4d4c64bd --- /dev/null +++ b/chromium/jingle/glue/channel_socket_adapter.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_GLUE_CHANNEL_SOCKET_ADAPTER_H_ +#define JINGLE_GLUE_CHANNEL_SOCKET_ADAPTER_H_ + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "net/socket/socket.h" +#include "third_party/libjingle/source/talk/base/socketaddress.h" +#include "third_party/libjingle/source/talk/base/sigslot.h" + +namespace base { +class MessageLoop; +} + +namespace cricket { +class TransportChannel; +} // namespace cricket + +namespace jingle_glue { + +// TransportChannelSocketAdapter implements net::Socket interface on +// top of libjingle's TransportChannel. It is used by +// JingleChromotocolConnection to provide net::Socket interface for channels. +class TransportChannelSocketAdapter : public net::Socket, + public sigslot::has_slots<> { + public: + // TransportChannel object is always owned by the corresponding session. + explicit TransportChannelSocketAdapter(cricket::TransportChannel* channel); + virtual ~TransportChannelSocketAdapter(); + + // Sets callback that should be called when the adapter is being + // destroyed. The callback is not allowed to touch the adapter, but + // can do anything else, e.g. destroy the TransportChannel. + void SetOnDestroyedCallback(const base::Closure& callback); + + // Closes the stream. |error_code| specifies error code that will + // be returned by Read() and Write() after the stream is closed. + // Must be called before the session and the channel are destroyed. + void Close(int error_code); + + // Socket implementation. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + + private: + void OnNewPacket(cricket::TransportChannel* channel, + const char* data, + size_t data_size, + int flags); + void OnWritableState(cricket::TransportChannel* channel); + void OnChannelDestroyed(cricket::TransportChannel* channel); + + base::MessageLoop* message_loop_; + + cricket::TransportChannel* channel_; + + base::Closure destruction_callback_; + + net::CompletionCallback read_callback_; + scoped_refptr<net::IOBuffer> read_buffer_; + int read_buffer_size_; + + net::CompletionCallback write_callback_; + scoped_refptr<net::IOBuffer> write_buffer_; + int write_buffer_size_; + + int closed_error_code_; + + DISALLOW_COPY_AND_ASSIGN(TransportChannelSocketAdapter); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_CHANNEL_SOCKET_ADAPTER_H_ diff --git a/chromium/jingle/glue/channel_socket_adapter_unittest.cc b/chromium/jingle/glue/channel_socket_adapter_unittest.cc new file mode 100644 index 00000000000..65dce71695b --- /dev/null +++ b/chromium/jingle/glue/channel_socket_adapter_unittest.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2012 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 "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/channel_socket_adapter.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/socket/socket.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libjingle/source/talk/p2p/base/transportchannel.h" + +using net::IOBuffer; + +using testing::_; +using testing::Return; + +namespace jingle_glue { + +namespace { +const int kBufferSize = 4096; +const char kTestData[] = "data"; +const int kTestDataSize = 4; +const int kTestError = -32123; +} // namespace + +class MockTransportChannel : public cricket::TransportChannel { + public: + MockTransportChannel() : cricket::TransportChannel(std::string(), 0) { + set_writable(true); + set_readable(true); + } + + MOCK_METHOD3(SendPacket, int(const char* data, size_t len, int flags)); + MOCK_METHOD2(SetOption, int(talk_base::Socket::Option opt, int value)); + MOCK_METHOD0(GetError, int()); + MOCK_CONST_METHOD0(GetIceRole, cricket::IceRole()); +}; + +class TransportChannelSocketAdapterTest : public testing::Test { + public: + TransportChannelSocketAdapterTest() + : callback_(base::Bind(&TransportChannelSocketAdapterTest::Callback, + base::Unretained(this))), + callback_result_(0) { + } + + protected: + virtual void SetUp() { + target_.reset(new TransportChannelSocketAdapter(&channel_)); + } + + void Callback(int result) { + callback_result_ = result; + } + + MockTransportChannel channel_; + scoped_ptr<TransportChannelSocketAdapter> target_; + net::CompletionCallback callback_; + int callback_result_; + base::MessageLoopForIO message_loop_; +}; + +// Verify that Read() returns net::ERR_IO_PENDING. +TEST_F(TransportChannelSocketAdapterTest, Read) { + scoped_refptr<IOBuffer> buffer(new IOBuffer(kBufferSize)); + + int result = target_->Read(buffer.get(), kBufferSize, callback_); + ASSERT_EQ(net::ERR_IO_PENDING, result); + + channel_.SignalReadPacket(&channel_, kTestData, kTestDataSize, 0); + EXPECT_EQ(kTestDataSize, callback_result_); +} + +// Verify that Read() after Close() returns error. +TEST_F(TransportChannelSocketAdapterTest, ReadClose) { + scoped_refptr<IOBuffer> buffer(new IOBuffer(kBufferSize)); + + int result = target_->Read(buffer.get(), kBufferSize, callback_); + ASSERT_EQ(net::ERR_IO_PENDING, result); + + target_->Close(kTestError); + EXPECT_EQ(kTestError, callback_result_); + + // All Read() calls after Close() should return the error. + EXPECT_EQ(kTestError, target_->Read(buffer.get(), kBufferSize, callback_)); +} + +// Verify that Write sends the packet and returns correct result. +TEST_F(TransportChannelSocketAdapterTest, Write) { + scoped_refptr<IOBuffer> buffer(new IOBuffer(kTestDataSize)); + + EXPECT_CALL(channel_, SendPacket(buffer->data(), kTestDataSize, 0)) + .WillOnce(Return(kTestDataSize)); + + int result = target_->Write(buffer.get(), kTestDataSize, callback_); + EXPECT_EQ(kTestDataSize, result); +} + +// Verify that the message is still sent if Write() is called while +// socket is not open yet. The result is the packet is lost. +TEST_F(TransportChannelSocketAdapterTest, WritePending) { + scoped_refptr<IOBuffer> buffer(new IOBuffer(kTestDataSize)); + + EXPECT_CALL(channel_, SendPacket(buffer->data(), kTestDataSize, 0)) + .Times(1) + .WillOnce(Return(SOCKET_ERROR)); + + EXPECT_CALL(channel_, GetError()) + .WillOnce(Return(EWOULDBLOCK)); + + int result = target_->Write(buffer.get(), kTestDataSize, callback_); + ASSERT_EQ(net::OK, result); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/chrome_async_socket.cc b/chromium/jingle/glue/chrome_async_socket.cc new file mode 100644 index 00000000000..c14fb99b11e --- /dev/null +++ b/chromium/jingle/glue/chrome_async_socket.cc @@ -0,0 +1,450 @@ +// Copyright (c) 2012 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/glue/chrome_async_socket.h" + +#include <algorithm> +#include <cstdlib> +#include <cstring> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/resolving_client_socket_factory.h" +#include "net/base/address_list.h" +#include "net/base/host_port_pair.h" +#include "net/base/io_buffer.h" +#include "net/base/net_util.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/ssl_client_socket.h" +#include "net/socket/tcp_client_socket.h" +#include "net/ssl/ssl_config_service.h" +#include "third_party/libjingle/source/talk/base/socketaddress.h" + +namespace jingle_glue { + +ChromeAsyncSocket::ChromeAsyncSocket( + ResolvingClientSocketFactory* resolving_client_socket_factory, + size_t read_buf_size, + size_t write_buf_size) + : weak_ptr_factory_(this), + resolving_client_socket_factory_(resolving_client_socket_factory), + state_(STATE_CLOSED), + error_(ERROR_NONE), + net_error_(net::OK), + 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(resolving_client_socket_factory_.get()); + 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)); +} + +// 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.hostname().empty() || address.port() == 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(!weak_ptr_factory_.HasWeakPtrs()); + weak_ptr_factory_.InvalidateWeakPtrs(); + + net::HostPortPair dest_host_port_pair(address.hostname(), address.port()); + + transport_socket_ = + resolving_client_socket_factory_->CreateTransportClientSocket( + dest_host_port_pair); + int status = transport_socket_->Connect( + base::Bind(&ChromeAsyncSocket::ProcessConnectDone, + weak_ptr_factory_.GetWeakPtr())); + 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. + base::MessageLoop* message_loop = base::MessageLoop::current(); + CHECK(message_loop); + message_loop->PostTask( + FROM_HERE, + base::Bind(&ChromeAsyncSocket::ProcessConnectDone, + weak_ptr_factory_.GetWeakPtr(), 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); + base::MessageLoop* message_loop = base::MessageLoop::current(); + CHECK(message_loop); + message_loop->PostTask( + FROM_HERE, + base::Bind(&ChromeAsyncSocket::DoRead, + weak_ptr_factory_.GetWeakPtr())); + 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(), + base::Bind(&ChromeAsyncSocket::ProcessReadDone, + weak_ptr_factory_.GetWeakPtr())); + 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); + base::MessageLoop* message_loop = base::MessageLoop::current(); + CHECK(message_loop); + message_loop->PostTask( + FROM_HERE, + base::Bind(&ChromeAsyncSocket::DoWrite, + weak_ptr_factory_.GetWeakPtr())); + 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_, + base::Bind(&ChromeAsyncSocket::ProcessWriteDone, + weak_ptr_factory_.GetWeakPtr())); + 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() { + weak_ptr_factory_.InvalidateWeakPtrs(); + 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. + weak_ptr_factory_.InvalidateWeakPtrs(); + + DCHECK(transport_socket_.get()); + scoped_ptr<net::ClientSocketHandle> socket_handle( + new net::ClientSocketHandle()); + socket_handle->SetSocket(transport_socket_.Pass()); + transport_socket_ = + resolving_client_socket_factory_->CreateSSLClientSocket( + socket_handle.Pass(), net::HostPortPair(domain_name, 443)); + int status = transport_socket_->Connect( + base::Bind(&ChromeAsyncSocket::ProcessSSLConnectDone, + weak_ptr_factory_.GetWeakPtr())); + if (status != net::ERR_IO_PENDING) { + base::MessageLoop* message_loop = base::MessageLoop::current(); + CHECK(message_loop); + message_loop->PostTask( + FROM_HERE, + base::Bind(&ChromeAsyncSocket::ProcessSSLConnectDone, + weak_ptr_factory_.GetWeakPtr(), 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 jingle_glue diff --git a/chromium/jingle/glue/chrome_async_socket.h b/chromium/jingle/glue/chrome_async_socket.h new file mode 100644 index 00000000000..1037d24c00b --- /dev/null +++ b/chromium/jingle/glue/chrome_async_socket.h @@ -0,0 +1,213 @@ +// Copyright (c) 2012 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_GLUE_CHROME_ASYNC_SOCKET_H_ +#define JINGLE_GLUE_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/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "third_party/libjingle/source/talk/xmpp/asyncsocket.h" + +namespace net { +class IOBufferWithSize; +class StreamSocket; +} // namespace net + +namespace jingle_glue { + +class ResolvingClientSocketFactory; + +class ChromeAsyncSocket : public buzz::AsyncSocket { + public: + // Takes ownership of |resolving_client_socket_factory|. + ChromeAsyncSocket( + ResolvingClientSocketFactory* resolving_client_socket_factory, + size_t read_buf_size, + size_t write_buf_size); + + // 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() OVERRIDE; + + // 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() OVERRIDE; + + // GetError() (which is of type net::Error) != net::OK only when + // error() == ERROR_WINSOCK. + virtual int GetError() OVERRIDE; + + // Tries to connect to the given address. + // + // If state() is not STATE_CLOSED, sets error to ERROR_WRONGSTATE + // and returns false. + // + // If |address| has an empty hostname or a zero port, sets error to + // ERROR_DNS and returns false. (We don't use the IP address even + // if it's present, as DNS resolution is done by + // |resolving_client_socket_factory_|. But it's perfectly fine if + // the hostname is a stringified IP address.) + // + // 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) OVERRIDE; + + // 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) OVERRIDE; + + // 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) OVERRIDE; + + // If the socket is not already closed, closes the socket and raises + // SignalClosed. Always returns true. + virtual bool Close() OVERRIDE; + + // 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) OVERRIDE; + + // 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(); + + base::WeakPtrFactory<ChromeAsyncSocket> weak_ptr_factory_; + scoped_ptr<ResolvingClientSocketFactory> resolving_client_socket_factory_; + + // buzz::AsyncSocket state. + buzz::AsyncSocket::State state_; + buzz::AsyncSocket::Error error_; + net::Error net_error_; + + // NULL iff state() == STATE_CLOSED. + scoped_ptr<net::StreamSocket> 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 jingle_glue + +#endif // JINGLE_GLUE_CHROME_ASYNC_SOCKET_H_ diff --git a/chromium/jingle/glue/chrome_async_socket_unittest.cc b/chromium/jingle/glue/chrome_async_socket_unittest.cc new file mode 100644 index 00000000000..db3d2b09c9f --- /dev/null +++ b/chromium/jingle/glue/chrome_async_socket_unittest.cc @@ -0,0 +1,1083 @@ +// Copyright (c) 2012 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/glue/chrome_async_socket.h" + +#include <deque> +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/resolving_client_socket_factory.h" +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/cert/mock_cert_verifier.h" +#include "net/http/transport_security_state.h" +#include "net/socket/socket_test_util.h" +#include "net/socket/ssl_client_socket.h" +#include "net/ssl/ssl_config_service.h" +#include "net/url_request/url_request_context_getter.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libjingle/source/talk/base/ipaddress.h" +#include "third_party/libjingle/source/talk/base/sigslot.h" +#include "third_party/libjingle/source/talk/base/socketaddress.h" + +namespace jingle_glue { + +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() OVERRIDE { + if (reads_.empty()) { + DCHECK(!has_pending_read_); + has_pending_read_ = true; + const net::MockRead pending_read(net::SYNCHRONOUS, 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) OVERRIDE { + DCHECK(!writes_.empty()); + net::MockWrite mock_write = writes_.front(); + writes_.pop_front(); + if (mock_write.result != net::OK) { + return net::MockWriteResult(mock_write.mode, 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(net::SYNCHRONOUS, net::ERR_UNEXPECTED); + } + return net::MockWriteResult(mock_write.mode, data.size()); + } + + // We ignore resets so we can pre-load the socket data provider with + // read/write events. + virtual void Reset() OVERRIDE {} + + // 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 MockXmppClientSocketFactory : public ResolvingClientSocketFactory { + public: + MockXmppClientSocketFactory( + net::ClientSocketFactory* mock_client_socket_factory, + const net::AddressList& address_list) + : mock_client_socket_factory_(mock_client_socket_factory), + address_list_(address_list), + cert_verifier_(new net::MockCertVerifier), + transport_security_state_(new net::TransportSecurityState) { + } + + // ResolvingClientSocketFactory implementation. + virtual scoped_ptr<net::StreamSocket> CreateTransportClientSocket( + const net::HostPortPair& host_and_port) OVERRIDE { + return mock_client_socket_factory_->CreateTransportClientSocket( + address_list_, NULL, net::NetLog::Source()); + } + + virtual scoped_ptr<net::SSLClientSocket> CreateSSLClientSocket( + scoped_ptr<net::ClientSocketHandle> transport_socket, + const net::HostPortPair& host_and_port) OVERRIDE { + net::SSLClientSocketContext context; + context.cert_verifier = cert_verifier_.get(); + context.transport_security_state = transport_security_state_.get(); + return mock_client_socket_factory_->CreateSSLClientSocket( + transport_socket.Pass(), host_and_port, ssl_config_, context); + } + + private: + scoped_ptr<net::ClientSocketFactory> mock_client_socket_factory_; + net::AddressList address_list_; + net::SSLConfig ssl_config_; + scoped_ptr<net::CertVerifier> cert_verifier_; + scoped_ptr<net::TransportSecurityState> transport_security_state_; +}; + +class ChromeAsyncSocketTest + : public testing::Test, + public sigslot::has_slots<> { + protected: + ChromeAsyncSocketTest() + : ssl_socket_data_provider_(net::ASYNC, net::OK), + addr_("localhost", 35) {} + + virtual ~ChromeAsyncSocketTest() {} + + virtual void SetUp() { + scoped_ptr<net::MockClientSocketFactory> mock_client_socket_factory( + new net::MockClientSocketFactory()); + mock_client_socket_factory->AddSocketDataProvider( + &async_socket_data_provider_); + mock_client_socket_factory->AddSSLSocketDataProvider( + &ssl_socket_data_provider_); + + // Fake DNS resolution for |addr_| and pass it to the factory. + net::IPAddressNumber resolved_addr; + EXPECT_TRUE(net::ParseIPLiteralToNumber("127.0.0.1", &resolved_addr)); + const net::AddressList address_list = + net::AddressList::CreateFromIPAddress(resolved_addr, addr_.port()); + scoped_ptr<MockXmppClientSocketFactory> mock_xmpp_client_socket_factory( + new MockXmppClientSocketFactory( + mock_client_socket_factory.release(), + address_list)); + chrome_async_socket_.reset( + new ChromeAsyncSocket(mock_xmpp_client_socket_factory.release(), + 14, 20)), + + 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_.RunUntilIdle(); + ExpectClosed(); + ExpectNoSignal(); + chrome_async_socket_.reset(); + } + + 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_.get())); + } + + 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(net::SYNCHRONOUS, net::OK)); + EXPECT_TRUE(chrome_async_socket_->Connect(addr_)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CONNECTING); + + message_loop_.RunUntilIdle(); + // 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_.RunUntilIdle(); + 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_ptr<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. + base::MessageLoop message_loop_; + + AsyncSocketDataProvider async_socket_data_provider_; + net::SSLSocketDataProvider ssl_socket_data_provider_; + + scoped_ptr<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, NoHostnameConnect) { + talk_base::IPAddress ip_address; + EXPECT_TRUE(talk_base::IPFromString("127.0.0.1", &ip_address)); + const talk_base::SocketAddress no_hostname_addr(ip_address, addr_.port()); + EXPECT_FALSE(chrome_async_socket_->Connect(no_hostname_addr)); + ExpectErrorState(ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_DNS); + + EXPECT_TRUE(chrome_async_socket_->Close()); + ExpectClosed(); +} + +TEST_F(ChromeAsyncSocketTest, ZeroPortConnect) { + const talk_base::SocketAddress zero_port_addr(addr_.hostname(), 0); + EXPECT_FALSE(chrome_async_socket_->Connect(zero_port_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_.RunUntilIdle(); +} + +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(net::ASYNC, net::OK)); + EXPECT_TRUE(chrome_async_socket_->Connect(addr_)); + ExpectNonErrorState(ChromeAsyncSocket::STATE_CONNECTING); + ExpectNoSignal(); + + message_loop_.RunUntilIdle(); + ExpectNonErrorState(ChromeAsyncSocket::STATE_OPEN); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CONNECT, ChromeAsyncSocket::STATE_OPEN)); + ExpectNoSignal(); + + message_loop_.RunUntilIdle(); + + 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(net::ASYNC, net::OK)); + EXPECT_TRUE(chrome_async_socket_->Connect(addr_)); + + message_loop_.RunUntilIdle(); + ExpectSignalSocketState( + SignalSocketState::NoError( + SIGNAL_CONNECT, ChromeAsyncSocket::STATE_OPEN)); + + DoCloseOpenedNoError(); + + message_loop_.RunUntilIdle(); +} + +TEST_F(ChromeAsyncSocketTest, PendingConnectError) { + async_socket_data_provider_.set_connect_data( + net::MockConnect(net::ASYNC, net::ERR_TIMED_OUT)); + EXPECT_TRUE(chrome_async_socket_->Connect(addr_)); + + message_loop_.RunUntilIdle(); + + 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(net::ASYNC, 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_CONNECTING, + ChromeAsyncSocket::ERROR_WRONGSTATE); + EXPECT_TRUE(chrome_async_socket_->Close()); + ExpectSignalSocketState( + SignalSocketState( + SIGNAL_CLOSE, ChromeAsyncSocket::STATE_CLOSED, + ChromeAsyncSocket::ERROR_WRONGSTATE, net::OK)); + }, "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_.RunUntilIdle(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, ReadTwice) { + async_socket_data_provider_.AddRead(net::MockRead(kReadData)); + DoOpenClosed(); + + ExpectReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunUntilIdle(); + + 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_.RunUntilIdle(); + + async_socket_data_provider_.AddRead( + net::MockRead(net::SYNCHRONOUS, 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_.RunUntilIdle(); + + 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(net::ASYNC, 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(net::SYNCHRONOUS, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::SYNCHRONOUS, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::SYNCHRONOUS, + kWriteData + 8, arraysize(kWriteData) - 8)); + DoOpenClosed(); + + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData, 3)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 3, 5)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunUntilIdle(); + + ExpectNoSignal(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, AsyncWrite) { + DoOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData + 8, arraysize(kWriteData) - 8)); + + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData, 3)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 3, 5)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunUntilIdle(); + + ExpectNoSignal(); + + DoCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, AsyncWriteError) { + DoOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, net::ERR_TIMED_OUT)); + + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData, 3)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 3, 5)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunUntilIdle(); + + 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_.RunUntilIdle(); + 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_.RunUntilIdle(); + 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(net::ASYNC, 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_.RunUntilIdle(); + 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_.RunUntilIdle(); + 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(net::ASYNC, 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_.RunUntilIdle(); + ExpectSSLConnectSignal(); + ExpectNoSignal(); + + message_loop_.RunUntilIdle(); + + 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(net::ASYNC, kWriteData, 3)); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData, 3)); + + EXPECT_FALSE(chrome_async_socket_->StartTls("fakedomain.com")); + + message_loop_.RunUntilIdle(); + + 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_.RunUntilIdle(); + + ExpectSSLReadSignal(); + ExpectNoSignal(); + + EXPECT_EQ(kReadData, DrainRead(1)); + + message_loop_.RunUntilIdle(); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, SSLSyncWrite) { + async_socket_data_provider_.AddWrite( + net::MockWrite(net::SYNCHRONOUS, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::SYNCHRONOUS, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::SYNCHRONOUS, + kWriteData + 8, arraysize(kWriteData) - 8)); + DoSSLOpenClosed(); + + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData, 3)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 3, 5)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunUntilIdle(); + + ExpectNoSignal(); + + DoSSLCloseOpenedNoError(); +} + +TEST_F(ChromeAsyncSocketTest, SSLAsyncWrite) { + DoSSLOpenClosed(); + + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData, 3)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData + 3, 5)); + async_socket_data_provider_.AddWrite( + net::MockWrite(net::ASYNC, kWriteData + 8, arraysize(kWriteData) - 8)); + + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData, 3)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 3, 5)); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(chrome_async_socket_->Write(kWriteData + 8, + arraysize(kWriteData) - 8)); + message_loop_.RunUntilIdle(); + + ExpectNoSignal(); + + DoSSLCloseOpenedNoError(); +} + +} // namespace + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/fake_network_manager.cc b/chromium/jingle/glue/fake_network_manager.cc new file mode 100644 index 00000000000..9694ee97114 --- /dev/null +++ b/chromium/jingle/glue/fake_network_manager.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 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/glue/fake_network_manager.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/utils.h" +#include "net/base/ip_endpoint.h" +#include "third_party/libjingle/source/talk/base/socketaddress.h" + +namespace jingle_glue { + +FakeNetworkManager::FakeNetworkManager(const net::IPAddressNumber& address) + : started_(false), + weak_factory_(this) { + net::IPEndPoint endpoint(address, 0); + talk_base::SocketAddress socket_address; + CHECK(IPEndPointToSocketAddress(endpoint, &socket_address)); + network_.reset(new talk_base::Network("fake", "Fake Network", + socket_address.ipaddr(), 32)); + network_->AddIP(socket_address.ipaddr()); +} + +FakeNetworkManager::~FakeNetworkManager() { +} + +void FakeNetworkManager::StartUpdating() { + started_ = true; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FakeNetworkManager::SendNetworksChangedSignal, + weak_factory_.GetWeakPtr())); +} + +void FakeNetworkManager::StopUpdating() { + started_ = false; +} + +void FakeNetworkManager::GetNetworks(NetworkList* networks) const { + networks->clear(); + networks->push_back(network_.get()); +} + +void FakeNetworkManager::SendNetworksChangedSignal() { + SignalNetworksChanged(); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/fake_network_manager.h b/chromium/jingle/glue/fake_network_manager.h new file mode 100644 index 00000000000..01e2f9105cd --- /dev/null +++ b/chromium/jingle/glue/fake_network_manager.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_GLUE_FAKE_NETWORK_MANAGER_H_ +#define JINGLE_GLUE_FAKE_NETWORK_MANAGER_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "net/base/net_util.h" +#include "third_party/libjingle/source/talk/base/network.h" + +namespace jingle_glue { + +// FakeNetworkManager always returns one interface with the IP address +// specified in the constructor. +class FakeNetworkManager : public talk_base::NetworkManager { + public: + FakeNetworkManager(const net::IPAddressNumber& address); + virtual ~FakeNetworkManager(); + + // talk_base::NetworkManager interface. + virtual void StartUpdating() OVERRIDE; + virtual void StopUpdating() OVERRIDE; + virtual void GetNetworks(NetworkList* networks) const OVERRIDE; + + protected: + void SendNetworksChangedSignal(); + + bool started_; + scoped_ptr<talk_base::Network> network_; + + base::WeakPtrFactory<FakeNetworkManager> weak_factory_; +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_FAKE_NETWORK_MANAGER_H_ diff --git a/chromium/jingle/glue/fake_socket_factory.cc b/chromium/jingle/glue/fake_socket_factory.cc new file mode 100644 index 00000000000..c5465a6f02a --- /dev/null +++ b/chromium/jingle/glue/fake_socket_factory.cc @@ -0,0 +1,195 @@ +// Copyright (c) 2011 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/glue/fake_socket_factory.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/utils.h" +#include "third_party/libjingle/source/talk/base/asyncsocket.h" + +namespace jingle_glue { + +FakeUDPPacketSocket::FakeUDPPacketSocket(FakeSocketManager* fake_socket_manager, + const net::IPEndPoint& address) + : fake_socket_manager_(fake_socket_manager), + endpoint_(address), state_(IS_OPEN), error_(0) { + CHECK(IPEndPointToSocketAddress(endpoint_, &local_address_)); + fake_socket_manager_->AddSocket(this); +} + +FakeUDPPacketSocket::~FakeUDPPacketSocket() { + fake_socket_manager_->RemoveSocket(this); +} + +talk_base::SocketAddress FakeUDPPacketSocket::GetLocalAddress() const { + DCHECK(CalledOnValidThread()); + return local_address_; +} + +talk_base::SocketAddress FakeUDPPacketSocket::GetRemoteAddress() const { + DCHECK(CalledOnValidThread()); + return remote_address_; +} + +int FakeUDPPacketSocket::Send(const void *data, size_t data_size) { + DCHECK(CalledOnValidThread()); + return SendTo(data, data_size, remote_address_); +} + +int FakeUDPPacketSocket::SendTo(const void *data, size_t data_size, + const talk_base::SocketAddress& address) { + DCHECK(CalledOnValidThread()); + + if (state_ == IS_CLOSED) { + return ENOTCONN; + } + + net::IPEndPoint destination; + if (!SocketAddressToIPEndPoint(address, &destination)) { + return EINVAL; + } + + const char* data_char = reinterpret_cast<const char*>(data); + std::vector<char> data_vector(data_char, data_char + data_size); + + fake_socket_manager_->SendPacket(endpoint_, destination, data_vector); + + return data_size; +} + +int FakeUDPPacketSocket::Close() { + DCHECK(CalledOnValidThread()); + state_ = IS_CLOSED; + return 0; +} + +talk_base::AsyncPacketSocket::State FakeUDPPacketSocket::GetState() const { + DCHECK(CalledOnValidThread()); + + switch (state_) { + case IS_OPEN: + return STATE_BOUND; + case IS_CLOSED: + return STATE_CLOSED; + } + + NOTREACHED(); + return STATE_CLOSED; +} + +int FakeUDPPacketSocket::GetOption(talk_base::Socket::Option opt, int* value) { + DCHECK(CalledOnValidThread()); + return -1; +} + +int FakeUDPPacketSocket::SetOption(talk_base::Socket::Option opt, int value) { + DCHECK(CalledOnValidThread()); + return -1; +} + +int FakeUDPPacketSocket::GetError() const { + DCHECK(CalledOnValidThread()); + return error_; +} + +void FakeUDPPacketSocket::SetError(int error) { + DCHECK(CalledOnValidThread()); + error_ = error; +} + +void FakeUDPPacketSocket::DeliverPacket(const net::IPEndPoint& from, + const std::vector<char>& data) { + DCHECK(CalledOnValidThread()); + + talk_base::SocketAddress address; + if (!jingle_glue::IPEndPointToSocketAddress(from, &address)) { + // We should always be able to convert address here because we + // don't expect IPv6 address on IPv4 connections. + NOTREACHED(); + return; + } + + SignalReadPacket(this, &data[0], data.size(), address); +} + +FakeSocketManager::FakeSocketManager() + : message_loop_(base::MessageLoop::current()) {} + +FakeSocketManager::~FakeSocketManager() { } + +void FakeSocketManager::SendPacket(const net::IPEndPoint& from, + const net::IPEndPoint& to, + const std::vector<char>& data) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + + message_loop_->PostTask( + FROM_HERE, + base::Bind(&FakeSocketManager::DeliverPacket, this, from, to, data)); +} + +void FakeSocketManager::DeliverPacket(const net::IPEndPoint& from, + const net::IPEndPoint& to, + const std::vector<char>& data) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + + std::map<net::IPEndPoint, FakeUDPPacketSocket*>::iterator it = + endpoints_.find(to); + if (it == endpoints_.end()) { + LOG(WARNING) << "Dropping packet with unknown destination: " + << to.ToString(); + return; + } + it->second->DeliverPacket(from, data); +} + +void FakeSocketManager::AddSocket(FakeUDPPacketSocket* socket_factory) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + + endpoints_[socket_factory->endpoint()] = socket_factory; +} + +void FakeSocketManager::RemoveSocket(FakeUDPPacketSocket* socket_factory) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_); + + endpoints_.erase(socket_factory->endpoint()); +} + +FakeSocketFactory::FakeSocketFactory(FakeSocketManager* socket_manager, + const net::IPAddressNumber& address) + : socket_manager_(socket_manager), + address_(address), + last_allocated_port_(0) { +} + +FakeSocketFactory::~FakeSocketFactory() { +} + +talk_base::AsyncPacketSocket* FakeSocketFactory::CreateUdpSocket( + const talk_base::SocketAddress& local_address, int min_port, int max_port) { + CHECK_EQ(min_port, 0); + CHECK_EQ(max_port, 0); + return new FakeUDPPacketSocket( + socket_manager_.get(), net::IPEndPoint(address_, ++last_allocated_port_)); +} + +talk_base::AsyncPacketSocket* FakeSocketFactory::CreateServerTcpSocket( + const talk_base::SocketAddress& local_address, int min_port, int max_port, + int opts) { + // TODO(sergeyu): Implement fake TCP sockets. + NOTIMPLEMENTED(); + return NULL; +} + +talk_base::AsyncPacketSocket* FakeSocketFactory::CreateClientTcpSocket( + const talk_base::SocketAddress& local_address, + const talk_base::SocketAddress& remote_address, + const talk_base::ProxyInfo& proxy_info, const std::string& user_agent, + int opts) { + // TODO(sergeyu): Implement fake TCP sockets. + NOTIMPLEMENTED(); + return NULL; +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/fake_socket_factory.h b/chromium/jingle/glue/fake_socket_factory.h new file mode 100644 index 00000000000..cb56a3c7818 --- /dev/null +++ b/chromium/jingle/glue/fake_socket_factory.h @@ -0,0 +1,123 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_GLUE_FAKE_SOCKET_FACTORY_H_ +#define JINGLE_GLUE_FAKE_SOCKET_FACTORY_H_ + +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "net/base/ip_endpoint.h" +#include "third_party/libjingle/source/talk/base/asyncpacketsocket.h" +#include "third_party/libjingle/source/talk/p2p/base/packetsocketfactory.h" + +namespace base { +class MessageLoop; +} + +namespace jingle_glue { + +class FakeSocketManager; + +class FakeUDPPacketSocket : public talk_base::AsyncPacketSocket, + public base::NonThreadSafe { + public: + FakeUDPPacketSocket(FakeSocketManager* fake_socket_manager, + const net::IPEndPoint& address); + virtual ~FakeUDPPacketSocket(); + + const net::IPEndPoint& endpoint() const { return endpoint_; } + void DeliverPacket(const net::IPEndPoint& from, + const std::vector<char>& data); + + // talk_base::AsyncPacketSocket implementation. + virtual talk_base::SocketAddress GetLocalAddress() const OVERRIDE; + virtual talk_base::SocketAddress GetRemoteAddress() const OVERRIDE; + virtual int Send(const void *pv, size_t cb) OVERRIDE; + virtual int SendTo(const void *pv, size_t cb, + const talk_base::SocketAddress& addr) OVERRIDE; + virtual int Close() OVERRIDE; + virtual State GetState() const OVERRIDE; + virtual int GetOption(talk_base::Socket::Option opt, int* value) OVERRIDE; + virtual int SetOption(talk_base::Socket::Option opt, int value) OVERRIDE; + virtual int GetError() const OVERRIDE; + virtual void SetError(int error) OVERRIDE; + + private: + enum InternalState { + IS_OPEN, + IS_CLOSED, + }; + + scoped_refptr<FakeSocketManager> fake_socket_manager_; + net::IPEndPoint endpoint_; + talk_base::SocketAddress local_address_; + talk_base::SocketAddress remote_address_; + InternalState state_; + int error_; + + DISALLOW_COPY_AND_ASSIGN(FakeUDPPacketSocket); +}; + +class FakeSocketManager : public base::RefCountedThreadSafe<FakeSocketManager> { + public: + FakeSocketManager(); + + void SendPacket(const net::IPEndPoint& from, + const net::IPEndPoint& to, + const std::vector<char>& data); + + void AddSocket(FakeUDPPacketSocket* socket_factory); + void RemoveSocket(FakeUDPPacketSocket* socket_factory); + + private: + friend class base::RefCountedThreadSafe<FakeSocketManager>; + + ~FakeSocketManager(); + + void DeliverPacket(const net::IPEndPoint& from, + const net::IPEndPoint& to, + const std::vector<char>& data); + + base::MessageLoop* message_loop_; + std::map<net::IPEndPoint, FakeUDPPacketSocket*> endpoints_; + + DISALLOW_COPY_AND_ASSIGN(FakeSocketManager); +}; + +class FakeSocketFactory : public talk_base::PacketSocketFactory { + public: + FakeSocketFactory(FakeSocketManager* socket_manager, + const net::IPAddressNumber& address); + virtual ~FakeSocketFactory(); + + // talk_base::PacketSocketFactory implementation. + virtual talk_base::AsyncPacketSocket* CreateUdpSocket( + const talk_base::SocketAddress& local_address, + int min_port, int max_port) OVERRIDE; + virtual talk_base::AsyncPacketSocket* CreateServerTcpSocket( + const talk_base::SocketAddress& local_address, int min_port, int max_port, + int opts) OVERRIDE; + virtual talk_base::AsyncPacketSocket* CreateClientTcpSocket( + const talk_base::SocketAddress& local_address, + const talk_base::SocketAddress& remote_address, + const talk_base::ProxyInfo& proxy_info, + const std::string& user_agent, + int opts) OVERRIDE; + + private: + scoped_refptr<FakeSocketManager> socket_manager_; + net::IPAddressNumber address_; + int last_allocated_port_; + + DISALLOW_COPY_AND_ASSIGN(FakeSocketFactory); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_FAKE_SOCKET_FACTORY_H_ diff --git a/chromium/jingle/glue/fake_ssl_client_socket.cc b/chromium/jingle/glue/fake_ssl_client_socket.cc new file mode 100644 index 00000000000..9d722c7861b --- /dev/null +++ b/chromium/jingle/glue/fake_ssl_client_socket.cc @@ -0,0 +1,347 @@ +// Copyright (c) 2012 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/glue/fake_ssl_client_socket.h" + +#include <cstdlib> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace jingle_glue { + +namespace { + +// The constants below were taken from libjingle's socketadapters.cc. +// Basically, we do a "fake" SSL handshake to fool proxies into +// thinking this is a real SSL connection. + +// This is a SSL v2 CLIENT_HELLO message. +// TODO(juberti): Should this have a session id? The response doesn't have a +// certificate, so the hello should have a session id. +static const uint8 kSslClientHello[] = { + 0x80, 0x46, // msg len + 0x01, // CLIENT_HELLO + 0x03, 0x01, // SSL 3.1 + 0x00, 0x2d, // ciphersuite len + 0x00, 0x00, // session id len + 0x00, 0x10, // challenge len + 0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0, // ciphersuites + 0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80, // + 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a, // + 0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64, // + 0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, // + 0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc, // challenge + 0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea // +}; + +// This is a TLSv1 SERVER_HELLO message. +static const uint8 kSslServerHello[] = { + 0x16, // handshake message + 0x03, 0x01, // SSL 3.1 + 0x00, 0x4a, // message len + 0x02, // SERVER_HELLO + 0x00, 0x00, 0x46, // handshake len + 0x03, 0x01, // SSL 3.1 + 0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0, // server random + 0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f, // + 0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1, // + 0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f, // + 0x20, // session id len + 0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f, // session id + 0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b, // + 0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38, // + 0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c, // + 0x00, 0x04, // RSA/RC4-128/MD5 + 0x00 // null compression +}; + +net::DrainableIOBuffer* NewDrainableIOBufferWithSize(int size) { + return new net::DrainableIOBuffer(new net::IOBuffer(size), size); +} + +} // namespace + +base::StringPiece FakeSSLClientSocket::GetSslClientHello() { + return base::StringPiece(reinterpret_cast<const char*>(kSslClientHello), + arraysize(kSslClientHello)); +} + +base::StringPiece FakeSSLClientSocket::GetSslServerHello() { + return base::StringPiece(reinterpret_cast<const char*>(kSslServerHello), + arraysize(kSslServerHello)); +} + +FakeSSLClientSocket::FakeSSLClientSocket( + scoped_ptr<net::StreamSocket> transport_socket) + : transport_socket_(transport_socket.Pass()), + next_handshake_state_(STATE_NONE), + handshake_completed_(false), + write_buf_(NewDrainableIOBufferWithSize(arraysize(kSslClientHello))), + read_buf_(NewDrainableIOBufferWithSize(arraysize(kSslServerHello))) { + CHECK(transport_socket_.get()); + std::memcpy(write_buf_->data(), kSslClientHello, arraysize(kSslClientHello)); +} + +FakeSSLClientSocket::~FakeSSLClientSocket() {} + +int FakeSSLClientSocket::Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK_EQ(next_handshake_state_, STATE_NONE); + DCHECK(handshake_completed_); + return transport_socket_->Read(buf, buf_len, callback); +} + +int FakeSSLClientSocket::Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK_EQ(next_handshake_state_, STATE_NONE); + DCHECK(handshake_completed_); + return transport_socket_->Write(buf, buf_len, callback); +} + +bool FakeSSLClientSocket::SetReceiveBufferSize(int32 size) { + return transport_socket_->SetReceiveBufferSize(size); +} + +bool FakeSSLClientSocket::SetSendBufferSize(int32 size) { + return transport_socket_->SetSendBufferSize(size); +} + +int FakeSSLClientSocket::Connect(const net::CompletionCallback& callback) { + // We don't support synchronous operation, even if + // |transport_socket_| does. + DCHECK(!callback.is_null()); + DCHECK_EQ(next_handshake_state_, STATE_NONE); + DCHECK(!handshake_completed_); + DCHECK(user_connect_callback_.is_null()); + DCHECK_EQ(write_buf_->BytesConsumed(), 0); + DCHECK_EQ(read_buf_->BytesConsumed(), 0); + + next_handshake_state_ = STATE_CONNECT; + int status = DoHandshakeLoop(); + if (status == net::ERR_IO_PENDING) + user_connect_callback_ = callback; + + return status; +} + +int FakeSSLClientSocket::DoHandshakeLoop() { + DCHECK_NE(next_handshake_state_, STATE_NONE); + int status = net::OK; + do { + HandshakeState state = next_handshake_state_; + next_handshake_state_ = STATE_NONE; + switch (state) { + case STATE_CONNECT: + status = DoConnect(); + break; + case STATE_SEND_CLIENT_HELLO: + status = DoSendClientHello(); + break; + case STATE_VERIFY_SERVER_HELLO: + status = DoVerifyServerHello(); + break; + default: + status = net::ERR_UNEXPECTED; + LOG(DFATAL) << "unexpected state: " << state; + break; + } + } while ((status != net::ERR_IO_PENDING) && + (next_handshake_state_ != STATE_NONE)); + return status; +} + +void FakeSSLClientSocket::RunUserConnectCallback(int status) { + DCHECK_LE(status, net::OK); + next_handshake_state_ = STATE_NONE; + net::CompletionCallback user_connect_callback = user_connect_callback_; + user_connect_callback_.Reset(); + user_connect_callback.Run(status); +} + +void FakeSSLClientSocket::DoHandshakeLoopWithUserConnectCallback() { + int status = DoHandshakeLoop(); + if (status != net::ERR_IO_PENDING) { + RunUserConnectCallback(status); + } +} + +int FakeSSLClientSocket::DoConnect() { + int status = transport_socket_->Connect( + base::Bind(&FakeSSLClientSocket::OnConnectDone, base::Unretained(this))); + if (status != net::OK) { + return status; + } + ProcessConnectDone(); + return net::OK; +} + +void FakeSSLClientSocket::OnConnectDone(int status) { + DCHECK_NE(status, net::ERR_IO_PENDING); + DCHECK_LE(status, net::OK); + DCHECK(!user_connect_callback_.is_null()); + if (status != net::OK) { + RunUserConnectCallback(status); + return; + } + ProcessConnectDone(); + DoHandshakeLoopWithUserConnectCallback(); +} + +void FakeSSLClientSocket::ProcessConnectDone() { + DCHECK_EQ(write_buf_->BytesConsumed(), 0); + DCHECK_EQ(read_buf_->BytesConsumed(), 0); + next_handshake_state_ = STATE_SEND_CLIENT_HELLO; +} + +int FakeSSLClientSocket::DoSendClientHello() { + int status = transport_socket_->Write( + write_buf_.get(), + write_buf_->BytesRemaining(), + base::Bind(&FakeSSLClientSocket::OnSendClientHelloDone, + base::Unretained(this))); + if (status < net::OK) { + return status; + } + ProcessSendClientHelloDone(static_cast<size_t>(status)); + return net::OK; +} + +void FakeSSLClientSocket::OnSendClientHelloDone(int status) { + DCHECK_NE(status, net::ERR_IO_PENDING); + DCHECK(!user_connect_callback_.is_null()); + if (status < net::OK) { + RunUserConnectCallback(status); + return; + } + ProcessSendClientHelloDone(static_cast<size_t>(status)); + DoHandshakeLoopWithUserConnectCallback(); +} + +void FakeSSLClientSocket::ProcessSendClientHelloDone(size_t written) { + DCHECK_LE(written, static_cast<size_t>(write_buf_->BytesRemaining())); + DCHECK_EQ(read_buf_->BytesConsumed(), 0); + if (written < static_cast<size_t>(write_buf_->BytesRemaining())) { + next_handshake_state_ = STATE_SEND_CLIENT_HELLO; + write_buf_->DidConsume(written); + } else { + next_handshake_state_ = STATE_VERIFY_SERVER_HELLO; + } +} + +int FakeSSLClientSocket::DoVerifyServerHello() { + int status = transport_socket_->Read( + read_buf_.get(), + read_buf_->BytesRemaining(), + base::Bind(&FakeSSLClientSocket::OnVerifyServerHelloDone, + base::Unretained(this))); + if (status < net::OK) { + return status; + } + size_t read = static_cast<size_t>(status); + return ProcessVerifyServerHelloDone(read); +} + +void FakeSSLClientSocket::OnVerifyServerHelloDone(int status) { + DCHECK_NE(status, net::ERR_IO_PENDING); + DCHECK(!user_connect_callback_.is_null()); + if (status < net::OK) { + RunUserConnectCallback(status); + return; + } + size_t read = static_cast<size_t>(status); + status = ProcessVerifyServerHelloDone(read); + if (status < net::OK) { + RunUserConnectCallback(status); + return; + } + if (handshake_completed_) { + RunUserConnectCallback(net::OK); + } else { + DoHandshakeLoopWithUserConnectCallback(); + } +} + +net::Error FakeSSLClientSocket::ProcessVerifyServerHelloDone(size_t read) { + DCHECK_LE(read, static_cast<size_t>(read_buf_->BytesRemaining())); + if (read == 0U) { + return net::ERR_UNEXPECTED; + } + const uint8* expected_data_start = + &kSslServerHello[arraysize(kSslServerHello) - + read_buf_->BytesRemaining()]; + if (std::memcmp(expected_data_start, read_buf_->data(), read) != 0) { + return net::ERR_UNEXPECTED; + } + if (read < static_cast<size_t>(read_buf_->BytesRemaining())) { + next_handshake_state_ = STATE_VERIFY_SERVER_HELLO; + read_buf_->DidConsume(read); + } else { + next_handshake_state_ = STATE_NONE; + handshake_completed_ = true; + } + return net::OK; +} + +void FakeSSLClientSocket::Disconnect() { + transport_socket_->Disconnect(); + next_handshake_state_ = STATE_NONE; + handshake_completed_ = false; + user_connect_callback_.Reset(); + write_buf_->SetOffset(0); + read_buf_->SetOffset(0); +} + +bool FakeSSLClientSocket::IsConnected() const { + return handshake_completed_ && transport_socket_->IsConnected(); +} + +bool FakeSSLClientSocket::IsConnectedAndIdle() const { + return handshake_completed_ && transport_socket_->IsConnectedAndIdle(); +} + +int FakeSSLClientSocket::GetPeerAddress(net::IPEndPoint* address) const { + return transport_socket_->GetPeerAddress(address); +} + +int FakeSSLClientSocket::GetLocalAddress(net::IPEndPoint* address) const { + return transport_socket_->GetLocalAddress(address); +} + +const net::BoundNetLog& FakeSSLClientSocket::NetLog() const { + return transport_socket_->NetLog(); +} + +void FakeSSLClientSocket::SetSubresourceSpeculation() { + transport_socket_->SetSubresourceSpeculation(); +} + +void FakeSSLClientSocket::SetOmniboxSpeculation() { + transport_socket_->SetOmniboxSpeculation(); +} + +bool FakeSSLClientSocket::WasEverUsed() const { + return transport_socket_->WasEverUsed(); +} + +bool FakeSSLClientSocket::UsingTCPFastOpen() const { + return transport_socket_->UsingTCPFastOpen(); +} + +bool FakeSSLClientSocket::WasNpnNegotiated() const { + return transport_socket_->WasNpnNegotiated(); +} + +net::NextProto FakeSSLClientSocket::GetNegotiatedProtocol() const { + return transport_socket_->GetNegotiatedProtocol(); +} + +bool FakeSSLClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) { + return transport_socket_->GetSSLInfo(ssl_info); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/fake_ssl_client_socket.h b/chromium/jingle/glue/fake_ssl_client_socket.h new file mode 100644 index 00000000000..54a9e2f2b1b --- /dev/null +++ b/chromium/jingle/glue/fake_ssl_client_socket.h @@ -0,0 +1,111 @@ +// Copyright (c) 2012 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. +// +// This StreamSocket implementation is to be used with servers that +// accept connections on port 443 but don't really use SSL. For +// example, the Google Talk servers do this to bypass proxies. (The +// connection is upgraded to TLS as part of the XMPP negotiation, so +// security is preserved.) A "fake" SSL handshake is done immediately +// after connection to fool proxies into thinking that this is a real +// SSL connection. +// +// NOTE: This StreamSocket implementation does *not* do a real SSL +// handshake nor does it do any encryption! + +#ifndef JINGLE_GLUE_FAKE_SSL_CLIENT_SOCKET_H_ +#define JINGLE_GLUE_FAKE_SSL_CLIENT_SOCKET_H_ + +#include <cstddef> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "net/socket/stream_socket.h" + +namespace net { +class DrainableIOBuffer; +class SSLInfo; +} // namespace net + +namespace jingle_glue { + +class FakeSSLClientSocket : public net::StreamSocket { + public: + explicit FakeSSLClientSocket(scoped_ptr<net::StreamSocket> transport_socket); + + virtual ~FakeSSLClientSocket(); + + // Exposed for testing. + static base::StringPiece GetSslClientHello(); + static base::StringPiece GetSslServerHello(); + + // net::StreamSocket implementation. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + virtual int Connect(const net::CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectedAndIdle() const OVERRIDE; + virtual int GetPeerAddress(net::IPEndPoint* address) const OVERRIDE; + virtual int GetLocalAddress(net::IPEndPoint* address) const OVERRIDE; + virtual const net::BoundNetLog& NetLog() const OVERRIDE; + virtual void SetSubresourceSpeculation() OVERRIDE; + virtual void SetOmniboxSpeculation() OVERRIDE; + virtual bool WasEverUsed() const OVERRIDE; + virtual bool UsingTCPFastOpen() const OVERRIDE; + virtual bool WasNpnNegotiated() const OVERRIDE; + virtual net::NextProto GetNegotiatedProtocol() const OVERRIDE; + virtual bool GetSSLInfo(net::SSLInfo* ssl_info) OVERRIDE; + + private: + enum HandshakeState { + STATE_NONE, + STATE_CONNECT, + STATE_SEND_CLIENT_HELLO, + STATE_VERIFY_SERVER_HELLO, + }; + + int DoHandshakeLoop(); + void RunUserConnectCallback(int status); + void DoHandshakeLoopWithUserConnectCallback(); + + int DoConnect(); + void OnConnectDone(int status); + void ProcessConnectDone(); + + int DoSendClientHello(); + void OnSendClientHelloDone(int status); + void ProcessSendClientHelloDone(size_t written); + + int DoVerifyServerHello(); + void OnVerifyServerHelloDone(int status); + net::Error ProcessVerifyServerHelloDone(size_t read); + + scoped_ptr<net::StreamSocket> transport_socket_; + + // During the handshake process, holds a value from HandshakeState. + // STATE_NONE otherwise. + HandshakeState next_handshake_state_; + + // True iff we're connected and we've finished the handshake. + bool handshake_completed_; + + // The callback passed to Connect(). + net::CompletionCallback user_connect_callback_; + + scoped_refptr<net::DrainableIOBuffer> write_buf_; + scoped_refptr<net::DrainableIOBuffer> read_buf_; +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_FAKE_SSL_CLIENT_SOCKET_H_ diff --git a/chromium/jingle/glue/fake_ssl_client_socket_unittest.cc b/chromium/jingle/glue/fake_ssl_client_socket_unittest.cc new file mode 100644 index 00000000000..f6d8fea5fb3 --- /dev/null +++ b/chromium/jingle/glue/fake_ssl_client_socket_unittest.cc @@ -0,0 +1,349 @@ +// Copyright (c) 2012 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/glue/fake_ssl_client_socket.h" + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/net_log.h" +#include "net/base/test_completion_callback.h" +#include "net/socket/socket_test_util.h" +#include "net/socket/stream_socket.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace jingle_glue { + +namespace { + +using ::testing::Return; +using ::testing::ReturnRef; + +// Used by RunUnsuccessfulHandshakeTestHelper. Represents where in +// the handshake step an error should be inserted. +enum HandshakeErrorLocation { + CONNECT_ERROR, + SEND_CLIENT_HELLO_ERROR, + VERIFY_SERVER_HELLO_ERROR, +}; + +// Private error codes appended to the net::Error set. +enum { + // An error representing a server hello that has been corrupted in + // transit. + ERR_MALFORMED_SERVER_HELLO = -15000, +}; + +// Used by PassThroughMethods test. +class MockClientSocket : public net::StreamSocket { + public: + virtual ~MockClientSocket() {} + + MOCK_METHOD3(Read, int(net::IOBuffer*, int, + const net::CompletionCallback&)); + MOCK_METHOD3(Write, int(net::IOBuffer*, int, + const net::CompletionCallback&)); + MOCK_METHOD1(SetReceiveBufferSize, bool(int32)); + MOCK_METHOD1(SetSendBufferSize, bool(int32)); + MOCK_METHOD1(Connect, int(const net::CompletionCallback&)); + MOCK_METHOD0(Disconnect, void()); + MOCK_CONST_METHOD0(IsConnected, bool()); + MOCK_CONST_METHOD0(IsConnectedAndIdle, bool()); + MOCK_CONST_METHOD1(GetPeerAddress, int(net::IPEndPoint*)); + MOCK_CONST_METHOD1(GetLocalAddress, int(net::IPEndPoint*)); + MOCK_CONST_METHOD0(NetLog, const net::BoundNetLog&()); + MOCK_METHOD0(SetSubresourceSpeculation, void()); + MOCK_METHOD0(SetOmniboxSpeculation, void()); + MOCK_CONST_METHOD0(WasEverUsed, bool()); + MOCK_CONST_METHOD0(UsingTCPFastOpen, bool()); + MOCK_CONST_METHOD0(NumBytesRead, int64()); + MOCK_CONST_METHOD0(GetConnectTimeMicros, base::TimeDelta()); + MOCK_CONST_METHOD0(WasNpnNegotiated, bool()); + MOCK_CONST_METHOD0(GetNegotiatedProtocol, net::NextProto()); + MOCK_METHOD1(GetSSLInfo, bool(net::SSLInfo*)); +}; + +// Break up |data| into a bunch of chunked MockReads/Writes and push +// them onto |ops|. +template <net::MockReadWriteType type> +void AddChunkedOps(base::StringPiece data, size_t chunk_size, net::IoMode mode, + std::vector<net::MockReadWrite<type> >* ops) { + DCHECK_GT(chunk_size, 0U); + size_t offset = 0; + while (offset < data.size()) { + size_t bounded_chunk_size = std::min(data.size() - offset, chunk_size); + ops->push_back(net::MockReadWrite<type>(mode, data.data() + offset, + bounded_chunk_size)); + offset += bounded_chunk_size; + } +} + +class FakeSSLClientSocketTest : public testing::Test { + protected: + FakeSSLClientSocketTest() {} + + virtual ~FakeSSLClientSocketTest() {} + + scoped_ptr<net::StreamSocket> MakeClientSocket() { + return mock_client_socket_factory_.CreateTransportClientSocket( + net::AddressList(), NULL, net::NetLog::Source()); + } + + void SetData(const net::MockConnect& mock_connect, + std::vector<net::MockRead>* reads, + std::vector<net::MockWrite>* writes) { + static_socket_data_provider_.reset( + new net::StaticSocketDataProvider( + reads->empty() ? NULL : &*reads->begin(), reads->size(), + writes->empty() ? NULL : &*writes->begin(), writes->size())); + static_socket_data_provider_->set_connect_data(mock_connect); + mock_client_socket_factory_.AddSocketDataProvider( + static_socket_data_provider_.get()); + } + + void ExpectStatus( + net::IoMode mode, int expected_status, int immediate_status, + net::TestCompletionCallback* test_completion_callback) { + if (mode == net::ASYNC) { + EXPECT_EQ(net::ERR_IO_PENDING, immediate_status); + int status = test_completion_callback->WaitForResult(); + EXPECT_EQ(expected_status, status); + } else { + EXPECT_EQ(expected_status, immediate_status); + } + } + + // Sets up the mock socket to generate a successful handshake + // (sliced up according to the parameters) and makes sure the + // FakeSSLClientSocket behaves as expected. + void RunSuccessfulHandshakeTest( + net::IoMode mode, size_t read_chunk_size, size_t write_chunk_size, + int num_resets) { + base::StringPiece ssl_client_hello = + FakeSSLClientSocket::GetSslClientHello(); + base::StringPiece ssl_server_hello = + FakeSSLClientSocket::GetSslServerHello(); + + net::MockConnect mock_connect(mode, net::OK); + std::vector<net::MockRead> reads; + std::vector<net::MockWrite> writes; + static const char kReadTestData[] = "read test data"; + static const char kWriteTestData[] = "write test data"; + for (int i = 0; i < num_resets + 1; ++i) { + SCOPED_TRACE(i); + AddChunkedOps(ssl_server_hello, read_chunk_size, mode, &reads); + AddChunkedOps(ssl_client_hello, write_chunk_size, mode, &writes); + reads.push_back( + net::MockRead(mode, kReadTestData, arraysize(kReadTestData))); + writes.push_back( + net::MockWrite(mode, kWriteTestData, arraysize(kWriteTestData))); + } + SetData(mock_connect, &reads, &writes); + + FakeSSLClientSocket fake_ssl_client_socket(MakeClientSocket()); + + for (int i = 0; i < num_resets + 1; ++i) { + SCOPED_TRACE(i); + net::TestCompletionCallback test_completion_callback; + int status = fake_ssl_client_socket.Connect( + test_completion_callback.callback()); + if (mode == net::ASYNC) { + EXPECT_FALSE(fake_ssl_client_socket.IsConnected()); + } + ExpectStatus(mode, net::OK, status, &test_completion_callback); + if (fake_ssl_client_socket.IsConnected()) { + int read_len = arraysize(kReadTestData); + int read_buf_len = 2 * read_len; + scoped_refptr<net::IOBuffer> read_buf( + new net::IOBuffer(read_buf_len)); + int read_status = fake_ssl_client_socket.Read( + read_buf.get(), read_buf_len, test_completion_callback.callback()); + ExpectStatus(mode, read_len, read_status, &test_completion_callback); + + scoped_refptr<net::IOBuffer> write_buf( + new net::StringIOBuffer(kWriteTestData)); + int write_status = + fake_ssl_client_socket.Write(write_buf.get(), + arraysize(kWriteTestData), + test_completion_callback.callback()); + ExpectStatus(mode, arraysize(kWriteTestData), write_status, + &test_completion_callback); + } else { + ADD_FAILURE(); + } + fake_ssl_client_socket.Disconnect(); + EXPECT_FALSE(fake_ssl_client_socket.IsConnected()); + } + } + + // Sets up the mock socket to generate an unsuccessful handshake + // FakeSSLClientSocket fails as expected. + void RunUnsuccessfulHandshakeTestHelper( + net::IoMode mode, int error, HandshakeErrorLocation location) { + DCHECK_NE(error, net::OK); + base::StringPiece ssl_client_hello = + FakeSSLClientSocket::GetSslClientHello(); + base::StringPiece ssl_server_hello = + FakeSSLClientSocket::GetSslServerHello(); + + net::MockConnect mock_connect(mode, net::OK); + std::vector<net::MockRead> reads; + std::vector<net::MockWrite> writes; + const size_t kChunkSize = 1; + AddChunkedOps(ssl_server_hello, kChunkSize, mode, &reads); + AddChunkedOps(ssl_client_hello, kChunkSize, mode, &writes); + switch (location) { + case CONNECT_ERROR: + mock_connect.result = error; + writes.clear(); + reads.clear(); + break; + case SEND_CLIENT_HELLO_ERROR: { + // Use a fixed index for repeatability. + size_t index = 100 % writes.size(); + writes[index].result = error; + writes[index].data = NULL; + writes[index].data_len = 0; + writes.resize(index + 1); + reads.clear(); + break; + } + case VERIFY_SERVER_HELLO_ERROR: { + // Use a fixed index for repeatability. + size_t index = 50 % reads.size(); + if (error == ERR_MALFORMED_SERVER_HELLO) { + static const char kBadData[] = "BAD_DATA"; + reads[index].data = kBadData; + reads[index].data_len = arraysize(kBadData); + } else { + reads[index].result = error; + reads[index].data = NULL; + reads[index].data_len = 0; + } + reads.resize(index + 1); + if (error == + net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) { + static const char kDummyData[] = "DUMMY"; + reads.push_back(net::MockRead(mode, kDummyData)); + } + break; + } + } + SetData(mock_connect, &reads, &writes); + + FakeSSLClientSocket fake_ssl_client_socket(MakeClientSocket()); + + // The two errors below are interpreted by FakeSSLClientSocket as + // an unexpected event. + int expected_status = + ((error == net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) || + (error == ERR_MALFORMED_SERVER_HELLO)) ? + net::ERR_UNEXPECTED : error; + + net::TestCompletionCallback test_completion_callback; + int status = fake_ssl_client_socket.Connect( + test_completion_callback.callback()); + EXPECT_FALSE(fake_ssl_client_socket.IsConnected()); + ExpectStatus(mode, expected_status, status, &test_completion_callback); + EXPECT_FALSE(fake_ssl_client_socket.IsConnected()); + } + + void RunUnsuccessfulHandshakeTest( + int error, HandshakeErrorLocation location) { + RunUnsuccessfulHandshakeTestHelper(net::SYNCHRONOUS, error, location); + RunUnsuccessfulHandshakeTestHelper(net::ASYNC, error, location); + } + + // MockTCPClientSocket needs a message loop. + base::MessageLoop message_loop_; + + net::MockClientSocketFactory mock_client_socket_factory_; + scoped_ptr<net::StaticSocketDataProvider> static_socket_data_provider_; +}; + +TEST_F(FakeSSLClientSocketTest, PassThroughMethods) { + scoped_ptr<MockClientSocket> mock_client_socket(new MockClientSocket()); + const int kReceiveBufferSize = 10; + const int kSendBufferSize = 20; + net::IPEndPoint ip_endpoint(net::IPAddressNumber(net::kIPv4AddressSize), 80); + const int kPeerAddress = 30; + net::BoundNetLog net_log; + EXPECT_CALL(*mock_client_socket, SetReceiveBufferSize(kReceiveBufferSize)); + EXPECT_CALL(*mock_client_socket, SetSendBufferSize(kSendBufferSize)); + EXPECT_CALL(*mock_client_socket, GetPeerAddress(&ip_endpoint)). + WillOnce(Return(kPeerAddress)); + EXPECT_CALL(*mock_client_socket, NetLog()).WillOnce(ReturnRef(net_log)); + EXPECT_CALL(*mock_client_socket, SetSubresourceSpeculation()); + EXPECT_CALL(*mock_client_socket, SetOmniboxSpeculation()); + + // Takes ownership of |mock_client_socket|. + FakeSSLClientSocket fake_ssl_client_socket( + mock_client_socket.PassAs<net::StreamSocket>()); + fake_ssl_client_socket.SetReceiveBufferSize(kReceiveBufferSize); + fake_ssl_client_socket.SetSendBufferSize(kSendBufferSize); + EXPECT_EQ(kPeerAddress, + fake_ssl_client_socket.GetPeerAddress(&ip_endpoint)); + EXPECT_EQ(&net_log, &fake_ssl_client_socket.NetLog()); + fake_ssl_client_socket.SetSubresourceSpeculation(); + fake_ssl_client_socket.SetOmniboxSpeculation(); +} + +TEST_F(FakeSSLClientSocketTest, SuccessfulHandshakeSync) { + for (size_t i = 1; i < 100; i += 3) { + SCOPED_TRACE(i); + for (size_t j = 1; j < 100; j += 5) { + SCOPED_TRACE(j); + RunSuccessfulHandshakeTest(net::SYNCHRONOUS, i, j, 0); + } + } +} + +TEST_F(FakeSSLClientSocketTest, SuccessfulHandshakeAsync) { + for (size_t i = 1; i < 100; i += 7) { + SCOPED_TRACE(i); + for (size_t j = 1; j < 100; j += 9) { + SCOPED_TRACE(j); + RunSuccessfulHandshakeTest(net::ASYNC, i, j, 0); + } + } +} + +TEST_F(FakeSSLClientSocketTest, ResetSocket) { + RunSuccessfulHandshakeTest(net::ASYNC, 1, 2, 3); +} + +TEST_F(FakeSSLClientSocketTest, UnsuccessfulHandshakeConnectError) { + RunUnsuccessfulHandshakeTest(net::ERR_ACCESS_DENIED, CONNECT_ERROR); +} + +TEST_F(FakeSSLClientSocketTest, UnsuccessfulHandshakeWriteError) { + RunUnsuccessfulHandshakeTest(net::ERR_OUT_OF_MEMORY, + SEND_CLIENT_HELLO_ERROR); +} + +TEST_F(FakeSSLClientSocketTest, UnsuccessfulHandshakeReadError) { + RunUnsuccessfulHandshakeTest(net::ERR_CONNECTION_CLOSED, + VERIFY_SERVER_HELLO_ERROR); +} + +TEST_F(FakeSSLClientSocketTest, PeerClosedDuringHandshake) { + RunUnsuccessfulHandshakeTest( + net::ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, + VERIFY_SERVER_HELLO_ERROR); +} + +TEST_F(FakeSSLClientSocketTest, MalformedServerHello) { + RunUnsuccessfulHandshakeTest(ERR_MALFORMED_SERVER_HELLO, + VERIFY_SERVER_HELLO_ERROR); +} + +} // namespace + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/jingle_glue_mock_objects.cc b/chromium/jingle/glue/jingle_glue_mock_objects.cc new file mode 100644 index 00000000000..6cd48ce0056 --- /dev/null +++ b/chromium/jingle/glue/jingle_glue_mock_objects.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2011 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/glue/jingle_glue_mock_objects.h" + +namespace jingle_glue { + +MockStream::MockStream() {} + +MockStream::~MockStream() {} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/jingle_glue_mock_objects.h b/chromium/jingle/glue/jingle_glue_mock_objects.h new file mode 100644 index 00000000000..e2cd704d7cd --- /dev/null +++ b/chromium/jingle/glue/jingle_glue_mock_objects.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_GLUE_JINGLE_GLUE_MOCK_OBJECTS_H_ +#define JINGLE_GLUE_JINGLE_GLUE_MOCK_OBJECTS_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "third_party/libjingle/source/talk/base/stream.h" + +namespace jingle_glue { + +class MockStream : public talk_base::StreamInterface { + public: + MockStream(); + virtual ~MockStream(); + + MOCK_CONST_METHOD0(GetState, talk_base::StreamState()); + + MOCK_METHOD4(Read, talk_base::StreamResult(void*, size_t, size_t*, int*)); + MOCK_METHOD4(Write, talk_base::StreamResult(const void*, size_t, + size_t*, int*)); + MOCK_CONST_METHOD1(GetAvailable, bool(size_t*)); + MOCK_METHOD0(Close, void()); + + MOCK_METHOD3(PostEvent, void(talk_base::Thread*, int, int)); + MOCK_METHOD2(PostEvent, void(int, int)); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_JINGLE_GLUE_MOCK_OBJECTS_H_ diff --git a/chromium/jingle/glue/logging_unittest.cc b/chromium/jingle/glue/logging_unittest.cc new file mode 100644 index 00000000000..85ac558ca3c --- /dev/null +++ b/chromium/jingle/glue/logging_unittest.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2012 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. + +// Note: this test tests LOG_V and LOG_E since all other logs are expressed +// in forms of them. LOG is also tested for good measure. +// Also note that we are only allowed to call InitLogging() twice so the test +// cases are more dense than normal. + +// The following include must be first in this file. It ensures that +// libjingle style logging is used. +#define LOGGING_INSIDE_LIBJINGLE + +#include "third_party/libjingle/overrides/talk/base/logging.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +static const wchar_t* const log_file_name = L"libjingle_logging.log"; +#else +static const char* const log_file_name = "libjingle_logging.log"; +#endif + +static const int kDefaultVerbosity = 0; + +static const char* AsString(talk_base::LoggingSeverity severity) { + switch (severity) { + case talk_base::LS_ERROR: + return "LS_ERROR"; + case talk_base::LS_WARNING: + return "LS_WARNING"; + case talk_base::LS_INFO: + return "LS_INFO"; + case talk_base::LS_VERBOSE: + return "LS_VERBOSE"; + case talk_base::LS_SENSITIVE: + return "LS_SENSITIVE"; + default: + return ""; + } +} + +static bool ContainsString(const std::string& original, + const char* string_to_match) { + return original.find(string_to_match) != std::string::npos; +} + +static bool Initialize(int verbosity_level) { + if (verbosity_level != kDefaultVerbosity) { + // Update the command line with specified verbosity level for this file. + CommandLine* command_line = CommandLine::ForCurrentProcess(); + std::ostringstream value_stream; + value_stream << "logging_unittest=" << verbosity_level; + const std::string& value = value_stream.str(); + command_line->AppendSwitchASCII("vmodule", value); + } + + // The command line flags are parsed here and the log file name is set. + logging::LoggingSettings settings; + settings.logging_dest = logging::LOG_TO_FILE; + settings.log_file = log_file_name; + settings.lock_log = logging::DONT_LOCK_LOG_FILE; + settings.delete_old = logging::DELETE_OLD_LOG_FILE; + if (!logging::InitLogging(settings)) { + return false; + } + EXPECT_TRUE(VLOG_IS_ON(verbosity_level)); + EXPECT_FALSE(VLOG_IS_ON(verbosity_level + 1)); + return true; +} + +TEST(LibjingleLogTest, DefaultConfiguration) { + ASSERT_TRUE(Initialize(kDefaultVerbosity)); + + // In the default configuration nothing should be logged. + LOG_V(talk_base::LS_ERROR) << AsString(talk_base::LS_ERROR); + LOG_V(talk_base::LS_WARNING) << AsString(talk_base::LS_WARNING); + LOG_V(talk_base::LS_INFO) << AsString(talk_base::LS_INFO); + LOG_V(talk_base::LS_VERBOSE) << AsString(talk_base::LS_VERBOSE); + LOG_V(talk_base::LS_SENSITIVE) << AsString(talk_base::LS_SENSITIVE); + + // Read file to string. + base::FilePath file_path(log_file_name); + std::string contents_of_file; + file_util::ReadFileToString(file_path, &contents_of_file); + + // Make sure string contains the expected values. + EXPECT_FALSE(ContainsString(contents_of_file, AsString(talk_base::LS_ERROR))); + EXPECT_FALSE(ContainsString(contents_of_file, + AsString(talk_base::LS_WARNING))); + EXPECT_FALSE(ContainsString(contents_of_file, AsString(talk_base::LS_INFO))); + EXPECT_FALSE(ContainsString(contents_of_file, + AsString(talk_base::LS_VERBOSE))); + EXPECT_FALSE(ContainsString(contents_of_file, + AsString(talk_base::LS_SENSITIVE))); +} + +TEST(LibjingleLogTest, InfoConfiguration) { + ASSERT_TRUE(Initialize(talk_base::LS_INFO)); + + // In this configuration everything lower or equal to LS_INFO should be + // logged. + LOG_V(talk_base::LS_ERROR) << AsString(talk_base::LS_ERROR); + LOG_V(talk_base::LS_WARNING) << AsString(talk_base::LS_WARNING); + LOG_V(talk_base::LS_INFO) << AsString(talk_base::LS_INFO); + LOG_V(talk_base::LS_VERBOSE) << AsString(talk_base::LS_VERBOSE); + LOG_V(talk_base::LS_SENSITIVE) << AsString(talk_base::LS_SENSITIVE); + + // Read file to string. + base::FilePath file_path(log_file_name); + std::string contents_of_file; + file_util::ReadFileToString(file_path, &contents_of_file); + + // Make sure string contains the expected values. + EXPECT_TRUE(ContainsString(contents_of_file, AsString(talk_base::LS_ERROR))); + EXPECT_TRUE(ContainsString(contents_of_file, + AsString(talk_base::LS_WARNING))); + EXPECT_TRUE(ContainsString(contents_of_file, AsString(talk_base::LS_INFO))); + EXPECT_FALSE(ContainsString(contents_of_file, + AsString(talk_base::LS_VERBOSE))); + EXPECT_FALSE(ContainsString(contents_of_file, + AsString(talk_base::LS_SENSITIVE))); + + // Also check that the log is proper. + EXPECT_TRUE(ContainsString(contents_of_file, "logging_unittest.cc")); + EXPECT_FALSE(ContainsString(contents_of_file, "logging.h")); + EXPECT_FALSE(ContainsString(contents_of_file, "logging.cc")); +} + +TEST(LibjingleLogTest, LogEverythingConfiguration) { + ASSERT_TRUE(Initialize(talk_base::LS_SENSITIVE)); + + // In this configuration everything should be logged. + LOG_V(talk_base::LS_ERROR) << AsString(talk_base::LS_ERROR); + LOG_V(talk_base::LS_WARNING) << AsString(talk_base::LS_WARNING); + LOG(LS_INFO) << AsString(talk_base::LS_INFO); + static const int kFakeError = 1; + LOG_E(LS_INFO, EN, kFakeError) << "LOG_E(" << AsString(talk_base::LS_INFO) << + ")"; + LOG_V(talk_base::LS_VERBOSE) << AsString(talk_base::LS_VERBOSE); + LOG_V(talk_base::LS_SENSITIVE) << AsString(talk_base::LS_SENSITIVE); + + // Read file to string. + base::FilePath file_path(log_file_name); + std::string contents_of_file; + file_util::ReadFileToString(file_path, &contents_of_file); + + // Make sure string contains the expected values. + EXPECT_TRUE(ContainsString(contents_of_file, AsString(talk_base::LS_ERROR))); + EXPECT_TRUE(ContainsString(contents_of_file, + AsString(talk_base::LS_WARNING))); + EXPECT_TRUE(ContainsString(contents_of_file, AsString(talk_base::LS_INFO))); + // LOG_E + EXPECT_TRUE(ContainsString(contents_of_file, strerror(kFakeError))); + EXPECT_TRUE(ContainsString(contents_of_file, + AsString(talk_base::LS_VERBOSE))); + EXPECT_TRUE(ContainsString(contents_of_file, + AsString(talk_base::LS_SENSITIVE))); +} diff --git a/chromium/jingle/glue/mock_task.cc b/chromium/jingle/glue/mock_task.cc new file mode 100644 index 00000000000..8894fbeaf90 --- /dev/null +++ b/chromium/jingle/glue/mock_task.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 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/glue/mock_task.h" + +namespace jingle_glue { + +MockTask::MockTask(TaskParent* parent) : talk_base::Task(parent) {} + +MockTask::~MockTask() {} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/mock_task.h b/chromium/jingle/glue/mock_task.h new file mode 100644 index 00000000000..7fdaddfb2da --- /dev/null +++ b/chromium/jingle/glue/mock_task.h @@ -0,0 +1,26 @@ +// Copyright (c) 2012 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. +// +// A mock of talk_base::Task. + +#ifndef JINGLE_GLUE_MOCK_TASK_H_ +#define JINGLE_GLUE_MOCK_TASK_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "third_party/libjingle/source/talk/base/task.h" + +namespace jingle_glue { + +class MockTask : public talk_base::Task { + public: + MockTask(TaskParent* parent); + + virtual ~MockTask(); + + MOCK_METHOD0(ProcessStart, int()); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_MOCK_TASK_H_ diff --git a/chromium/jingle/glue/proxy_resolving_client_socket.cc b/chromium/jingle/glue/proxy_resolving_client_socket.cc new file mode 100644 index 00000000000..d63411bdc60 --- /dev/null +++ b/chromium/jingle/glue/proxy_resolving_client_socket.cc @@ -0,0 +1,398 @@ +// Copyright (c) 2012 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/glue/proxy_resolving_client_socket.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_network_session.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/client_socket_pool_manager.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +namespace jingle_glue { + +ProxyResolvingClientSocket::ProxyResolvingClientSocket( + net::ClientSocketFactory* socket_factory, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, + const net::SSLConfig& ssl_config, + const net::HostPortPair& dest_host_port_pair) + : proxy_resolve_callback_( + base::Bind(&ProxyResolvingClientSocket::ProcessProxyResolveDone, + base::Unretained(this))), + connect_callback_( + base::Bind(&ProxyResolvingClientSocket::ProcessConnectDone, + base::Unretained(this))), + ssl_config_(ssl_config), + pac_request_(NULL), + dest_host_port_pair_(dest_host_port_pair), + // Assume that we intend to do TLS on this socket; all + // current use cases do. + proxy_url_("https://" + dest_host_port_pair_.ToString()), + tried_direct_connect_fallback_(false), + bound_net_log_( + net::BoundNetLog::Make( + request_context_getter->GetURLRequestContext()->net_log(), + net::NetLog::SOURCE_SOCKET)), + weak_factory_(this) { + DCHECK(request_context_getter.get()); + net::URLRequestContext* request_context = + request_context_getter->GetURLRequestContext(); + DCHECK(request_context); + DCHECK(!dest_host_port_pair_.host().empty()); + DCHECK_GT(dest_host_port_pair_.port(), 0); + DCHECK(proxy_url_.is_valid()); + + net::HttpNetworkSession::Params session_params; + session_params.client_socket_factory = socket_factory; + session_params.host_resolver = request_context->host_resolver(); + session_params.cert_verifier = request_context->cert_verifier(); + session_params.transport_security_state = + request_context->transport_security_state(); + // TODO(rkn): This is NULL because ServerBoundCertService is not thread safe. + session_params.server_bound_cert_service = NULL; + session_params.proxy_service = request_context->proxy_service(); + session_params.ssl_config_service = request_context->ssl_config_service(); + session_params.http_auth_handler_factory = + request_context->http_auth_handler_factory(); + session_params.network_delegate = request_context->network_delegate(); + session_params.http_server_properties = + request_context->http_server_properties(); + session_params.net_log = request_context->net_log(); + + const net::HttpNetworkSession::Params* reference_params = + request_context->GetNetworkSessionParams(); + if (reference_params) { + session_params.host_mapping_rules = reference_params->host_mapping_rules; + session_params.ignore_certificate_errors = + reference_params->ignore_certificate_errors; + session_params.http_pipelining_enabled = + reference_params->http_pipelining_enabled; + session_params.testing_fixed_http_port = + reference_params->testing_fixed_http_port; + session_params.testing_fixed_https_port = + reference_params->testing_fixed_https_port; + session_params.trusted_spdy_proxy = reference_params->trusted_spdy_proxy; + } + + network_session_ = new net::HttpNetworkSession(session_params); +} + +ProxyResolvingClientSocket::~ProxyResolvingClientSocket() { + Disconnect(); +} + +int ProxyResolvingClientSocket::Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + if (transport_.get() && transport_->socket()) + return transport_->socket()->Read(buf, buf_len, callback); + NOTREACHED(); + return net::ERR_SOCKET_NOT_CONNECTED; +} + +int ProxyResolvingClientSocket::Write( + net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback) { + if (transport_.get() && transport_->socket()) + return transport_->socket()->Write(buf, buf_len, callback); + NOTREACHED(); + return net::ERR_SOCKET_NOT_CONNECTED; +} + +bool ProxyResolvingClientSocket::SetReceiveBufferSize(int32 size) { + if (transport_.get() && transport_->socket()) + return transport_->socket()->SetReceiveBufferSize(size); + NOTREACHED(); + return false; +} + +bool ProxyResolvingClientSocket::SetSendBufferSize(int32 size) { + if (transport_.get() && transport_->socket()) + return transport_->socket()->SetSendBufferSize(size); + NOTREACHED(); + return false; +} + +int ProxyResolvingClientSocket::Connect( + const net::CompletionCallback& callback) { + DCHECK(user_connect_callback_.is_null()); + + tried_direct_connect_fallback_ = false; + + // First we try and resolve the proxy. + int status = network_session_->proxy_service()->ResolveProxy( + proxy_url_, + &proxy_info_, + proxy_resolve_callback_, + &pac_request_, + bound_net_log_); + if (status != net::ERR_IO_PENDING) { + // We defer execution of ProcessProxyResolveDone instead of calling it + // directly here for simplicity. From the caller's point of view, + // the connect always happens asynchronously. + base::MessageLoop* message_loop = base::MessageLoop::current(); + CHECK(message_loop); + message_loop->PostTask( + FROM_HERE, + base::Bind(&ProxyResolvingClientSocket::ProcessProxyResolveDone, + weak_factory_.GetWeakPtr(), status)); + } + user_connect_callback_ = callback; + return net::ERR_IO_PENDING; +} + +void ProxyResolvingClientSocket::RunUserConnectCallback(int status) { + DCHECK_LE(status, net::OK); + net::CompletionCallback user_connect_callback = user_connect_callback_; + user_connect_callback_.Reset(); + user_connect_callback.Run(status); +} + +// Always runs asynchronously. +void ProxyResolvingClientSocket::ProcessProxyResolveDone(int status) { + pac_request_ = NULL; + + DCHECK_NE(status, net::ERR_IO_PENDING); + if (status == net::OK) { + // Remove unsupported proxies from the list. + proxy_info_.RemoveProxiesWithoutScheme( + net::ProxyServer::SCHEME_DIRECT | + net::ProxyServer::SCHEME_HTTP | net::ProxyServer::SCHEME_HTTPS | + net::ProxyServer::SCHEME_SOCKS4 | net::ProxyServer::SCHEME_SOCKS5); + + if (proxy_info_.is_empty()) { + // No proxies/direct to choose from. This happens when we don't support + // any of the proxies in the returned list. + status = net::ERR_NO_SUPPORTED_PROXIES; + } + } + + // Since we are faking the URL, it is possible that no proxies match our URL. + // Try falling back to a direct connection if we have not tried that before. + if (status != net::OK) { + if (!tried_direct_connect_fallback_) { + tried_direct_connect_fallback_ = true; + proxy_info_.UseDirect(); + } else { + CloseTransportSocket(); + RunUserConnectCallback(status); + return; + } + } + + transport_.reset(new net::ClientSocketHandle); + // Now that we have resolved the proxy, we need to connect. + status = net::InitSocketHandleForRawConnect( + dest_host_port_pair_, network_session_.get(), proxy_info_, ssl_config_, + ssl_config_, net::kPrivacyModeDisabled, bound_net_log_, transport_.get(), + connect_callback_); + if (status != net::ERR_IO_PENDING) { + // Since this method is always called asynchronously. it is OK to call + // ProcessConnectDone synchronously. + ProcessConnectDone(status); + } +} + +void ProxyResolvingClientSocket::ProcessConnectDone(int status) { + if (status != net::OK) { + // If the connection fails, try another proxy. + status = ReconsiderProxyAfterError(status); + // ReconsiderProxyAfterError either returns an error (in which case it is + // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering + // another proxy. + DCHECK_NE(status, net::OK); + if (status == net::ERR_IO_PENDING) + // Proxy reconsideration pending. Return. + return; + CloseTransportSocket(); + } else { + ReportSuccessfulProxyConnection(); + } + RunUserConnectCallback(status); +} + +// TODO(sanjeevr): This has largely been copied from +// HttpStreamFactoryImpl::Job::ReconsiderProxyAfterError. This should be +// refactored into some common place. +// This method reconsiders the proxy on certain errors. If it does reconsider +// a proxy it always returns ERR_IO_PENDING and posts a call to +// ProcessProxyResolveDone with the result of the reconsideration. +int ProxyResolvingClientSocket::ReconsiderProxyAfterError(int error) { + DCHECK(!pac_request_); + DCHECK_NE(error, net::OK); + DCHECK_NE(error, net::ERR_IO_PENDING); + // A failure to resolve the hostname or any error related to establishing a + // TCP connection could be grounds for trying a new proxy configuration. + // + // Why do this when a hostname cannot be resolved? Some URLs only make sense + // to proxy servers. The hostname in those URLs might fail to resolve if we + // are still using a non-proxy config. We need to check if a proxy config + // now exists that corresponds to a proxy server that could load the URL. + // + switch (error) { + case net::ERR_PROXY_CONNECTION_FAILED: + case net::ERR_NAME_NOT_RESOLVED: + case net::ERR_INTERNET_DISCONNECTED: + case net::ERR_ADDRESS_UNREACHABLE: + case net::ERR_CONNECTION_CLOSED: + case net::ERR_CONNECTION_RESET: + case net::ERR_CONNECTION_REFUSED: + case net::ERR_CONNECTION_ABORTED: + case net::ERR_TIMED_OUT: + case net::ERR_TUNNEL_CONNECTION_FAILED: + case net::ERR_SOCKS_CONNECTION_FAILED: + break; + case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: + // Remap the SOCKS-specific "host unreachable" error to a more + // generic error code (this way consumers like the link doctor + // know to substitute their error page). + // + // Note that if the host resolving was done by the SOCSK5 proxy, we can't + // differentiate between a proxy-side "host not found" versus a proxy-side + // "address unreachable" error, and will report both of these failures as + // ERR_ADDRESS_UNREACHABLE. + return net::ERR_ADDRESS_UNREACHABLE; + default: + return error; + } + + if (proxy_info_.is_https() && ssl_config_.send_client_cert) { + network_session_->ssl_client_auth_cache()->Remove( + proxy_info_.proxy_server().host_port_pair().ToString()); + } + + int rv = network_session_->proxy_service()->ReconsiderProxyAfterError( + proxy_url_, &proxy_info_, proxy_resolve_callback_, &pac_request_, + bound_net_log_); + if (rv == net::OK || rv == net::ERR_IO_PENDING) { + CloseTransportSocket(); + } else { + // If ReconsiderProxyAfterError() failed synchronously, it means + // there was nothing left to fall-back to, so fail the transaction + // with the last connection error we got. + rv = error; + } + + // We either have new proxy info or there was an error in falling back. + // In both cases we want to post ProcessProxyResolveDone (in the error case + // we might still want to fall back a direct connection). + if (rv != net::ERR_IO_PENDING) { + base::MessageLoop* message_loop = base::MessageLoop::current(); + CHECK(message_loop); + message_loop->PostTask( + FROM_HERE, + base::Bind(&ProxyResolvingClientSocket::ProcessProxyResolveDone, + weak_factory_.GetWeakPtr(), rv)); + // Since we potentially have another try to go (trying the direct connect) + // set the return code code to ERR_IO_PENDING. + rv = net::ERR_IO_PENDING; + } + return rv; +} + +void ProxyResolvingClientSocket::ReportSuccessfulProxyConnection() { + network_session_->proxy_service()->ReportSuccess(proxy_info_); +} + +void ProxyResolvingClientSocket::Disconnect() { + CloseTransportSocket(); + if (pac_request_) { + network_session_->proxy_service()->CancelPacRequest(pac_request_); + pac_request_ = NULL; + } + user_connect_callback_.Reset(); +} + +bool ProxyResolvingClientSocket::IsConnected() const { + if (!transport_.get() || !transport_->socket()) + return false; + return transport_->socket()->IsConnected(); +} + +bool ProxyResolvingClientSocket::IsConnectedAndIdle() const { + if (!transport_.get() || !transport_->socket()) + return false; + return transport_->socket()->IsConnectedAndIdle(); +} + +int ProxyResolvingClientSocket::GetPeerAddress( + net::IPEndPoint* address) const { + if (transport_.get() && transport_->socket()) + return transport_->socket()->GetPeerAddress(address); + NOTREACHED(); + return net::ERR_SOCKET_NOT_CONNECTED; +} + +int ProxyResolvingClientSocket::GetLocalAddress( + net::IPEndPoint* address) const { + if (transport_.get() && transport_->socket()) + return transport_->socket()->GetLocalAddress(address); + NOTREACHED(); + return net::ERR_SOCKET_NOT_CONNECTED; +} + +const net::BoundNetLog& ProxyResolvingClientSocket::NetLog() const { + if (transport_.get() && transport_->socket()) + return transport_->socket()->NetLog(); + NOTREACHED(); + return bound_net_log_; +} + +void ProxyResolvingClientSocket::SetSubresourceSpeculation() { + if (transport_.get() && transport_->socket()) + transport_->socket()->SetSubresourceSpeculation(); + else + NOTREACHED(); +} + +void ProxyResolvingClientSocket::SetOmniboxSpeculation() { + if (transport_.get() && transport_->socket()) + transport_->socket()->SetOmniboxSpeculation(); + else + NOTREACHED(); +} + +bool ProxyResolvingClientSocket::WasEverUsed() const { + if (transport_.get() && transport_->socket()) + return transport_->socket()->WasEverUsed(); + NOTREACHED(); + return false; +} + +bool ProxyResolvingClientSocket::UsingTCPFastOpen() const { + if (transport_.get() && transport_->socket()) + return transport_->socket()->UsingTCPFastOpen(); + NOTREACHED(); + return false; +} + +bool ProxyResolvingClientSocket::WasNpnNegotiated() const { + return false; +} + +net::NextProto ProxyResolvingClientSocket::GetNegotiatedProtocol() const { + if (transport_.get() && transport_->socket()) + return transport_->socket()->GetNegotiatedProtocol(); + NOTREACHED(); + return net::kProtoUnknown; +} + +bool ProxyResolvingClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) { + return false; +} + +void ProxyResolvingClientSocket::CloseTransportSocket() { + if (transport_.get() && transport_->socket()) + transport_->socket()->Disconnect(); + transport_.reset(); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/proxy_resolving_client_socket.h b/chromium/jingle/glue/proxy_resolving_client_socket.h new file mode 100644 index 00000000000..b920591da5f --- /dev/null +++ b/chromium/jingle/glue/proxy_resolving_client_socket.h @@ -0,0 +1,106 @@ +// Copyright (c) 2012 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. +// +// This StreamSocket implementation wraps a ClientSocketHandle that is created +// from the client socket pool after resolving proxies. + +#ifndef JINGLE_GLUE_PROXY_RESOLVING_CLIENT_SOCKET_H_ +#define JINGLE_GLUE_PROXY_RESOLVING_CLIENT_SOCKET_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/proxy/proxy_info.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/stream_socket.h" +#include "net/ssl/ssl_config_service.h" +#include "url/gurl.h" + +namespace net { +class ClientSocketFactory; +class ClientSocketHandle; +class HttpNetworkSession; +class URLRequestContextGetter; +} // namespace net + +// TODO(sanjeevr): Move this to net/ +namespace jingle_glue { + +class ProxyResolvingClientSocket : public net::StreamSocket { + public: + // Constructs a new ProxyResolvingClientSocket. |socket_factory| is + // the ClientSocketFactory that will be used by the underlying + // HttpNetworkSession. If |socket_factory| is NULL, the default + // socket factory (net::ClientSocketFactory::GetDefaultFactory()) + // will be used. |dest_host_port_pair| is the destination for this + // socket. The hostname must be non-empty and the port must be > 0. + ProxyResolvingClientSocket( + net::ClientSocketFactory* socket_factory, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, + const net::SSLConfig& ssl_config, + const net::HostPortPair& dest_host_port_pair); + virtual ~ProxyResolvingClientSocket(); + + // net::StreamSocket implementation. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + virtual int Connect(const net::CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectedAndIdle() const OVERRIDE; + virtual int GetPeerAddress(net::IPEndPoint* address) const OVERRIDE; + virtual int GetLocalAddress(net::IPEndPoint* address) const OVERRIDE; + virtual const net::BoundNetLog& NetLog() const OVERRIDE; + virtual void SetSubresourceSpeculation() OVERRIDE; + virtual void SetOmniboxSpeculation() OVERRIDE; + virtual bool WasEverUsed() const OVERRIDE; + virtual bool UsingTCPFastOpen() const OVERRIDE; + virtual bool WasNpnNegotiated() const OVERRIDE; + virtual net::NextProto GetNegotiatedProtocol() const OVERRIDE; + virtual bool GetSSLInfo(net::SSLInfo* ssl_info) OVERRIDE; + + private: + // Proxy resolution and connection functions. + void ProcessProxyResolveDone(int status); + void ProcessConnectDone(int status); + + void CloseTransportSocket(); + void RunUserConnectCallback(int status); + int ReconsiderProxyAfterError(int error); + void ReportSuccessfulProxyConnection(); + + // Callbacks passed to net APIs. + net::CompletionCallback proxy_resolve_callback_; + net::CompletionCallback connect_callback_; + + scoped_refptr<net::HttpNetworkSession> network_session_; + + // The transport socket. + scoped_ptr<net::ClientSocketHandle> transport_; + + const net::SSLConfig ssl_config_; + net::ProxyService::PacRequest* pac_request_; + net::ProxyInfo proxy_info_; + const net::HostPortPair dest_host_port_pair_; + const GURL proxy_url_; + bool tried_direct_connect_fallback_; + net::BoundNetLog bound_net_log_; + base::WeakPtrFactory<ProxyResolvingClientSocket> weak_factory_; + + // The callback passed to Connect(). + net::CompletionCallback user_connect_callback_; +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_PROXY_RESOLVING_CLIENT_SOCKET_H_ diff --git a/chromium/jingle/glue/proxy_resolving_client_socket_unittest.cc b/chromium/jingle/glue/proxy_resolving_client_socket_unittest.cc new file mode 100644 index 00000000000..cdcbc56f211 --- /dev/null +++ b/chromium/jingle/glue/proxy_resolving_client_socket_unittest.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2012 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/glue/proxy_resolving_client_socket.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "net/base/test_completion_callback.h" +#include "net/dns/mock_host_resolver.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/socket_test_util.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MyTestURLRequestContext : public net::TestURLRequestContext { + public: + MyTestURLRequestContext() : TestURLRequestContext(true) { + context_storage_.set_proxy_service( + net::ProxyService::CreateFixedFromPacResult( + "PROXY bad:99; PROXY maybe:80; DIRECT")); + Init(); + } + virtual ~MyTestURLRequestContext() {} +}; + +} // namespace + +namespace jingle_glue { + +class ProxyResolvingClientSocketTest : public testing::Test { + protected: + ProxyResolvingClientSocketTest() + : url_request_context_getter_(new net::TestURLRequestContextGetter( + base::MessageLoopProxy::current(), + scoped_ptr<net::TestURLRequestContext>( + new MyTestURLRequestContext))) {} + + virtual ~ProxyResolvingClientSocketTest() {} + + virtual void TearDown() { + // Clear out any messages posted by ProxyResolvingClientSocket's + // destructor. + message_loop_.RunUntilIdle(); + } + + base::MessageLoop message_loop_; + scoped_refptr<net::TestURLRequestContextGetter> url_request_context_getter_; +}; + +// TODO(sanjeevr): Fix this test on Linux. +TEST_F(ProxyResolvingClientSocketTest, DISABLED_ConnectError) { + net::HostPortPair dest("0.0.0.0", 0); + ProxyResolvingClientSocket proxy_resolving_socket( + NULL, + url_request_context_getter_, + net::SSLConfig(), + dest); + net::TestCompletionCallback callback; + int status = proxy_resolving_socket.Connect(callback.callback()); + // Connect always returns ERR_IO_PENDING because it is always asynchronous. + EXPECT_EQ(net::ERR_IO_PENDING, status); + status = callback.WaitForResult(); + // ProxyResolvingClientSocket::Connect() will always return an error of + // ERR_ADDRESS_INVALID for a 0 IP address. + EXPECT_EQ(net::ERR_ADDRESS_INVALID, status); +} + +TEST_F(ProxyResolvingClientSocketTest, ReportsBadProxies) { + net::HostPortPair dest("example.com", 443); + net::MockClientSocketFactory socket_factory; + + net::StaticSocketDataProvider socket_data1; + socket_data1.set_connect_data( + net::MockConnect(net::ASYNC, net::ERR_ADDRESS_UNREACHABLE)); + socket_factory.AddSocketDataProvider(&socket_data1); + + net::MockRead reads[] = { + net::MockRead("HTTP/1.1 200 Success\r\n\r\n") + }; + net::MockWrite writes[] = { + net::MockWrite("CONNECT example.com:443 HTTP/1.1\r\n" + "Host: example.com:443\r\n" + "Proxy-Connection: keep-alive\r\n\r\n") + }; + net::StaticSocketDataProvider socket_data2(reads, arraysize(reads), + writes, arraysize(writes)); + socket_data2.set_connect_data(net::MockConnect(net::ASYNC, net::OK)); + socket_factory.AddSocketDataProvider(&socket_data2); + + ProxyResolvingClientSocket proxy_resolving_socket( + &socket_factory, + url_request_context_getter_, + net::SSLConfig(), + dest); + + net::TestCompletionCallback callback; + int status = proxy_resolving_socket.Connect(callback.callback()); + EXPECT_EQ(net::ERR_IO_PENDING, status); + status = callback.WaitForResult(); + EXPECT_EQ(net::OK, status); + + net::URLRequestContext* context = + url_request_context_getter_->GetURLRequestContext(); + const net::ProxyRetryInfoMap& retry_info = + context->proxy_service()->proxy_retry_info(); + + EXPECT_EQ(1u, retry_info.size()); + net::ProxyRetryInfoMap::const_iterator iter = retry_info.find("bad:99"); + EXPECT_TRUE(iter != retry_info.end()); +} + +// TODO(sanjeevr): Add more unit-tests. +} // namespace jingle_glue diff --git a/chromium/jingle/glue/pseudotcp_adapter.cc b/chromium/jingle/glue/pseudotcp_adapter.cc new file mode 100644 index 00000000000..afd6c36909a --- /dev/null +++ b/chromium/jingle/glue/pseudotcp_adapter.cc @@ -0,0 +1,598 @@ +// Copyright (c) 2012 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/glue/pseudotcp_adapter.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "net/base/address_list.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" + +using cricket::PseudoTcp; + +namespace { +const int kReadBufferSize = 65536; // Maximum size of a packet. +const uint16 kDefaultMtu = 1280; +} // namespace + +namespace jingle_glue { + +class PseudoTcpAdapter::Core : public cricket::IPseudoTcpNotify, + public base::RefCounted<Core> { + public: + Core(net::Socket* socket); + + // Functions used to implement net::StreamSocket. + int Read(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback); + int Write(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback); + int Connect(const net::CompletionCallback& callback); + void Disconnect(); + bool IsConnected() const; + + // cricket::IPseudoTcpNotify interface. + // These notifications are triggered from NotifyPacket. + virtual void OnTcpOpen(cricket::PseudoTcp* tcp) OVERRIDE; + virtual void OnTcpReadable(cricket::PseudoTcp* tcp) OVERRIDE; + virtual void OnTcpWriteable(cricket::PseudoTcp* tcp) OVERRIDE; + // This is triggered by NotifyClock or NotifyPacket. + virtual void OnTcpClosed(cricket::PseudoTcp* tcp, uint32 error) OVERRIDE; + // This is triggered by NotifyClock, NotifyPacket, Recv and Send. + virtual WriteResult TcpWritePacket(cricket::PseudoTcp* tcp, + const char* buffer, size_t len) OVERRIDE; + + void SetAckDelay(int delay_ms); + void SetNoDelay(bool no_delay); + void SetReceiveBufferSize(int32 size); + void SetSendBufferSize(int32 size); + void SetWriteWaitsForSend(bool write_waits_for_send); + + void DeleteSocket(); + + private: + friend class base::RefCounted<Core>; + virtual ~Core(); + + // These are invoked by the underlying Socket, and may trigger callbacks. + // They hold a reference to |this| while running, to protect from deletion. + void OnRead(int result); + void OnWritten(int result); + + // These may trigger callbacks, so the holder must hold a reference on + // the stack while calling them. + void DoReadFromSocket(); + void HandleReadResults(int result); + void HandleTcpClock(); + + // Checks if current write has completed in the write-waits-for-send + // mode. + void CheckWriteComplete(); + + // This re-sets |timer| without triggering callbacks. + void AdjustClock(); + + net::CompletionCallback connect_callback_; + net::CompletionCallback read_callback_; + net::CompletionCallback write_callback_; + + cricket::PseudoTcp pseudo_tcp_; + scoped_ptr<net::Socket> socket_; + + scoped_refptr<net::IOBuffer> read_buffer_; + int read_buffer_size_; + scoped_refptr<net::IOBuffer> write_buffer_; + int write_buffer_size_; + + // Whether we need to wait for data to be sent before completing write. + bool write_waits_for_send_; + + // Set to true in the write-waits-for-send mode when we've + // successfully writtend data to the send buffer and waiting for the + // data to be sent to the remote end. + bool waiting_write_position_; + + // Number of the bytes written by the last write stored while we wait + // for the data to be sent (i.e. when waiting_write_position_ = true). + int last_write_result_; + + bool socket_write_pending_; + scoped_refptr<net::IOBuffer> socket_read_buffer_; + + base::OneShotTimer<Core> timer_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + + +PseudoTcpAdapter::Core::Core(net::Socket* socket) + : pseudo_tcp_(this, 0), + socket_(socket), + write_waits_for_send_(false), + waiting_write_position_(false), + socket_write_pending_(false) { + // Doesn't trigger callbacks. + pseudo_tcp_.NotifyMTU(kDefaultMtu); +} + +PseudoTcpAdapter::Core::~Core() { +} + +int PseudoTcpAdapter::Core::Read(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback) { + DCHECK(read_callback_.is_null()); + + // Reference the Core in case a callback deletes the adapter. + scoped_refptr<Core> core(this); + + int result = pseudo_tcp_.Recv(buffer->data(), buffer_size); + if (result < 0) { + result = net::MapSystemError(pseudo_tcp_.GetError()); + DCHECK(result < 0); + } + + if (result == net::ERR_IO_PENDING) { + read_buffer_ = buffer; + read_buffer_size_ = buffer_size; + read_callback_ = callback; + } + + AdjustClock(); + + return result; +} + +int PseudoTcpAdapter::Core::Write(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback) { + DCHECK(write_callback_.is_null()); + + // Reference the Core in case a callback deletes the adapter. + scoped_refptr<Core> core(this); + + int result = pseudo_tcp_.Send(buffer->data(), buffer_size); + if (result < 0) { + result = net::MapSystemError(pseudo_tcp_.GetError()); + DCHECK(result < 0); + } + + AdjustClock(); + + if (result == net::ERR_IO_PENDING) { + write_buffer_ = buffer; + write_buffer_size_ = buffer_size; + write_callback_ = callback; + return result; + } + + if (result < 0) + return result; + + // Need to wait until the data is sent to the peer when + // send-confirmation mode is enabled. + if (write_waits_for_send_ && pseudo_tcp_.GetBytesBufferedNotSent() > 0) { + DCHECK(!waiting_write_position_); + waiting_write_position_ = true; + last_write_result_ = result; + write_buffer_ = buffer; + write_buffer_size_ = buffer_size; + write_callback_ = callback; + return net::ERR_IO_PENDING; + } + + return result; +} + +int PseudoTcpAdapter::Core::Connect(const net::CompletionCallback& callback) { + DCHECK_EQ(pseudo_tcp_.State(), cricket::PseudoTcp::TCP_LISTEN); + + // Reference the Core in case a callback deletes the adapter. + scoped_refptr<Core> core(this); + + // Start the connection attempt. + int result = pseudo_tcp_.Connect(); + if (result < 0) + return net::ERR_FAILED; + + AdjustClock(); + + connect_callback_ = callback; + DoReadFromSocket(); + + return net::ERR_IO_PENDING; +} + +void PseudoTcpAdapter::Core::Disconnect() { + // Don't dispatch outstanding callbacks, as mandated by net::StreamSocket. + read_callback_.Reset(); + read_buffer_ = NULL; + write_callback_.Reset(); + write_buffer_ = NULL; + connect_callback_.Reset(); + + // TODO(wez): Connect should succeed if called after Disconnect, which + // PseudoTcp doesn't support, so we need to teardown the internal PseudoTcp + // and create a new one in Connect. + // TODO(wez): Close sets a shutdown flag inside PseudoTcp but has no other + // effect. This should be addressed in PseudoTcp, really. + // In the meantime we can fake OnTcpClosed notification and tear down the + // PseudoTcp. + pseudo_tcp_.Close(true); +} + +bool PseudoTcpAdapter::Core::IsConnected() const { + return pseudo_tcp_.State() == PseudoTcp::TCP_ESTABLISHED; +} + +void PseudoTcpAdapter::Core::OnTcpOpen(PseudoTcp* tcp) { + DCHECK(tcp == &pseudo_tcp_); + + if (!connect_callback_.is_null()) { + net::CompletionCallback callback = connect_callback_; + connect_callback_.Reset(); + callback.Run(net::OK); + } + + OnTcpReadable(tcp); + OnTcpWriteable(tcp); +} + +void PseudoTcpAdapter::Core::OnTcpReadable(PseudoTcp* tcp) { + DCHECK_EQ(tcp, &pseudo_tcp_); + if (read_callback_.is_null()) + return; + + int result = pseudo_tcp_.Recv(read_buffer_->data(), read_buffer_size_); + if (result < 0) { + result = net::MapSystemError(pseudo_tcp_.GetError()); + DCHECK(result < 0); + if (result == net::ERR_IO_PENDING) + return; + } + + AdjustClock(); + + net::CompletionCallback callback = read_callback_; + read_callback_.Reset(); + read_buffer_ = NULL; + callback.Run(result); +} + +void PseudoTcpAdapter::Core::OnTcpWriteable(PseudoTcp* tcp) { + DCHECK_EQ(tcp, &pseudo_tcp_); + if (write_callback_.is_null()) + return; + + if (waiting_write_position_) { + CheckWriteComplete(); + return; + } + + int result = pseudo_tcp_.Send(write_buffer_->data(), write_buffer_size_); + if (result < 0) { + result = net::MapSystemError(pseudo_tcp_.GetError()); + DCHECK(result < 0); + if (result == net::ERR_IO_PENDING) + return; + } + + AdjustClock(); + + if (write_waits_for_send_ && pseudo_tcp_.GetBytesBufferedNotSent() > 0) { + DCHECK(!waiting_write_position_); + waiting_write_position_ = true; + last_write_result_ = result; + return; + } + + net::CompletionCallback callback = write_callback_; + write_callback_.Reset(); + write_buffer_ = NULL; + callback.Run(result); +} + +void PseudoTcpAdapter::Core::OnTcpClosed(PseudoTcp* tcp, uint32 error) { + DCHECK_EQ(tcp, &pseudo_tcp_); + + if (!connect_callback_.is_null()) { + net::CompletionCallback callback = connect_callback_; + connect_callback_.Reset(); + callback.Run(net::MapSystemError(error)); + } + + if (!read_callback_.is_null()) { + net::CompletionCallback callback = read_callback_; + read_callback_.Reset(); + callback.Run(net::MapSystemError(error)); + } + + if (!write_callback_.is_null()) { + net::CompletionCallback callback = write_callback_; + write_callback_.Reset(); + callback.Run(net::MapSystemError(error)); + } +} + +void PseudoTcpAdapter::Core::SetAckDelay(int delay_ms) { + pseudo_tcp_.SetOption(cricket::PseudoTcp::OPT_ACKDELAY, delay_ms); +} + +void PseudoTcpAdapter::Core::SetNoDelay(bool no_delay) { + pseudo_tcp_.SetOption(cricket::PseudoTcp::OPT_NODELAY, no_delay ? 1 : 0); +} + +void PseudoTcpAdapter::Core::SetReceiveBufferSize(int32 size) { + pseudo_tcp_.SetOption(cricket::PseudoTcp::OPT_RCVBUF, size); +} + +void PseudoTcpAdapter::Core::SetSendBufferSize(int32 size) { + pseudo_tcp_.SetOption(cricket::PseudoTcp::OPT_SNDBUF, size); +} + +void PseudoTcpAdapter::Core::SetWriteWaitsForSend(bool write_waits_for_send) { + write_waits_for_send_ = write_waits_for_send; +} + +void PseudoTcpAdapter::Core::DeleteSocket() { + socket_.reset(); +} + +cricket::IPseudoTcpNotify::WriteResult PseudoTcpAdapter::Core::TcpWritePacket( + PseudoTcp* tcp, + const char* buffer, + size_t len) { + DCHECK_EQ(tcp, &pseudo_tcp_); + + // If we already have a write pending, we behave like a congested network, + // returning success for the write, but dropping the packet. PseudoTcp will + // back-off and retransmit, adjusting for the perceived congestion. + if (socket_write_pending_) + return IPseudoTcpNotify::WR_SUCCESS; + + scoped_refptr<net::IOBuffer> write_buffer = new net::IOBuffer(len); + memcpy(write_buffer->data(), buffer, len); + + // Our underlying socket is datagram-oriented, which means it should either + // send exactly as many bytes as we requested, or fail. + int result; + if (socket_.get()) { + result = socket_->Write( + write_buffer.get(), + len, + base::Bind(&PseudoTcpAdapter::Core::OnWritten, base::Unretained(this))); + } else { + result = net::ERR_CONNECTION_CLOSED; + } + if (result == net::ERR_IO_PENDING) { + socket_write_pending_ = true; + return IPseudoTcpNotify::WR_SUCCESS; + } else if (result == net::ERR_MSG_TOO_BIG) { + return IPseudoTcpNotify::WR_TOO_LARGE; + } else if (result < 0) { + return IPseudoTcpNotify::WR_FAIL; + } else { + return IPseudoTcpNotify::WR_SUCCESS; + } +} + +void PseudoTcpAdapter::Core::DoReadFromSocket() { + if (!socket_read_buffer_.get()) + socket_read_buffer_ = new net::IOBuffer(kReadBufferSize); + + int result = 1; + while (socket_.get() && result > 0) { + result = socket_->Read( + socket_read_buffer_.get(), + kReadBufferSize, + base::Bind(&PseudoTcpAdapter::Core::OnRead, base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + HandleReadResults(result); + } +} + +void PseudoTcpAdapter::Core::HandleReadResults(int result) { + if (result <= 0) { + LOG(ERROR) << "Read returned " << result; + return; + } + + // TODO(wez): Disconnect on failure of NotifyPacket? + pseudo_tcp_.NotifyPacket(socket_read_buffer_->data(), result); + AdjustClock(); + + CheckWriteComplete(); +} + +void PseudoTcpAdapter::Core::OnRead(int result) { + // Reference the Core in case a callback deletes the adapter. + scoped_refptr<Core> core(this); + + HandleReadResults(result); + if (result >= 0) + DoReadFromSocket(); +} + +void PseudoTcpAdapter::Core::OnWritten(int result) { + // Reference the Core in case a callback deletes the adapter. + scoped_refptr<Core> core(this); + + socket_write_pending_ = false; + if (result < 0) { + LOG(WARNING) << "Write failed. Error code: " << result; + } +} + +void PseudoTcpAdapter::Core::AdjustClock() { + long timeout = 0; + if (pseudo_tcp_.GetNextClock(PseudoTcp::Now(), timeout)) { + timer_.Stop(); + timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(std::max(timeout, 0L)), this, + &PseudoTcpAdapter::Core::HandleTcpClock); + } +} + +void PseudoTcpAdapter::Core::HandleTcpClock() { + // Reference the Core in case a callback deletes the adapter. + scoped_refptr<Core> core(this); + + pseudo_tcp_.NotifyClock(PseudoTcp::Now()); + AdjustClock(); + + CheckWriteComplete(); +} + +void PseudoTcpAdapter::Core::CheckWriteComplete() { + if (!write_callback_.is_null() && waiting_write_position_) { + if (pseudo_tcp_.GetBytesBufferedNotSent() == 0) { + waiting_write_position_ = false; + + net::CompletionCallback callback = write_callback_; + write_callback_.Reset(); + write_buffer_ = NULL; + callback.Run(last_write_result_); + } + } +} + +// Public interface implemention. + +PseudoTcpAdapter::PseudoTcpAdapter(net::Socket* socket) + : core_(new Core(socket)) { +} + +PseudoTcpAdapter::~PseudoTcpAdapter() { + Disconnect(); + + // Make sure that the underlying socket is destroyed before PseudoTcp. + core_->DeleteSocket(); +} + +int PseudoTcpAdapter::Read(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + return core_->Read(buffer, buffer_size, callback); +} + +int PseudoTcpAdapter::Write(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + return core_->Write(buffer, buffer_size, callback); +} + +bool PseudoTcpAdapter::SetReceiveBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + + core_->SetReceiveBufferSize(size); + return false; +} + +bool PseudoTcpAdapter::SetSendBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + + core_->SetSendBufferSize(size); + return false; +} + +int PseudoTcpAdapter::Connect(const net::CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + + // net::StreamSocket requires that Connect return OK if already connected. + if (IsConnected()) + return net::OK; + + return core_->Connect(callback); +} + +void PseudoTcpAdapter::Disconnect() { + DCHECK(CalledOnValidThread()); + core_->Disconnect(); +} + +bool PseudoTcpAdapter::IsConnected() const { + return core_->IsConnected(); +} + +bool PseudoTcpAdapter::IsConnectedAndIdle() const { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); + return false; +} + +int PseudoTcpAdapter::GetPeerAddress(net::IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + + // We don't have a meaningful peer address, but we can't return an + // error, so we return a INADDR_ANY instead. + net::IPAddressNumber ip_address(net::kIPv4AddressSize); + *address = net::IPEndPoint(ip_address, 0); + return net::OK; +} + +int PseudoTcpAdapter::GetLocalAddress(net::IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); + return net::ERR_FAILED; +} + +const net::BoundNetLog& PseudoTcpAdapter::NetLog() const { + DCHECK(CalledOnValidThread()); + return net_log_; +} + +void PseudoTcpAdapter::SetSubresourceSpeculation() { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); +} + +void PseudoTcpAdapter::SetOmniboxSpeculation() { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); +} + +bool PseudoTcpAdapter::WasEverUsed() const { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); + return true; +} + +bool PseudoTcpAdapter::UsingTCPFastOpen() const { + DCHECK(CalledOnValidThread()); + return false; +} + +bool PseudoTcpAdapter::WasNpnNegotiated() const { + DCHECK(CalledOnValidThread()); + return false; +} + +net::NextProto PseudoTcpAdapter::GetNegotiatedProtocol() const { + DCHECK(CalledOnValidThread()); + return net::kProtoUnknown; +} + +bool PseudoTcpAdapter::GetSSLInfo(net::SSLInfo* ssl_info) { + DCHECK(CalledOnValidThread()); + return false; +} + +void PseudoTcpAdapter::SetAckDelay(int delay_ms) { + DCHECK(CalledOnValidThread()); + core_->SetAckDelay(delay_ms); +} + +void PseudoTcpAdapter::SetNoDelay(bool no_delay) { + DCHECK(CalledOnValidThread()); + core_->SetNoDelay(no_delay); +} + +void PseudoTcpAdapter::SetWriteWaitsForSend(bool write_waits_for_send) { + DCHECK(CalledOnValidThread()); + core_->SetWriteWaitsForSend(write_waits_for_send); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/pseudotcp_adapter.h b/chromium/jingle/glue/pseudotcp_adapter.h new file mode 100644 index 00000000000..63764c4665a --- /dev/null +++ b/chromium/jingle/glue/pseudotcp_adapter.h @@ -0,0 +1,92 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_GLUE_PSEUDOTCP_ADAPTER_H_ +#define JINGLE_GLUE_PSEUDOTCP_ADAPTER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/timer/timer.h" +#include "net/base/net_log.h" +#include "net/socket/stream_socket.h" +#include "third_party/libjingle/source/talk/p2p/base/pseudotcp.h" + +namespace jingle_glue { + +// PseudoTcpAdapter adapts a connectionless net::Socket to a connection- +// oriented net::StreamSocket using PseudoTcp. Because net::StreamSockets +// can be deleted during callbacks, while PseudoTcp cannot, the core of the +// PseudoTcpAdapter is reference counted, with a reference held by the +// adapter, and an additional reference held on the stack during callbacks. +class PseudoTcpAdapter : public net::StreamSocket, base::NonThreadSafe { + public: + // Creates an adapter for the supplied Socket. |socket| should already + // be ready for use, and ownership of it will be assumed by the adapter. + PseudoTcpAdapter(net::Socket* socket); + virtual ~PseudoTcpAdapter(); + + // net::Socket implementation. + virtual int Read(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Write(net::IOBuffer* buffer, int buffer_size, + const net::CompletionCallback& callback) OVERRIDE; + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + + // net::StreamSocket implementation. + virtual int Connect(const net::CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectedAndIdle() const OVERRIDE; + virtual int GetPeerAddress(net::IPEndPoint* address) const OVERRIDE; + virtual int GetLocalAddress(net::IPEndPoint* address) const OVERRIDE; + virtual const net::BoundNetLog& NetLog() const OVERRIDE; + virtual void SetSubresourceSpeculation() OVERRIDE; + virtual void SetOmniboxSpeculation() OVERRIDE; + virtual bool WasEverUsed() const OVERRIDE; + virtual bool UsingTCPFastOpen() const OVERRIDE; + virtual bool WasNpnNegotiated() const OVERRIDE; + virtual net::NextProto GetNegotiatedProtocol() const OVERRIDE; + virtual bool GetSSLInfo(net::SSLInfo* ssl_info) OVERRIDE; + + // Set the delay for sending ACK. + void SetAckDelay(int delay_ms); + + // Set whether Nagle's algorithm is enabled. + void SetNoDelay(bool no_delay); + + // When write_waits_for_send flag is set to true the Write() method + // will wait until the data is sent to the remote end before the + // write completes (it still doesn't wait until the data is received + // and acknowledged by the remote end). Otherwise write completes + // after the data has been copied to the send buffer. + // + // This flag is useful in cases when the sender needs to get + // feedback from the connection when it is congested. E.g. remoting + // host uses this feature to adjust screen capturing rate according + // to the available bandwidth. In the same time it may negatively + // impact performance in some cases. E.g. when the sender writes one + // byte at a time then each byte will always be sent in a separate + // packet. + // + // TODO(sergeyu): Remove this flag once remoting has a better + // flow-control solution. + void SetWriteWaitsForSend(bool write_waits_for_send); + + private: + class Core; + + scoped_refptr<Core> core_; + + net::BoundNetLog net_log_; + + DISALLOW_COPY_AND_ASSIGN(PseudoTcpAdapter); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_STREAM_SOCKET_ADAPTER_H_ diff --git a/chromium/jingle/glue/pseudotcp_adapter_unittest.cc b/chromium/jingle/glue/pseudotcp_adapter_unittest.cc new file mode 100644 index 00000000000..08e59fc3c79 --- /dev/null +++ b/chromium/jingle/glue/pseudotcp_adapter_unittest.cc @@ -0,0 +1,447 @@ +// Copyright (c) 2012 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/glue/pseudotcp_adapter.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "jingle/glue/thread_wrapper.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/udp/udp_socket.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace jingle_glue { +namespace { +class FakeSocket; +} // namespace +} // namespace jingle_glue + +namespace jingle_glue { + +namespace { + +// The range is chosen arbitrarily. It must be big enough so that we +// always have at least two UDP ports available. +const int kMinPort = 32000; +const int kMaxPort = 33000; + +const int kMessageSize = 1024; +const int kMessages = 100; +const int kTestDataSize = kMessages * kMessageSize; + +class RateLimiter { + public: + virtual ~RateLimiter() { }; + // Returns true if the new packet needs to be dropped, false otherwise. + virtual bool DropNextPacket() = 0; +}; + +class LeakyBucket : public RateLimiter { + public: + // |rate| is in drops per second. + LeakyBucket(double volume, double rate) + : volume_(volume), + rate_(rate), + level_(0.0), + last_update_(base::TimeTicks::HighResNow()) { + } + + virtual ~LeakyBucket() { } + + virtual bool DropNextPacket() OVERRIDE { + base::TimeTicks now = base::TimeTicks::HighResNow(); + double interval = (now - last_update_).InSecondsF(); + last_update_ = now; + level_ = level_ + 1.0 - interval * rate_; + if (level_ > volume_) { + level_ = volume_; + return true; + } else if (level_ < 0.0) { + level_ = 0.0; + } + return false; + } + + private: + double volume_; + double rate_; + double level_; + base::TimeTicks last_update_; +}; + +class FakeSocket : public net::Socket { + public: + FakeSocket() + : rate_limiter_(NULL), + latency_ms_(0) { + } + virtual ~FakeSocket() { } + + void AppendInputPacket(const std::vector<char>& data) { + if (rate_limiter_ && rate_limiter_->DropNextPacket()) + return; // Lose the packet. + + if (!read_callback_.is_null()) { + int size = std::min(read_buffer_size_, static_cast<int>(data.size())); + memcpy(read_buffer_->data(), &data[0], data.size()); + net::CompletionCallback cb = read_callback_; + read_callback_.Reset(); + read_buffer_ = NULL; + cb.Run(size); + } else { + incoming_packets_.push_back(data); + } + } + + void Connect(FakeSocket* peer_socket) { + peer_socket_ = peer_socket; + } + + void set_rate_limiter(RateLimiter* rate_limiter) { + rate_limiter_ = rate_limiter; + }; + + void set_latency(int latency_ms) { latency_ms_ = latency_ms; }; + + // net::Socket interface. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE { + CHECK(read_callback_.is_null()); + CHECK(buf); + + if (incoming_packets_.size() > 0) { + scoped_refptr<net::IOBuffer> buffer(buf); + int size = std::min( + static_cast<int>(incoming_packets_.front().size()), buf_len); + memcpy(buffer->data(), &*incoming_packets_.front().begin(), size); + incoming_packets_.pop_front(); + return size; + } else { + read_callback_ = callback; + read_buffer_ = buf; + read_buffer_size_ = buf_len; + return net::ERR_IO_PENDING; + } + } + + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE { + DCHECK(buf); + if (peer_socket_) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&FakeSocket::AppendInputPacket, + base::Unretained(peer_socket_), + std::vector<char>(buf->data(), buf->data() + buf_len)), + base::TimeDelta::FromMilliseconds(latency_ms_)); + } + + return buf_len; + } + + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE { + NOTIMPLEMENTED(); + return false; + } + virtual bool SetSendBufferSize(int32 size) OVERRIDE { + NOTIMPLEMENTED(); + return false; + } + + private: + scoped_refptr<net::IOBuffer> read_buffer_; + int read_buffer_size_; + net::CompletionCallback read_callback_; + + std::deque<std::vector<char> > incoming_packets_; + + FakeSocket* peer_socket_; + RateLimiter* rate_limiter_; + int latency_ms_; +}; + +class TCPChannelTester : public base::RefCountedThreadSafe<TCPChannelTester> { + public: + TCPChannelTester(base::MessageLoop* message_loop, + net::Socket* client_socket, + net::Socket* host_socket) + : message_loop_(message_loop), + host_socket_(host_socket), + client_socket_(client_socket), + done_(false), + write_errors_(0), + read_errors_(0) {} + + void Start() { + message_loop_->PostTask( + FROM_HERE, base::Bind(&TCPChannelTester::DoStart, this)); + } + + void CheckResults() { + EXPECT_EQ(0, write_errors_); + EXPECT_EQ(0, read_errors_); + + ASSERT_EQ(kTestDataSize + kMessageSize, input_buffer_->capacity()); + + output_buffer_->SetOffset(0); + ASSERT_EQ(kTestDataSize, output_buffer_->size()); + + EXPECT_EQ(0, memcmp(output_buffer_->data(), + input_buffer_->StartOfBuffer(), kTestDataSize)); + } + + protected: + virtual ~TCPChannelTester() {} + + void Done() { + done_ = true; + message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); + } + + void DoStart() { + InitBuffers(); + DoRead(); + DoWrite(); + } + + void InitBuffers() { + output_buffer_ = new net::DrainableIOBuffer( + new net::IOBuffer(kTestDataSize), kTestDataSize); + memset(output_buffer_->data(), 123, kTestDataSize); + + input_buffer_ = new net::GrowableIOBuffer(); + // Always keep kMessageSize bytes available at the end of the input buffer. + input_buffer_->SetCapacity(kMessageSize); + } + + void DoWrite() { + int result = 1; + while (result > 0) { + if (output_buffer_->BytesRemaining() == 0) + break; + + int bytes_to_write = std::min(output_buffer_->BytesRemaining(), + kMessageSize); + result = client_socket_->Write( + output_buffer_.get(), + bytes_to_write, + base::Bind(&TCPChannelTester::OnWritten, base::Unretained(this))); + HandleWriteResult(result); + } + } + + void OnWritten(int result) { + HandleWriteResult(result); + DoWrite(); + } + + void HandleWriteResult(int result) { + if (result <= 0 && result != net::ERR_IO_PENDING) { + LOG(ERROR) << "Received error " << result << " when trying to write"; + write_errors_++; + Done(); + } else if (result > 0) { + output_buffer_->DidConsume(result); + } + } + + void DoRead() { + int result = 1; + while (result > 0) { + input_buffer_->set_offset(input_buffer_->capacity() - kMessageSize); + + result = host_socket_->Read( + input_buffer_.get(), + kMessageSize, + base::Bind(&TCPChannelTester::OnRead, base::Unretained(this))); + HandleReadResult(result); + }; + } + + void OnRead(int result) { + HandleReadResult(result); + DoRead(); + } + + void HandleReadResult(int result) { + if (result <= 0 && result != net::ERR_IO_PENDING) { + if (!done_) { + LOG(ERROR) << "Received error " << result << " when trying to read"; + read_errors_++; + Done(); + } + } else if (result > 0) { + // Allocate memory for the next read. + input_buffer_->SetCapacity(input_buffer_->capacity() + result); + if (input_buffer_->capacity() == kTestDataSize + kMessageSize) + Done(); + } + } + + private: + friend class base::RefCountedThreadSafe<TCPChannelTester>; + + base::MessageLoop* message_loop_; + net::Socket* host_socket_; + net::Socket* client_socket_; + bool done_; + + scoped_refptr<net::DrainableIOBuffer> output_buffer_; + scoped_refptr<net::GrowableIOBuffer> input_buffer_; + + int write_errors_; + int read_errors_; +}; + +class PseudoTcpAdapterTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + JingleThreadWrapper::EnsureForCurrentMessageLoop(); + + host_socket_ = new FakeSocket(); + client_socket_ = new FakeSocket(); + + host_socket_->Connect(client_socket_); + client_socket_->Connect(host_socket_); + + host_pseudotcp_.reset(new PseudoTcpAdapter(host_socket_)); + client_pseudotcp_.reset(new PseudoTcpAdapter(client_socket_)); + } + + FakeSocket* host_socket_; + FakeSocket* client_socket_; + + scoped_ptr<PseudoTcpAdapter> host_pseudotcp_; + scoped_ptr<PseudoTcpAdapter> client_pseudotcp_; + base::MessageLoop message_loop_; +}; + +TEST_F(PseudoTcpAdapterTest, DataTransfer) { + net::TestCompletionCallback host_connect_cb; + net::TestCompletionCallback client_connect_cb; + + int rv1 = host_pseudotcp_->Connect(host_connect_cb.callback()); + int rv2 = client_pseudotcp_->Connect(client_connect_cb.callback()); + + if (rv1 == net::ERR_IO_PENDING) + rv1 = host_connect_cb.WaitForResult(); + if (rv2 == net::ERR_IO_PENDING) + rv2 = client_connect_cb.WaitForResult(); + ASSERT_EQ(net::OK, rv1); + ASSERT_EQ(net::OK, rv2); + + scoped_refptr<TCPChannelTester> tester = + new TCPChannelTester(&message_loop_, host_pseudotcp_.get(), + client_pseudotcp_.get()); + + tester->Start(); + message_loop_.Run(); + tester->CheckResults(); +} + +TEST_F(PseudoTcpAdapterTest, LimitedChannel) { + const int kLatencyMs = 20; + const int kPacketsPerSecond = 400; + const int kBurstPackets = 10; + + LeakyBucket host_limiter(kBurstPackets, kPacketsPerSecond); + host_socket_->set_latency(kLatencyMs); + host_socket_->set_rate_limiter(&host_limiter); + + LeakyBucket client_limiter(kBurstPackets, kPacketsPerSecond); + host_socket_->set_latency(kLatencyMs); + client_socket_->set_rate_limiter(&client_limiter); + + net::TestCompletionCallback host_connect_cb; + net::TestCompletionCallback client_connect_cb; + + int rv1 = host_pseudotcp_->Connect(host_connect_cb.callback()); + int rv2 = client_pseudotcp_->Connect(client_connect_cb.callback()); + + if (rv1 == net::ERR_IO_PENDING) + rv1 = host_connect_cb.WaitForResult(); + if (rv2 == net::ERR_IO_PENDING) + rv2 = client_connect_cb.WaitForResult(); + ASSERT_EQ(net::OK, rv1); + ASSERT_EQ(net::OK, rv2); + + scoped_refptr<TCPChannelTester> tester = + new TCPChannelTester(&message_loop_, host_pseudotcp_.get(), + client_pseudotcp_.get()); + + tester->Start(); + message_loop_.Run(); + tester->CheckResults(); +} + +class DeleteOnConnected { + public: + DeleteOnConnected(base::MessageLoop* message_loop, + scoped_ptr<PseudoTcpAdapter>* adapter) + : message_loop_(message_loop), adapter_(adapter) {} + void OnConnected(int error) { + adapter_->reset(); + message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); + } + base::MessageLoop* message_loop_; + scoped_ptr<PseudoTcpAdapter>* adapter_; +}; + +TEST_F(PseudoTcpAdapterTest, DeleteOnConnected) { + // This test verifies that deleting the adapter mid-callback doesn't lead + // to deleted structures being touched as the stack unrolls, so the failure + // mode is a crash rather than a normal test failure. + net::TestCompletionCallback client_connect_cb; + DeleteOnConnected host_delete(&message_loop_, &host_pseudotcp_); + + host_pseudotcp_->Connect(base::Bind(&DeleteOnConnected::OnConnected, + base::Unretained(&host_delete))); + client_pseudotcp_->Connect(client_connect_cb.callback()); + message_loop_.Run(); + + ASSERT_EQ(NULL, host_pseudotcp_.get()); +} + +// Verify that we can send/receive data with the write-waits-for-send +// flag set. +TEST_F(PseudoTcpAdapterTest, WriteWaitsForSendLetsDataThrough) { + net::TestCompletionCallback host_connect_cb; + net::TestCompletionCallback client_connect_cb; + + host_pseudotcp_->SetWriteWaitsForSend(true); + client_pseudotcp_->SetWriteWaitsForSend(true); + + // Disable Nagle's algorithm because the test is slow when it is + // enabled. + host_pseudotcp_->SetNoDelay(true); + + int rv1 = host_pseudotcp_->Connect(host_connect_cb.callback()); + int rv2 = client_pseudotcp_->Connect(client_connect_cb.callback()); + + if (rv1 == net::ERR_IO_PENDING) + rv1 = host_connect_cb.WaitForResult(); + if (rv2 == net::ERR_IO_PENDING) + rv2 = client_connect_cb.WaitForResult(); + ASSERT_EQ(net::OK, rv1); + ASSERT_EQ(net::OK, rv2); + + scoped_refptr<TCPChannelTester> tester = + new TCPChannelTester(&message_loop_, host_pseudotcp_.get(), + client_pseudotcp_.get()); + + tester->Start(); + message_loop_.Run(); + tester->CheckResults(); +} + +} // namespace + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/resolving_client_socket_factory.h b/chromium/jingle/glue/resolving_client_socket_factory.h new file mode 100644 index 00000000000..d1b9fc1f389 --- /dev/null +++ b/chromium/jingle/glue/resolving_client_socket_factory.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_GLUE_RESOLVING_CLIENT_SOCKET_FACTORY_H_ +#define JINGLE_GLUE_RESOLVING_CLIENT_SOCKET_FACTORY_H_ + +#include "base/memory/scoped_ptr.h" + +namespace net { +class ClientSocketHandle; +class HostPortPair; +class SSLClientSocket; +class StreamSocket; +} // namespace net + +// TODO(sanjeevr): Move this to net/ + +namespace jingle_glue { + +// Interface for a ClientSocketFactory that creates ClientSockets that can +// resolve host names and tunnel through proxies. +class ResolvingClientSocketFactory { + public: + virtual ~ResolvingClientSocketFactory() { } + // Method to create a transport socket using a HostPortPair. + virtual scoped_ptr<net::StreamSocket> CreateTransportClientSocket( + const net::HostPortPair& host_and_port) = 0; + + virtual scoped_ptr<net::SSLClientSocket> CreateSSLClientSocket( + scoped_ptr<net::ClientSocketHandle> transport_socket, + const net::HostPortPair& host_and_port) = 0; +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_RESOLVING_CLIENT_SOCKET_FACTORY_H_ diff --git a/chromium/jingle/glue/task_pump.cc b/chromium/jingle/glue/task_pump.cc new file mode 100644 index 00000000000..be5f25b4b67 --- /dev/null +++ b/chromium/jingle/glue/task_pump.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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 "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/task_pump.h" + +namespace jingle_glue { + +TaskPump::TaskPump() + : weak_factory_(this), + posted_wake_(false), + stopped_(false) {} + +TaskPump::~TaskPump() { + DCHECK(CalledOnValidThread()); +} + +void TaskPump::WakeTasks() { + DCHECK(CalledOnValidThread()); + if (!stopped_ && !posted_wake_) { + base::MessageLoop* current_message_loop = base::MessageLoop::current(); + CHECK(current_message_loop); + // Do the requested wake up. + current_message_loop->PostTask( + FROM_HERE, + base::Bind(&TaskPump::CheckAndRunTasks, weak_factory_.GetWeakPtr())); + posted_wake_ = true; + } +} + +int64 TaskPump::CurrentTime() { + DCHECK(CalledOnValidThread()); + // Only timeout tasks rely on this function. Since we're not using + // libjingle tasks for timeout, it's safe to return 0 here. + return 0; +} + +void TaskPump::Stop() { + stopped_ = true; +} + +void TaskPump::CheckAndRunTasks() { + DCHECK(CalledOnValidThread()); + if (stopped_) { + return; + } + posted_wake_ = false; + // We shouldn't be using libjingle for timeout tasks, so we should + // have no timeout tasks at all. + + // TODO(akalin): Add HasTimeoutTask() back in TaskRunner class and + // uncomment this check. + // DCHECK(!HasTimeoutTask()) + RunTasks(); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/task_pump.h b/chromium/jingle/glue/task_pump.h new file mode 100644 index 00000000000..17ce68916da --- /dev/null +++ b/chromium/jingle/glue/task_pump.h @@ -0,0 +1,42 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_GLUE_TASK_PUMP_H_ +#define JINGLE_GLUE_TASK_PUMP_H_ + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "third_party/libjingle/source/talk/base/taskrunner.h" + +namespace jingle_glue { + +// talk_base::TaskRunner implementation that works on chromium threads. +class TaskPump : public talk_base::TaskRunner, public base::NonThreadSafe { + public: + TaskPump(); + + virtual ~TaskPump(); + + // talk_base::TaskRunner implementation. + virtual void WakeTasks() OVERRIDE; + virtual int64 CurrentTime() OVERRIDE; + + // No tasks will be processed after this is called, even if + // WakeTasks() is called. + void Stop(); + + private: + void CheckAndRunTasks(); + + base::WeakPtrFactory<TaskPump> weak_factory_; + bool posted_wake_; + bool stopped_; + + DISALLOW_COPY_AND_ASSIGN(TaskPump); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_TASK_PUMP_H_ diff --git a/chromium/jingle/glue/task_pump_unittest.cc b/chromium/jingle/glue/task_pump_unittest.cc new file mode 100644 index 00000000000..a9d5c5c420d --- /dev/null +++ b/chromium/jingle/glue/task_pump_unittest.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2012 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/glue/task_pump.h" + +#include "base/message_loop/message_loop.h" +#include "jingle/glue/mock_task.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace jingle_glue { + +namespace { + +using ::testing::Return; + +class TaskPumpTest : public testing::Test { + private: + base::MessageLoop message_loop_; +}; + +TEST_F(TaskPumpTest, Basic) { + TaskPump task_pump; + MockTask* task = new MockTask(&task_pump); + // We have to do this since the state enum is protected in + // talk_base::Task. + const int TASK_STATE_DONE = 2; + EXPECT_CALL(*task, ProcessStart()).WillOnce(Return(TASK_STATE_DONE)); + task->Start(); + + base::MessageLoop::current()->RunUntilIdle(); +} + +TEST_F(TaskPumpTest, Stop) { + TaskPump task_pump; + MockTask* task = new MockTask(&task_pump); + // We have to do this since the state enum is protected in + // talk_base::Task. + const int TASK_STATE_ERROR = 3; + ON_CALL(*task, ProcessStart()).WillByDefault(Return(TASK_STATE_ERROR)); + EXPECT_CALL(*task, ProcessStart()).Times(0); + task->Start(); + + task_pump.Stop(); + base::MessageLoop::current()->RunUntilIdle(); +} + +} // namespace + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/thread_wrapper.cc b/chromium/jingle/glue/thread_wrapper.cc new file mode 100644 index 00000000000..f26bc3c113f --- /dev/null +++ b/chromium/jingle/glue/thread_wrapper.cc @@ -0,0 +1,302 @@ +// Copyright (c) 2012 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/glue/thread_wrapper.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/lazy_instance.h" +#include "base/threading/thread_local.h" +#include "third_party/libjingle/source/talk/base/nullsocketserver.h" + +namespace jingle_glue { + +struct JingleThreadWrapper::PendingSend { + PendingSend(const talk_base::Message& message_value) + : sending_thread(JingleThreadWrapper::current()), + message(message_value), + done_event(true, false) { + DCHECK(sending_thread); + } + + JingleThreadWrapper* sending_thread; + talk_base::Message message; + base::WaitableEvent done_event; +}; + +base::LazyInstance<base::ThreadLocalPointer<JingleThreadWrapper> > + g_jingle_thread_wrapper = LAZY_INSTANCE_INITIALIZER; + +// static +void JingleThreadWrapper::EnsureForCurrentMessageLoop() { + if (JingleThreadWrapper::current() == NULL) { + base::MessageLoop* message_loop = base::MessageLoop::current(); + g_jingle_thread_wrapper.Get() + .Set(new JingleThreadWrapper(message_loop->message_loop_proxy())); + message_loop->AddDestructionObserver(current()); + } + + DCHECK_EQ(talk_base::Thread::Current(), current()); +} + +// static +JingleThreadWrapper* JingleThreadWrapper::current() { + return g_jingle_thread_wrapper.Get().Get(); +} + +JingleThreadWrapper::JingleThreadWrapper( + scoped_refptr<base::SingleThreadTaskRunner> task_runner) + : talk_base::Thread(new talk_base::NullSocketServer()), + task_runner_(task_runner), + send_allowed_(false), + last_task_id_(0), + pending_send_event_(true, false), + weak_ptr_factory_(this), + weak_ptr_(weak_ptr_factory_.GetWeakPtr()) { + DCHECK(task_runner->BelongsToCurrentThread()); + DCHECK(!talk_base::Thread::Current()); + talk_base::MessageQueueManager::Instance()->Add(this); + WrapCurrent(); +} + +JingleThreadWrapper::~JingleThreadWrapper() { + Clear(NULL, talk_base::MQID_ANY, NULL); +} + +void JingleThreadWrapper::WillDestroyCurrentMessageLoop() { + DCHECK_EQ(talk_base::Thread::Current(), current()); + UnwrapCurrent(); + g_jingle_thread_wrapper.Get().Set(NULL); + talk_base::ThreadManager::Instance()->SetCurrentThread(NULL); + talk_base::MessageQueueManager::Instance()->Remove(this); + talk_base::SocketServer* ss = socketserver(); + delete this; + delete ss; +} + +void JingleThreadWrapper::Post( + talk_base::MessageHandler* handler, uint32 message_id, + talk_base::MessageData* data, bool time_sensitive) { + PostTaskInternal(0, handler, message_id, data); +} + +void JingleThreadWrapper::PostDelayed( + int delay_ms, talk_base::MessageHandler* handler, + uint32 message_id, talk_base::MessageData* data) { + PostTaskInternal(delay_ms, handler, message_id, data); +} + +void JingleThreadWrapper::Clear(talk_base::MessageHandler* handler, uint32 id, + talk_base::MessageList* removed) { + base::AutoLock auto_lock(lock_); + + for (MessagesQueue::iterator it = messages_.begin(); + it != messages_.end();) { + MessagesQueue::iterator next = it; + ++next; + + if (it->second.Match(handler, id)) { + if (removed) { + removed->push_back(it->second); + } else { + delete it->second.pdata; + } + messages_.erase(it); + } + + it = next; + } + + for (std::list<PendingSend*>::iterator it = pending_send_messages_.begin(); + it != pending_send_messages_.end();) { + std::list<PendingSend*>::iterator next = it; + ++next; + + if ((*it)->message.Match(handler, id)) { + if (removed) { + removed ->push_back((*it)->message); + } else { + delete (*it)->message.pdata; + } + (*it)->done_event.Signal(); + pending_send_messages_.erase(it); + } + + it = next; + } +} + +void JingleThreadWrapper::Send(talk_base::MessageHandler *handler, uint32 id, + talk_base::MessageData *data) { + if (fStop_) + return; + + JingleThreadWrapper* current_thread = JingleThreadWrapper::current(); + DCHECK(current_thread != NULL) << "Send() can be called only from a " + "thread that has JingleThreadWrapper."; + + talk_base::Message message; + message.phandler = handler; + message.message_id = id; + message.pdata = data; + + if (current_thread == this) { + handler->OnMessage(&message); + return; + } + + // Send message from a thread different than |this|. + + // Allow inter-thread send only from threads that have + // |send_allowed_| flag set. + DCHECK(current_thread->send_allowed_) << "Send()'ing synchronous " + "messages is not allowed from the current thread."; + + PendingSend pending_send(message); + { + base::AutoLock auto_lock(lock_); + pending_send_messages_.push_back(&pending_send); + } + + // Need to signal |pending_send_event_| here in case the thread is + // sending message to another thread. + pending_send_event_.Signal(); + task_runner_->PostTask(FROM_HERE, + base::Bind(&JingleThreadWrapper::ProcessPendingSends, + weak_ptr_)); + + + while (!pending_send.done_event.IsSignaled()) { + base::WaitableEvent* events[] = {&pending_send.done_event, + ¤t_thread->pending_send_event_}; + size_t event = base::WaitableEvent::WaitMany(events, arraysize(events)); + DCHECK(event == 0 || event == 1); + + if (event == 1) + current_thread->ProcessPendingSends(); + } +} + +void JingleThreadWrapper::ProcessPendingSends() { + while (true) { + PendingSend* pending_send = NULL; + { + base::AutoLock auto_lock(lock_); + if (!pending_send_messages_.empty()) { + pending_send = pending_send_messages_.front(); + pending_send_messages_.pop_front(); + } else { + // Reset the event while |lock_| is still locked. + pending_send_event_.Reset(); + break; + } + } + if (pending_send) { + pending_send->message.phandler->OnMessage(&pending_send->message); + pending_send->done_event.Signal(); + } + } +} + +void JingleThreadWrapper::PostTaskInternal( + int delay_ms, talk_base::MessageHandler* handler, + uint32 message_id, talk_base::MessageData* data) { + int task_id; + talk_base::Message message; + message.phandler = handler; + message.message_id = message_id; + message.pdata = data; + { + base::AutoLock auto_lock(lock_); + task_id = ++last_task_id_; + messages_.insert(std::pair<int, talk_base::Message>(task_id, message)); + } + + if (delay_ms <= 0) { + task_runner_->PostTask(FROM_HERE, + base::Bind(&JingleThreadWrapper::RunTask, + weak_ptr_, task_id)); + } else { + task_runner_->PostDelayedTask(FROM_HERE, + base::Bind(&JingleThreadWrapper::RunTask, + weak_ptr_, task_id), + base::TimeDelta::FromMilliseconds(delay_ms)); + } +} + +void JingleThreadWrapper::RunTask(int task_id) { + bool have_message = false; + talk_base::Message message; + { + base::AutoLock auto_lock(lock_); + MessagesQueue::iterator it = messages_.find(task_id); + if (it != messages_.end()) { + have_message = true; + message = it->second; + messages_.erase(it); + } + } + + if (have_message) { + if (message.message_id == talk_base::MQID_DISPOSE) { + DCHECK(message.phandler == NULL); + delete message.pdata; + } else { + message.phandler->OnMessage(&message); + } + } +} + +// All methods below are marked as not reached. See comments in the +// header for more details. +void JingleThreadWrapper::Quit() { + NOTREACHED(); +} + +bool JingleThreadWrapper::IsQuitting() { + NOTREACHED(); + return false; +} + +void JingleThreadWrapper::Restart() { + NOTREACHED(); +} + +bool JingleThreadWrapper::Get(talk_base::Message*, int, bool) { + NOTREACHED(); + return false; +} + +bool JingleThreadWrapper::Peek(talk_base::Message*, int) { + NOTREACHED(); + return false; +} + +void JingleThreadWrapper::PostAt(uint32, talk_base::MessageHandler*, + uint32, talk_base::MessageData*) { + NOTREACHED(); +} + +void JingleThreadWrapper::Dispatch(talk_base::Message* message) { + NOTREACHED(); +} + +void JingleThreadWrapper::ReceiveSends() { + NOTREACHED(); +} + +int JingleThreadWrapper::GetDelay() { + NOTREACHED(); + return 0; +} + +void JingleThreadWrapper::Stop() { + NOTREACHED(); +} + +void JingleThreadWrapper::Run() { + NOTREACHED(); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/thread_wrapper.h b/chromium/jingle/glue/thread_wrapper.h new file mode 100644 index 00000000000..29693081da9 --- /dev/null +++ b/chromium/jingle/glue/thread_wrapper.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_GLUE_THREAD_WRAPPER_H_ +#define JINGLE_GLUE_THREAD_WRAPPER_H_ + +#include <list> +#include <map> + +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "third_party/libjingle/source/talk/base/thread.h" + +namespace jingle_glue { + +// JingleThreadWrapper implements talk_base::Thread interface on top of +// Chromium's SingleThreadTaskRunner interface. Currently only the bare minimum +// that is used by P2P part of libjingle is implemented. There are two ways to +// create this object: +// +// - Call EnsureForCurrentMessageLoop(). This approach works only on threads +// that have MessageLoop In this case JingleThreadWrapper deletes itself +// automatically when MessageLoop is destroyed. +// - Using JingleThreadWrapper() constructor. In this case the creating code +// must pass a valid task runner for the current thread and also delete the +// wrapper later. +class JingleThreadWrapper : public base::MessageLoop::DestructionObserver, + public talk_base::Thread { + public: + // Create JingleThreadWrapper for the current thread if it hasn't been created + // yet. The thread wrapper is destroyed automatically when the current + // MessageLoop is destroyed. + static void EnsureForCurrentMessageLoop(); + + // Returns thread wrapper for the current thread. NULL is returned + // if EnsureForCurrentMessageLoop() has never been called for this + // thread. + static JingleThreadWrapper* current(); + + explicit JingleThreadWrapper( + scoped_refptr<base::SingleThreadTaskRunner> task_runner); + virtual ~JingleThreadWrapper(); + + // Sets whether the thread can be used to send messages + // synchronously to another thread using Send() method. Set to false + // by default to avoid potential jankiness when Send() used on + // renderer thread. It should be set explicitly for threads that + // need to call Send() for other threads. + void set_send_allowed(bool allowed) { send_allowed_ = allowed; } + + // MessageLoop::DestructionObserver implementation. + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + // talk_base::MessageQueue overrides. + virtual void Post(talk_base::MessageHandler *phandler, + uint32 id, + talk_base::MessageData *pdata, + bool time_sensitive) OVERRIDE; + virtual void PostDelayed(int delay_ms, + talk_base::MessageHandler* handler, + uint32 id, + talk_base::MessageData* data) OVERRIDE; + virtual void Clear(talk_base::MessageHandler* handler, + uint32 id, + talk_base::MessageList* removed) OVERRIDE; + virtual void Send(talk_base::MessageHandler *handler, + uint32 id, + talk_base::MessageData *data) OVERRIDE; + + // Following methods are not supported.They are overriden just to + // ensure that they are not called (each of them contain NOTREACHED + // in the body). Some of this methods can be implemented if it + // becomes neccessary to use libjingle code that calls them. + virtual void Quit() OVERRIDE; + virtual bool IsQuitting() OVERRIDE; + virtual void Restart() OVERRIDE; + virtual bool Get(talk_base::Message* message, + int delay_ms, + bool process_io) OVERRIDE; + virtual bool Peek(talk_base::Message* message, + int delay_ms) OVERRIDE; + virtual void PostAt(uint32 timestamp, + talk_base::MessageHandler* handler, + uint32 id, + talk_base::MessageData* data) OVERRIDE; + virtual void Dispatch(talk_base::Message* message) OVERRIDE; + virtual void ReceiveSends() OVERRIDE; + virtual int GetDelay() OVERRIDE; + + // talk_base::Thread overrides. + virtual void Stop() OVERRIDE; + virtual void Run() OVERRIDE; + + private: + typedef std::map<int, talk_base::Message> MessagesQueue; + struct PendingSend; + + void PostTaskInternal( + int delay_ms, talk_base::MessageHandler* handler, + uint32 message_id, talk_base::MessageData* data); + void RunTask(int task_id); + void ProcessPendingSends(); + + // Task runner used to execute messages posted on this thread. + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + bool send_allowed_; + + // |lock_| must be locked when accessing |messages_|. + base::Lock lock_; + int last_task_id_; + MessagesQueue messages_; + std::list<PendingSend*> pending_send_messages_; + base::WaitableEvent pending_send_event_; + + base::WeakPtrFactory<JingleThreadWrapper> weak_ptr_factory_; + base::WeakPtr<JingleThreadWrapper> weak_ptr_; + + DISALLOW_COPY_AND_ASSIGN(JingleThreadWrapper); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_THREAD_WRAPPER_H_ diff --git a/chromium/jingle/glue/thread_wrapper_unittest.cc b/chromium/jingle/glue/thread_wrapper_unittest.cc new file mode 100644 index 00000000000..8230297dbcf --- /dev/null +++ b/chromium/jingle/glue/thread_wrapper_unittest.cc @@ -0,0 +1,308 @@ +// Copyright (c) 2012 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 "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "jingle/glue/thread_wrapper.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::InvokeWithoutArgs; +using ::testing::Mock; + +namespace jingle_glue { + +static const uint32 kTestMessage1 = 1; +static const uint32 kTestMessage2 = 2; + +static const int kTestDelayMs1 = 10; +static const int kTestDelayMs2 = 20; +static const int kTestDelayMs3 = 30; +static const int kTestDelayMs4 = 40; +static const int kMaxTestDelay = 40; + +namespace { + +class MockMessageHandler : public talk_base::MessageHandler { + public: + MOCK_METHOD1(OnMessage, void(talk_base::Message* msg)); +}; + +MATCHER_P3(MatchMessage, handler, message_id, data, "") { + return arg->phandler == handler && + arg->message_id == message_id && + arg->pdata == data; +} + +ACTION(DeleteMessageData) { + delete arg0->pdata; +} + +// Helper class used in the Dispose test. +class DeletableObject { + public: + DeletableObject(bool* deleted) + : deleted_(deleted) { + *deleted = false; + } + + ~DeletableObject() { + *deleted_ = true; + } + + private: + bool* deleted_; +}; + +} // namespace + +class ThreadWrapperTest : public testing::Test { + public: + // This method is used by the SendDuringSend test. It sends message to the + // main thread synchronously using Send(). + void PingMainThread() { + talk_base::MessageData* data = new talk_base::MessageData(); + MockMessageHandler handler; + + EXPECT_CALL(handler, OnMessage( + MatchMessage(&handler, kTestMessage2, data))) + .WillOnce(DeleteMessageData()); + thread_->Send(&handler, kTestMessage2, data); + } + + protected: + ThreadWrapperTest() + : thread_(NULL) { + } + + virtual void SetUp() OVERRIDE { + JingleThreadWrapper::EnsureForCurrentMessageLoop(); + thread_ = talk_base::Thread::Current(); + } + + // ThreadWrapper destroyes itself when |message_loop_| is destroyed. + base::MessageLoop message_loop_; + talk_base::Thread* thread_; + MockMessageHandler handler1_; + MockMessageHandler handler2_; +}; + +TEST_F(ThreadWrapperTest, Post) { + talk_base::MessageData* data1 = new talk_base::MessageData(); + talk_base::MessageData* data2 = new talk_base::MessageData(); + talk_base::MessageData* data3 = new talk_base::MessageData(); + talk_base::MessageData* data4 = new talk_base::MessageData(); + + thread_->Post(&handler1_, kTestMessage1, data1); + thread_->Post(&handler1_, kTestMessage2, data2); + thread_->Post(&handler2_, kTestMessage1, data3); + thread_->Post(&handler2_, kTestMessage1, data4); + + InSequence in_seq; + + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, data1))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage2, data2))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, data3))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, data4))) + .WillOnce(DeleteMessageData()); + + message_loop_.RunUntilIdle(); +} + +TEST_F(ThreadWrapperTest, PostDelayed) { + talk_base::MessageData* data1 = new talk_base::MessageData(); + talk_base::MessageData* data2 = new talk_base::MessageData(); + talk_base::MessageData* data3 = new talk_base::MessageData(); + talk_base::MessageData* data4 = new talk_base::MessageData(); + + thread_->PostDelayed(kTestDelayMs1, &handler1_, kTestMessage1, data1); + thread_->PostDelayed(kTestDelayMs2, &handler1_, kTestMessage2, data2); + thread_->PostDelayed(kTestDelayMs3, &handler2_, kTestMessage1, data3); + thread_->PostDelayed(kTestDelayMs4, &handler2_, kTestMessage1, data4); + + InSequence in_seq; + + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, data1))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage2, data2))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, data3))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, data4))) + .WillOnce(DeleteMessageData()); + + message_loop_.PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(kMaxTestDelay)); + message_loop_.Run(); +} + +TEST_F(ThreadWrapperTest, Clear) { + thread_->Post(&handler1_, kTestMessage1, NULL); + thread_->Post(&handler1_, kTestMessage2, NULL); + thread_->Post(&handler2_, kTestMessage1, NULL); + thread_->Post(&handler2_, kTestMessage2, NULL); + + thread_->Clear(&handler1_, kTestMessage2); + + InSequence in_seq; + + talk_base::MessageData* null_data = NULL; + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, null_data))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, null_data))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage2, null_data))) + .WillOnce(DeleteMessageData()); + + message_loop_.RunUntilIdle(); +} + +TEST_F(ThreadWrapperTest, ClearDelayed) { + thread_->PostDelayed(kTestDelayMs1, &handler1_, kTestMessage1, NULL); + thread_->PostDelayed(kTestDelayMs2, &handler1_, kTestMessage2, NULL); + thread_->PostDelayed(kTestDelayMs3, &handler2_, kTestMessage1, NULL); + thread_->PostDelayed(kTestDelayMs4, &handler2_, kTestMessage1, NULL); + + thread_->Clear(&handler1_, kTestMessage2); + + InSequence in_seq; + + talk_base::MessageData* null_data = NULL; + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, null_data))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, null_data))) + .WillOnce(DeleteMessageData()); + EXPECT_CALL(handler2_, OnMessage( + MatchMessage(&handler2_, kTestMessage1, null_data))) + .WillOnce(DeleteMessageData()); + + message_loop_.PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(kMaxTestDelay)); + message_loop_.Run(); +} + +// Verify that the queue is cleared when a handler is destroyed. +TEST_F(ThreadWrapperTest, ClearDestoroyed) { + MockMessageHandler* handler_ptr; + { + MockMessageHandler handler; + handler_ptr = &handler; + thread_->Post(&handler, kTestMessage1, NULL); + } + talk_base::MessageList removed; + thread_->Clear(handler_ptr, talk_base::MQID_ANY, &removed); + DCHECK_EQ(0U, removed.size()); +} + +// Verify that Send() calls handler synchronously when called on the +// same thread. +TEST_F(ThreadWrapperTest, SendSameThread) { + talk_base::MessageData* data = new talk_base::MessageData(); + + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, data))) + .WillOnce(DeleteMessageData()); + thread_->Send(&handler1_, kTestMessage1, data); +} + +void InitializeWrapperForNewThread(talk_base::Thread** thread, + base::WaitableEvent* done_event) { + JingleThreadWrapper::EnsureForCurrentMessageLoop(); + JingleThreadWrapper::current()->set_send_allowed(true); + *thread = JingleThreadWrapper::current(); + done_event->Signal(); +} + +// Verify that Send() calls handler synchronously when called for a +// different thread. +TEST_F(ThreadWrapperTest, SendToOtherThread) { + JingleThreadWrapper::current()->set_send_allowed(true); + + base::Thread second_thread("JingleThreadWrapperTest"); + second_thread.Start(); + + base::WaitableEvent initialized_event(true, false); + talk_base::Thread* target; + second_thread.message_loop()->PostTask( + FROM_HERE, base::Bind(&InitializeWrapperForNewThread, + &target, &initialized_event)); + initialized_event.Wait(); + + ASSERT_TRUE(target != NULL); + + talk_base::MessageData* data = new talk_base::MessageData(); + + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, data))) + .WillOnce(DeleteMessageData()); + target->Send(&handler1_, kTestMessage1, data); + + Mock::VerifyAndClearExpectations(&handler1_); +} + +// Verify that thread handles Send() while another Send() is +// pending. The test creates second thread and Send()s kTestMessage1 +// to that thread. kTestMessage1 handler calls PingMainThread() which +// tries to Send() kTestMessage2 to the main thread. +TEST_F(ThreadWrapperTest, SendDuringSend) { + JingleThreadWrapper::current()->set_send_allowed(true); + + base::Thread second_thread("JingleThreadWrapperTest"); + second_thread.Start(); + + base::WaitableEvent initialized_event(true, false); + talk_base::Thread* target; + second_thread.message_loop()->PostTask( + FROM_HERE, base::Bind(&InitializeWrapperForNewThread, + &target, &initialized_event)); + initialized_event.Wait(); + + ASSERT_TRUE(target != NULL); + + talk_base::MessageData* data = new talk_base::MessageData(); + + EXPECT_CALL(handler1_, OnMessage( + MatchMessage(&handler1_, kTestMessage1, data))) + .WillOnce(DoAll( + InvokeWithoutArgs( + this, &ThreadWrapperTest::PingMainThread), + DeleteMessageData())); + target->Send(&handler1_, kTestMessage1, data); + + Mock::VerifyAndClearExpectations(&handler1_); +} + +TEST_F(ThreadWrapperTest, Dispose) { + bool deleted_; + thread_->Dispose(new DeletableObject(&deleted_)); + EXPECT_FALSE(deleted_); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(deleted_); +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/utils.cc b/chromium/jingle/glue/utils.cc new file mode 100644 index 00000000000..bc548ea8112 --- /dev/null +++ b/chromium/jingle/glue/utils.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2012 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/glue/utils.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_util.h" +#include "third_party/libjingle/source/talk/base/byteorder.h" +#include "third_party/libjingle/source/talk/base/socketaddress.h" +#include "third_party/libjingle/source/talk/p2p/base/candidate.h" + +namespace jingle_glue { + +bool IPEndPointToSocketAddress(const net::IPEndPoint& ip_endpoint, + talk_base::SocketAddress* address) { + sockaddr_storage addr; + socklen_t len = sizeof(addr); + return ip_endpoint.ToSockAddr(reinterpret_cast<sockaddr*>(&addr), &len) && + talk_base::SocketAddressFromSockAddrStorage(addr, address); +} + +bool SocketAddressToIPEndPoint(const talk_base::SocketAddress& address, + net::IPEndPoint* ip_endpoint) { + sockaddr_storage addr; + int size = address.ToSockAddrStorage(&addr); + return (size > 0) && + ip_endpoint->FromSockAddr(reinterpret_cast<sockaddr*>(&addr), size); +} + +std::string SerializeP2PCandidate(const cricket::Candidate& candidate) { + // TODO(sergeyu): Use SDP to format candidates? + base::DictionaryValue value; + value.SetString("ip", candidate.address().ipaddr().ToString()); + value.SetInteger("port", candidate.address().port()); + value.SetString("type", candidate.type()); + value.SetString("protocol", candidate.protocol()); + value.SetString("username", candidate.username()); + value.SetString("password", candidate.password()); + value.SetDouble("preference", candidate.preference()); + value.SetInteger("generation", candidate.generation()); + + std::string result; + base::JSONWriter::Write(&value, &result); + return result; +} + +bool DeserializeP2PCandidate(const std::string& candidate_str, + cricket::Candidate* candidate) { + scoped_ptr<base::Value> value( + base::JSONReader::Read(candidate_str, base::JSON_ALLOW_TRAILING_COMMAS)); + if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) { + return false; + } + + base::DictionaryValue* dic_value = + static_cast<base::DictionaryValue*>(value.get()); + + std::string ip; + int port; + std::string type; + std::string protocol; + std::string username; + std::string password; + double preference; + int generation; + + if (!dic_value->GetString("ip", &ip) || + !dic_value->GetInteger("port", &port) || + !dic_value->GetString("type", &type) || + !dic_value->GetString("protocol", &protocol) || + !dic_value->GetString("username", &username) || + !dic_value->GetString("password", &password) || + !dic_value->GetDouble("preference", &preference) || + !dic_value->GetInteger("generation", &generation)) { + return false; + } + + candidate->set_address(talk_base::SocketAddress(ip, port)); + candidate->set_type(type); + candidate->set_protocol(protocol); + candidate->set_username(username); + candidate->set_password(password); + candidate->set_preference(static_cast<float>(preference)); + candidate->set_generation(generation); + + return true; +} + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/utils.h b/chromium/jingle/glue/utils.h new file mode 100644 index 00000000000..a655f71a2fc --- /dev/null +++ b/chromium/jingle/glue/utils.h @@ -0,0 +1,39 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_GLUE_UTILS_H_ +#define JINGLE_GLUE_UTILS_H_ + +#include <string> + +namespace net { +class IPEndPoint; +} // namespace net + +namespace talk_base { +class SocketAddress; +} // namespace talk_base + +namespace cricket { +class Candidate; +} // namespace cricket + +namespace jingle_glue { + +// Chromium and libjingle represent socket addresses differently. The +// following two functions are used to convert addresses from one +// representation to another. +bool IPEndPointToSocketAddress(const net::IPEndPoint& ip_endpoint, + talk_base::SocketAddress* address); +bool SocketAddressToIPEndPoint(const talk_base::SocketAddress& address, + net::IPEndPoint* ip_endpoint); + +// Helper functions to serialize and deserialize P2P candidates. +std::string SerializeP2PCandidate(const cricket::Candidate& candidate); +bool DeserializeP2PCandidate(const std::string& address, + cricket::Candidate* candidate); + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_UTILS_H_ diff --git a/chromium/jingle/glue/xmpp_client_socket_factory.cc b/chromium/jingle/glue/xmpp_client_socket_factory.cc new file mode 100644 index 00000000000..4823ee5d9bd --- /dev/null +++ b/chromium/jingle/glue/xmpp_client_socket_factory.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2012 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/glue/xmpp_client_socket_factory.h" + +#include "base/logging.h" +#include "jingle/glue/fake_ssl_client_socket.h" +#include "jingle/glue/proxy_resolving_client_socket.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/ssl_client_socket.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +namespace jingle_glue { + +XmppClientSocketFactory::XmppClientSocketFactory( + net::ClientSocketFactory* client_socket_factory, + const net::SSLConfig& ssl_config, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, + bool use_fake_ssl_client_socket) + : client_socket_factory_(client_socket_factory), + request_context_getter_(request_context_getter), + ssl_config_(ssl_config), + use_fake_ssl_client_socket_(use_fake_ssl_client_socket) { + CHECK(client_socket_factory_); +} + +XmppClientSocketFactory::~XmppClientSocketFactory() {} + +scoped_ptr<net::StreamSocket> +XmppClientSocketFactory::CreateTransportClientSocket( + const net::HostPortPair& host_and_port) { + // TODO(akalin): Use socket pools. + scoped_ptr<net::StreamSocket> transport_socket( + new ProxyResolvingClientSocket( + NULL, + request_context_getter_, + ssl_config_, + host_and_port)); + return (use_fake_ssl_client_socket_ ? + scoped_ptr<net::StreamSocket>( + new FakeSSLClientSocket(transport_socket.Pass())) : + transport_socket.Pass()); +} + +scoped_ptr<net::SSLClientSocket> +XmppClientSocketFactory::CreateSSLClientSocket( + scoped_ptr<net::ClientSocketHandle> transport_socket, + const net::HostPortPair& host_and_port) { + net::SSLClientSocketContext context; + context.cert_verifier = + request_context_getter_->GetURLRequestContext()->cert_verifier(); + context.transport_security_state = request_context_getter_-> + GetURLRequestContext()->transport_security_state(); + DCHECK(context.transport_security_state); + // TODO(rkn): context.server_bound_cert_service is NULL because the + // ServerBoundCertService class is not thread safe. + return client_socket_factory_->CreateSSLClientSocket( + transport_socket.Pass(), host_and_port, ssl_config_, context); +} + + +} // namespace jingle_glue diff --git a/chromium/jingle/glue/xmpp_client_socket_factory.h b/chromium/jingle/glue/xmpp_client_socket_factory.h new file mode 100644 index 00000000000..4204c1982e8 --- /dev/null +++ b/chromium/jingle/glue/xmpp_client_socket_factory.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_GLUE_XMPP_CLIENT_SOCKET_FACTORY_H_ +#define JINGLE_GLUE_XMPP_CLIENT_SOCKET_FACTORY_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "jingle/glue/resolving_client_socket_factory.h" +#include "net/ssl/ssl_config_service.h" + +namespace net { +class ClientSocketFactory; +class ClientSocketHandle; +class HostPortPair; +class SSLClientSocket; +class StreamSocket; +class URLRequestContextGetter; +} // namespace net + +namespace jingle_glue { + +class XmppClientSocketFactory : public ResolvingClientSocketFactory { + public: + // Does not take ownership of |client_socket_factory|. + XmppClientSocketFactory( + net::ClientSocketFactory* client_socket_factory, + const net::SSLConfig& ssl_config, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, + bool use_fake_ssl_client_socket); + + virtual ~XmppClientSocketFactory(); + + // ResolvingClientSocketFactory implementation. + virtual scoped_ptr<net::StreamSocket> CreateTransportClientSocket( + const net::HostPortPair& host_and_port) OVERRIDE; + + virtual scoped_ptr<net::SSLClientSocket> CreateSSLClientSocket( + scoped_ptr<net::ClientSocketHandle> transport_socket, + const net::HostPortPair& host_and_port) OVERRIDE; + + private: + net::ClientSocketFactory* const client_socket_factory_; + scoped_refptr<net::URLRequestContextGetter> request_context_getter_; + const net::SSLConfig ssl_config_; + const bool use_fake_ssl_client_socket_; + + DISALLOW_COPY_AND_ASSIGN(XmppClientSocketFactory); +}; + +} // namespace jingle_glue + +#endif // JINGLE_GLUE_XMPP_CLIENT_SOCKET_FACTORY_H_ diff --git a/chromium/jingle/jingle.gyp b/chromium/jingle/jingle.gyp new file mode 100644 index 00000000000..ecfd30e2778 --- /dev/null +++ b/chromium/jingle/jingle.gyp @@ -0,0 +1,249 @@ +# Copyright (c) 2012 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. + +{ + 'variables': { + 'chromium_code': 1, + }, # variables + 'conditions': [ + ['enable_webrtc==1 or OS!="android"', { + 'targets': [ + # A library of various utils for integration with libjingle. + { + 'target_name': 'jingle_glue', + 'type': 'static_library', + 'sources': [ + 'glue/channel_socket_adapter.cc', + 'glue/channel_socket_adapter.h', + 'glue/chrome_async_socket.cc', + 'glue/chrome_async_socket.h', + 'glue/fake_ssl_client_socket.cc', + 'glue/fake_ssl_client_socket.h', + 'glue/proxy_resolving_client_socket.cc', + 'glue/proxy_resolving_client_socket.h', + 'glue/pseudotcp_adapter.cc', + 'glue/pseudotcp_adapter.h', + 'glue/resolving_client_socket_factory.h', + 'glue/task_pump.cc', + 'glue/task_pump.h', + 'glue/thread_wrapper.cc', + 'glue/thread_wrapper.h', + 'glue/utils.cc', + 'glue/utils.h', + 'glue/xmpp_client_socket_factory.cc', + 'glue/xmpp_client_socket_factory.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../net/net.gyp:net', + '../third_party/libjingle/libjingle.gyp:libjingle', + ], + 'export_dependent_settings': [ + '../third_party/libjingle/libjingle.gyp:libjingle', + ], + }, + # A library for sending and receiving peer-issued notifications. + # + # TODO(akalin): Separate out the XMPP stuff from this library into + # its own library. + { + 'target_name': 'notifier', + 'type': 'static_library', + 'sources': [ + 'notifier/base/const_communicator.h', + 'notifier/base/gaia_constants.cc', + 'notifier/base/gaia_constants.h', + 'notifier/base/gaia_token_pre_xmpp_auth.cc', + 'notifier/base/gaia_token_pre_xmpp_auth.h', + 'notifier/base/notification_method.h', + 'notifier/base/notification_method.cc', + 'notifier/base/notifier_options.cc', + 'notifier/base/notifier_options.h', + 'notifier/base/notifier_options_util.cc', + 'notifier/base/notifier_options_util.h', + 'notifier/base/server_information.cc', + 'notifier/base/server_information.h', + 'notifier/base/weak_xmpp_client.cc', + 'notifier/base/weak_xmpp_client.h', + 'notifier/base/xmpp_connection.cc', + 'notifier/base/xmpp_connection.h', + 'notifier/communicator/connection_settings.cc', + 'notifier/communicator/connection_settings.h', + 'notifier/communicator/login.cc', + 'notifier/communicator/login.h', + 'notifier/communicator/login_settings.cc', + 'notifier/communicator/login_settings.h', + 'notifier/communicator/single_login_attempt.cc', + 'notifier/communicator/single_login_attempt.h', + 'notifier/listener/non_blocking_push_client.cc', + 'notifier/listener/non_blocking_push_client.h', + 'notifier/listener/notification_constants.cc', + 'notifier/listener/notification_constants.h', + 'notifier/listener/notification_defines.cc', + 'notifier/listener/notification_defines.h', + 'notifier/listener/push_client_observer.cc', + 'notifier/listener/push_client_observer.h', + 'notifier/listener/push_client.cc', + 'notifier/listener/push_client.h', + 'notifier/listener/push_notifications_listen_task.cc', + 'notifier/listener/push_notifications_listen_task.h', + 'notifier/listener/push_notifications_send_update_task.cc', + 'notifier/listener/push_notifications_send_update_task.h', + 'notifier/listener/push_notifications_subscribe_task.cc', + 'notifier/listener/push_notifications_subscribe_task.h', + 'notifier/listener/send_ping_task.cc', + 'notifier/listener/send_ping_task.h', + 'notifier/listener/xml_element_util.cc', + 'notifier/listener/xml_element_util.h', + 'notifier/listener/xmpp_push_client.cc', + 'notifier/listener/xmpp_push_client.h', + ], + 'defines' : [ + '_CRT_SECURE_NO_WARNINGS', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../net/net.gyp:net', + '../third_party/expat/expat.gyp:expat', + '../third_party/libjingle/libjingle.gyp:libjingle', + '../url/url.gyp:url_lib', + 'jingle_glue', + ], + 'export_dependent_settings': [ + '../third_party/libjingle/libjingle.gyp:libjingle', + ], + 'conditions': [ + ['toolkit_uses_gtk == 1', { + 'dependencies': [ + '../build/linux/system.gyp:gtk' + ], + }], + ], + }, + { + 'target_name': 'notifier_test_util', + 'type': 'static_library', + 'sources': [ + 'notifier/base/fake_base_task.cc', + 'notifier/base/fake_base_task.h', + 'notifier/listener/fake_push_client.cc', + 'notifier/listener/fake_push_client.h', + 'notifier/listener/fake_push_client_observer.cc', + 'notifier/listener/fake_push_client_observer.h', + ], + 'dependencies': [ + 'notifier', + '../base/base.gyp:base', + '../testing/gmock.gyp:gmock', + ], + }, + { + 'target_name': 'jingle_glue_test_util', + 'type': 'static_library', + 'sources': [ + 'glue/fake_network_manager.cc', + 'glue/fake_network_manager.h', + 'glue/fake_socket_factory.cc', + 'glue/fake_socket_factory.h', + ], + 'dependencies': [ + 'jingle_glue', + '../base/base.gyp:base', + ], + }, + { + 'target_name': 'jingle_unittests', + 'type': 'executable', + 'sources': [ + 'glue/channel_socket_adapter_unittest.cc', + 'glue/chrome_async_socket_unittest.cc', + 'glue/fake_ssl_client_socket_unittest.cc', + 'glue/jingle_glue_mock_objects.cc', + 'glue/jingle_glue_mock_objects.h', + 'glue/logging_unittest.cc', + 'glue/mock_task.cc', + 'glue/mock_task.h', + 'glue/proxy_resolving_client_socket_unittest.cc', + 'glue/pseudotcp_adapter_unittest.cc', + 'glue/task_pump_unittest.cc', + 'glue/thread_wrapper_unittest.cc', + 'notifier/base/weak_xmpp_client_unittest.cc', + 'notifier/base/xmpp_connection_unittest.cc', + 'notifier/communicator/connection_settings_unittest.cc', + 'notifier/communicator/login_settings_unittest.cc', + 'notifier/communicator/single_login_attempt_unittest.cc', + 'notifier/listener/non_blocking_push_client_unittest.cc', + 'notifier/listener/notification_defines_unittest.cc', + 'notifier/listener/push_client_unittest.cc', + 'notifier/listener/push_notifications_send_update_task_unittest.cc', + 'notifier/listener/push_notifications_subscribe_task_unittest.cc', + 'notifier/listener/send_ping_task_unittest.cc', + 'notifier/listener/xml_element_util_unittest.cc', + 'notifier/listener/xmpp_push_client_unittest.cc', + 'run_all_unittests.cc', + ], + 'conditions': [ + ['OS=="android"', { + 'sources!': [ + # TODO(jrg): + # EXPECT_DEBUG_DEATH() uses features not enabled. + # Should we -std=c++0x or -std=gnu++0x? + 'glue/chrome_async_socket_unittest.cc', + 'notifier/base/xmpp_connection_unittest.cc', + ], + }]], + 'include_dirs': [ + '..', + ], + 'dependencies': [ + 'jingle_glue', + 'jingle_glue_test_util', + 'notifier', + 'notifier_test_util', + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../net/net.gyp:net', + '../net/net.gyp:net_test_support', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + '../third_party/libjingle/libjingle.gyp:libjingle', + ], + }, + ], + }, { # enable_webrtc!=1 and OS=="android" + 'targets': [ + # Stub targets as Android doesn't use libjingle when webrtc is disabled. + { + 'target_name': 'jingle_glue', + 'type': 'none', + }, + { + 'target_name': 'jingle_glue_test_util', + 'type': 'none', + }, + { + 'target_name': 'notifier', + 'type': 'static_library', + 'sources': [ + 'notifier/base/gaia_constants.cc', + 'notifier/base/gaia_constants.h', + 'notifier/base/notification_method.h', + 'notifier/base/notification_method.cc', + 'notifier/base/notifier_options.cc', + 'notifier/base/notifier_options.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../net/net.gyp:net', + ], + }, + { + 'target_name': 'notifier_test_util', + 'type': 'none', + }, + ], + }], + ], +} diff --git a/chromium/jingle/notifier/DEPS b/chromium/jingle/notifier/DEPS new file mode 100644 index 00000000000..2d7d13faad4 --- /dev/null +++ b/chromium/jingle/notifier/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + # notifier depends on libjingle. + "+talk/base", + "+talk/xmpp", + "+talk/xmllite", +] diff --git a/chromium/jingle/notifier/base/const_communicator.h b/chromium/jingle/notifier/base/const_communicator.h new file mode 100644 index 00000000000..05f5f70ca22 --- /dev/null +++ b/chromium/jingle/notifier/base/const_communicator.h @@ -0,0 +1,13 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_NOTIFIER_BASE_CONST_BASE_H_ +#define JINGLE_NOTIFIER_BASE_CONST_BASE_H_ + +namespace notifier { +// The default port for jabber/xmpp communications. +const int kDefaultXmppPort = 5222; +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_CONST_BASE_H_ diff --git a/chromium/jingle/notifier/base/fake_base_task.cc b/chromium/jingle/notifier/base/fake_base_task.cc new file mode 100644 index 00000000000..f3d64cab4d0 --- /dev/null +++ b/chromium/jingle/notifier/base/fake_base_task.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2012 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 "base/compiler_specific.h" +#include "jingle/notifier/base/fake_base_task.h" +#include "jingle/notifier/base/weak_xmpp_client.h" +#include "talk/xmpp/asyncsocket.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace notifier { + +using ::testing::_; +using ::testing::Return; + +class MockAsyncSocket : public buzz::AsyncSocket { + public: + virtual ~MockAsyncSocket() {} + + MOCK_METHOD0(state, State()); + MOCK_METHOD0(error, Error()); + MOCK_METHOD0(GetError, int()); + MOCK_METHOD1(Connect, bool(const talk_base::SocketAddress&)); + MOCK_METHOD3(Read, bool(char*, size_t, size_t*)); + MOCK_METHOD2(Write, bool(const char*, size_t)); + MOCK_METHOD0(Close, bool()); + MOCK_METHOD1(StartTls, bool(const std::string&)); +}; + +} // namespace notifier + +namespace { + +// Extends WeakXmppClient to make jid() return a full jid, as required by +// PushNotificationsSubscribeTask. +class FakeWeakXmppClient : public notifier::WeakXmppClient { + public: + explicit FakeWeakXmppClient(talk_base::TaskParent* parent) + : notifier::WeakXmppClient(parent), + jid_("test@example.com/testresource") {} + + virtual ~FakeWeakXmppClient() {} + + virtual const buzz::Jid& jid() const OVERRIDE { + return jid_; + } + + private: + buzz::Jid jid_; +}; + +} // namespace + +namespace notifier { + +FakeBaseTask::FakeBaseTask() { + // Owned by |task_pump_|. + FakeWeakXmppClient* weak_xmpp_client = + new FakeWeakXmppClient(&task_pump_); + + weak_xmpp_client->Start(); + buzz::XmppClientSettings settings; + // Owned by |weak_xmpp_client|. + MockAsyncSocket* mock_async_socket = new MockAsyncSocket(); + EXPECT_CALL(*mock_async_socket, Connect(_)).WillOnce(Return(true)); + weak_xmpp_client->Connect(settings, "en", mock_async_socket, NULL); + // Initialize the XMPP client. + task_pump_.RunTasks(); + + base_task_ = weak_xmpp_client->AsWeakPtr(); +} + +FakeBaseTask::~FakeBaseTask() {} + +base::WeakPtr<buzz::XmppTaskParentInterface> FakeBaseTask::AsWeakPtr() { + return base_task_; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/fake_base_task.h b/chromium/jingle/notifier/base/fake_base_task.h new file mode 100644 index 00000000000..2cb6d777554 --- /dev/null +++ b/chromium/jingle/notifier/base/fake_base_task.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012 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. +// +// A stand-in for stuff that expects a weak pointer to a BaseTask for +// testing. + +#ifndef JINGLE_NOTIFIER_FAKE_XMPP_CLIENT_H_ +#define JINGLE_NOTIFIER_FAKE_XMPP_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/memory/weak_ptr.h" +#include "jingle/glue/task_pump.h" + +namespace buzz { +class XmppTaskParentInterface; +} // namespace buzz + +namespace notifier { + +class FakeBaseTask { + public: + FakeBaseTask(); + ~FakeBaseTask(); + + base::WeakPtr<buzz::XmppTaskParentInterface> AsWeakPtr(); + + private: + jingle_glue::TaskPump task_pump_; + base::WeakPtr<buzz::XmppTaskParentInterface> base_task_; + + DISALLOW_COPY_AND_ASSIGN(FakeBaseTask); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_FAKE_XMPP_CLIENT_H_ diff --git a/chromium/jingle/notifier/base/gaia_constants.cc b/chromium/jingle/notifier/base/gaia_constants.cc new file mode 100644 index 00000000000..f726843109e --- /dev/null +++ b/chromium/jingle/notifier/base/gaia_constants.cc @@ -0,0 +1,12 @@ +// Copyright (c) 2012 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/gaia_constants.h" + +namespace notifier { + +// By default use a Google cookie auth mechanism. +const char kDefaultGaiaAuthMechanism[] = "X-GOOGLE-TOKEN"; + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/gaia_constants.h b/chromium/jingle/notifier/base/gaia_constants.h new file mode 100644 index 00000000000..fe2c993bd2e --- /dev/null +++ b/chromium/jingle/notifier/base/gaia_constants.h @@ -0,0 +1,14 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_BASE_GAIA_CONSTANTS_H_ +#define JINGLE_NOTIFIER_BASE_GAIA_CONSTANTS_H_ + +namespace notifier { + +extern const char kDefaultGaiaAuthMechanism[]; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_GAIA_CONSTANTS_H_ diff --git a/chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.cc b/chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.cc new file mode 100644 index 00000000000..f7ddf8dca7b --- /dev/null +++ b/chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2011 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/gaia_token_pre_xmpp_auth.h" + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "talk/base/socketaddress.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/saslcookiemechanism.h" + +namespace notifier { + +namespace { + +class GaiaCookieMechanism : public buzz::SaslCookieMechanism { + public: + GaiaCookieMechanism(const std::string & mechanism, + const std::string & username, + const std::string & cookie, + const std::string & token_service) + : buzz::SaslCookieMechanism( + mechanism, username, cookie, token_service) {} + + virtual ~GaiaCookieMechanism() {} + + virtual buzz::XmlElement* StartSaslAuth() OVERRIDE { + buzz::XmlElement* auth = buzz::SaslCookieMechanism::StartSaslAuth(); + // These attributes are necessary for working with non-gmail gaia + // accounts. + const std::string NS_GOOGLE_AUTH_PROTOCOL( + "http://www.google.com/talk/protocol/auth"); + const buzz::QName QN_GOOGLE_ALLOW_GENERATED_JID_XMPP_LOGIN( + NS_GOOGLE_AUTH_PROTOCOL, "allow-generated-jid"); + const buzz::QName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT( + NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result"); + auth->SetAttr(QN_GOOGLE_ALLOW_GENERATED_JID_XMPP_LOGIN, "true"); + auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true"); + return auth; + } + + private: + DISALLOW_COPY_AND_ASSIGN(GaiaCookieMechanism); +}; + +} // namespace + +GaiaTokenPreXmppAuth::GaiaTokenPreXmppAuth( + const std::string& username, + const std::string& token, + const std::string& token_service, + const std::string& auth_mechanism) + : username_(username), + token_(token), + token_service_(token_service), + auth_mechanism_(auth_mechanism) { + DCHECK(!auth_mechanism_.empty()); +} + +GaiaTokenPreXmppAuth::~GaiaTokenPreXmppAuth() { } + +void GaiaTokenPreXmppAuth::StartPreXmppAuth( + const buzz::Jid& jid, + const talk_base::SocketAddress& server, + const talk_base::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token) { + SignalAuthDone(); +} + +bool GaiaTokenPreXmppAuth::IsAuthDone() const { + return true; +} + +bool GaiaTokenPreXmppAuth::IsAuthorized() const { + return true; +} + +bool GaiaTokenPreXmppAuth::HadError() const { + return false; +} + +int GaiaTokenPreXmppAuth::GetError() const { + return 0; +} + +buzz::CaptchaChallenge GaiaTokenPreXmppAuth::GetCaptchaChallenge() const { + return buzz::CaptchaChallenge(); +} + +std::string GaiaTokenPreXmppAuth::GetAuthToken() const { + return token_; +} + +std::string GaiaTokenPreXmppAuth::GetAuthMechanism() const { + return auth_mechanism_; +} + +std::string GaiaTokenPreXmppAuth::ChooseBestSaslMechanism( + const std::vector<std::string> & mechanisms, bool encrypted) { + return (std::find(mechanisms.begin(), mechanisms.end(), auth_mechanism_) != + mechanisms.end()) + ? auth_mechanism_ + : std::string(); +} + +buzz::SaslMechanism* GaiaTokenPreXmppAuth::CreateSaslMechanism( + const std::string& mechanism) { + if (mechanism == auth_mechanism_) + return new GaiaCookieMechanism( + mechanism, username_, token_, token_service_); + return NULL; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.h b/chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.h new file mode 100644 index 00000000000..60e0c96ca61 --- /dev/null +++ b/chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.h @@ -0,0 +1,67 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_NOTIFIER_BASE_GAIA_TOKEN_PRE_XMPP_AUTH_H_ +#define JINGLE_NOTIFIER_BASE_GAIA_TOKEN_PRE_XMPP_AUTH_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "talk/xmpp/prexmppauth.h" + +namespace notifier { + +// This class implements buzz::PreXmppAuth interface for token-based +// authentication in GTalk. It looks for the X-GOOGLE-TOKEN auth mechanism +// and uses that instead of the default auth mechanism (PLAIN). +class GaiaTokenPreXmppAuth : public buzz::PreXmppAuth { + public: + GaiaTokenPreXmppAuth(const std::string& username, const std::string& token, + const std::string& token_service, + const std::string& auth_mechanism); + + virtual ~GaiaTokenPreXmppAuth(); + + // buzz::PreXmppAuth (-buzz::SaslHandler) implementation. We stub + // all the methods out as we don't actually do any authentication at + // this point. + virtual void StartPreXmppAuth(const buzz::Jid& jid, + const talk_base::SocketAddress& server, + const talk_base::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token) OVERRIDE; + + virtual bool IsAuthDone() const OVERRIDE; + + virtual bool IsAuthorized() const OVERRIDE; + + virtual bool HadError() const OVERRIDE; + + virtual int GetError() const OVERRIDE; + + virtual buzz::CaptchaChallenge GetCaptchaChallenge() const OVERRIDE; + + virtual std::string GetAuthToken() const OVERRIDE; + + virtual std::string GetAuthMechanism() const OVERRIDE; + + // buzz::SaslHandler implementation. + + virtual std::string ChooseBestSaslMechanism( + const std::vector<std::string>& mechanisms, bool encrypted) OVERRIDE; + + virtual buzz::SaslMechanism* CreateSaslMechanism( + const std::string& mechanism) OVERRIDE; + + private: + std::string username_; + std::string token_; + std::string token_service_; + std::string auth_mechanism_; +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_GAIA_TOKEN_PRE_XMPP_AUTH_H_ diff --git a/chromium/jingle/notifier/base/notification_method.cc b/chromium/jingle/notifier/base/notification_method.cc new file mode 100644 index 00000000000..6095ed01462 --- /dev/null +++ b/chromium/jingle/notifier/base/notification_method.cc @@ -0,0 +1,42 @@ +// 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/notification_method.h" + +#include "base/logging.h" + +namespace notifier { + +const NotificationMethod kDefaultNotificationMethod = NOTIFICATION_SERVER; + +std::string NotificationMethodToString( + NotificationMethod notification_method) { + switch (notification_method) { + case NOTIFICATION_P2P: + return "NOTIFICATION_P2P"; + break; + case NOTIFICATION_SERVER: + return "NOTIFICATION_SERVER"; + break; + default: + LOG(WARNING) << "Unknown value for notification method: " + << notification_method; + break; + } + return "<unknown notification method>"; +} + +NotificationMethod StringToNotificationMethod(const std::string& str) { + if (str == "p2p") { + return NOTIFICATION_P2P; + } else if (str == "server") { + return NOTIFICATION_SERVER; + } + LOG(WARNING) << "Unknown notification method \"" << str + << "\"; using method " + << NotificationMethodToString(kDefaultNotificationMethod); + return kDefaultNotificationMethod; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/notification_method.h b/chromium/jingle/notifier/base/notification_method.h new file mode 100644 index 00000000000..63addabc310 --- /dev/null +++ b/chromium/jingle/notifier/base/notification_method.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_BASE_NOTIFICATION_METHOD_H_ +#define JINGLE_NOTIFIER_BASE_NOTIFICATION_METHOD_H_ + +#include <string> + +namespace notifier { + +enum NotificationMethod { + // Old peer-to-peer notification method. Currently only used for + // testing. + NOTIFICATION_P2P, + // Server-issued notifications. The default. + NOTIFICATION_SERVER, +}; + +extern const NotificationMethod kDefaultNotificationMethod; + +std::string NotificationMethodToString( + NotificationMethod notification_method); + +// If the given string is not one of "p2p" or "server", returns +// kDefaultNotificationMethod. +NotificationMethod StringToNotificationMethod(const std::string& str); + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_NOTIFICATION_METHOD_H_ + diff --git a/chromium/jingle/notifier/base/notifier_options.cc b/chromium/jingle/notifier/base/notifier_options.cc new file mode 100644 index 00000000000..3cf02ed88c9 --- /dev/null +++ b/chromium/jingle/notifier/base/notifier_options.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2011 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/notifier_options.h" + +#include "jingle/notifier/base/gaia_constants.h" + +namespace notifier { + +NotifierOptions::NotifierOptions() + : try_ssltcp_first(false), + allow_insecure_connection(false), + invalidate_xmpp_login(false), + notification_method(kDefaultNotificationMethod), + auth_mechanism(kDefaultGaiaAuthMechanism) {} + +NotifierOptions::~NotifierOptions() { } + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/notifier_options.h b/chromium/jingle/notifier/base/notifier_options.h new file mode 100644 index 00000000000..0d7b471969f --- /dev/null +++ b/chromium/jingle/notifier/base/notifier_options.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 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. + +#ifndef JINGLE_NOTIFIER_BASE_NOTIFIER_OPTIONS_H_ +#define JINGLE_NOTIFIER_BASE_NOTIFIER_OPTIONS_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "jingle/notifier/base/notification_method.h" +#include "net/base/host_port_pair.h" +#include "net/url_request/url_request_context_getter.h" + +namespace notifier { + +struct NotifierOptions { + NotifierOptions(); + ~NotifierOptions(); + + // Indicates that the SSLTCP port (443) is to be tried before the the XMPP + // port (5222) during login. + bool try_ssltcp_first; + + // Indicates that insecure connections (e.g., plain authentication, + // no TLS) are allowed. Only used for testing. + bool allow_insecure_connection; + + // Indicates that the login info passed to XMPP is invalidated so + // that login fails. + bool invalidate_xmpp_login; + + // Contains a custom URL and port for the notification server, if one is to + // be used. Empty otherwise. + net::HostPortPair xmpp_host_port; + + // Indicates the method used by sync clients while sending and listening to + // notifications. + NotificationMethod notification_method; + + // Specifies the auth mechanism to use ("X-GOOGLE-TOKEN", "X-OAUTH2", etc), + std::string auth_mechanism; + + // The URLRequestContextGetter to use for doing I/O. + scoped_refptr<net::URLRequestContextGetter> request_context_getter; +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_NOTIFIER_OPTIONS_H_ diff --git a/chromium/jingle/notifier/base/notifier_options_util.cc b/chromium/jingle/notifier/base/notifier_options_util.cc new file mode 100644 index 00000000000..fa48209ea69 --- /dev/null +++ b/chromium/jingle/notifier/base/notifier_options_util.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2012 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/notifier_options_util.h" + +#include "base/logging.h" +#include "jingle/notifier/base/const_communicator.h" +#include "jingle/notifier/base/notifier_options.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/jid.h" + +namespace notifier { + +buzz::XmppClientSettings MakeXmppClientSettings( + const NotifierOptions& notifier_options, + const std::string& email, const std::string& token) { + buzz::Jid jid = buzz::Jid(email); + DCHECK(!jid.node().empty()); + DCHECK(jid.IsValid()); + + buzz::XmppClientSettings xmpp_client_settings; + xmpp_client_settings.set_user(jid.node()); + xmpp_client_settings.set_resource("chrome-sync"); + xmpp_client_settings.set_host(jid.domain()); + xmpp_client_settings.set_use_tls(buzz::TLS_ENABLED); + xmpp_client_settings.set_auth_token(notifier_options.auth_mechanism, + notifier_options.invalidate_xmpp_login ? + token + "bogus" : token); + if (notifier_options.auth_mechanism == buzz::AUTH_MECHANISM_OAUTH2) + xmpp_client_settings.set_token_service("oauth2"); + else + xmpp_client_settings.set_token_service("chromiumsync"); + if (notifier_options.allow_insecure_connection) { + xmpp_client_settings.set_allow_plain(true); + xmpp_client_settings.set_use_tls(buzz::TLS_DISABLED); + } + return xmpp_client_settings; +} + +ServerList GetServerList( + const NotifierOptions& notifier_options) { + ServerList servers; + // Override the default servers with a test notification server if one was + // provided. + if (!notifier_options.xmpp_host_port.host().empty()) { + servers.push_back( + ServerInformation(notifier_options.xmpp_host_port, + DOES_NOT_SUPPORT_SSLTCP)); + } else { + // The default servers support SSLTCP. + servers.push_back( + ServerInformation( + net::HostPortPair("talk.google.com", + notifier::kDefaultXmppPort), + SUPPORTS_SSLTCP)); + servers.push_back( + ServerInformation( + net::HostPortPair("talkx.l.google.com", + notifier::kDefaultXmppPort), + SUPPORTS_SSLTCP)); + } + return servers; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/notifier_options_util.h b/chromium/jingle/notifier/base/notifier_options_util.h new file mode 100644 index 00000000000..6ba24716a6c --- /dev/null +++ b/chromium/jingle/notifier/base/notifier_options_util.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 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. +// +// Utility functions for NotifierOptions. + +#ifndef JINGLE_NOTIFIER_BASE_NOTIFIER_OPTIONS_UTIL_H_ +#define JINGLE_NOTIFIER_BASE_NOTIFIER_OPTIONS_UTIL_H_ + +#include <string> +#include <vector> + +#include "jingle/notifier/base/server_information.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +struct NotifierOptions; + +buzz::XmppClientSettings MakeXmppClientSettings( + const NotifierOptions& notifier_options, + const std::string& email, const std::string& token); + +ServerList GetServerList(const NotifierOptions& notifier_options); + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_NOTIFIER_OPTIONS_UTIL_H_ diff --git a/chromium/jingle/notifier/base/server_information.cc b/chromium/jingle/notifier/base/server_information.cc new file mode 100644 index 00000000000..003dacace77 --- /dev/null +++ b/chromium/jingle/notifier/base/server_information.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 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/server_information.h" + +#include "base/logging.h" + +namespace notifier { + +ServerInformation::ServerInformation( + const net::HostPortPair& server, SslTcpSupport ssltcp_support) + : server(server), ssltcp_support(ssltcp_support) { + DCHECK(!server.host().empty()); + DCHECK_GT(server.port(), 0); +} + +ServerInformation::ServerInformation() + : ssltcp_support(DOES_NOT_SUPPORT_SSLTCP) {} + +ServerInformation::~ServerInformation() {} + +bool ServerInformation::Equals(const ServerInformation& other) const { + return + server.Equals(other.server) && + (ssltcp_support == other.ssltcp_support); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/server_information.h b/chromium/jingle/notifier/base/server_information.h new file mode 100644 index 00000000000..c6e3ffdb5b7 --- /dev/null +++ b/chromium/jingle/notifier/base/server_information.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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. +// +// A utility struct for storing the information for an XMPP server. + +#ifndef JINGLE_NOTIFIER_BASE_SERVER_INFORMATION_H_ +#define JINGLE_NOTIFIER_BASE_SERVER_INFORMATION_H_ + +#include <vector> + +#include "net/base/host_port_pair.h" + +namespace notifier { + +enum SslTcpSupport { DOES_NOT_SUPPORT_SSLTCP, SUPPORTS_SSLTCP }; + +struct ServerInformation { + ServerInformation(const net::HostPortPair& server, + SslTcpSupport ssltcp_support); + ServerInformation(); + ~ServerInformation(); + + bool Equals(const ServerInformation& other) const; + + net::HostPortPair server; + SslTcpSupport ssltcp_support; +}; + +typedef std::vector<ServerInformation> ServerList; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_SERVER_INFORMATION_H_ diff --git a/chromium/jingle/notifier/base/weak_xmpp_client.cc b/chromium/jingle/notifier/base/weak_xmpp_client.cc new file mode 100644 index 00000000000..fa89e46d486 --- /dev/null +++ b/chromium/jingle/notifier/base/weak_xmpp_client.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2012 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/weak_xmpp_client.h" + +#include "base/compiler_specific.h" + +namespace notifier { + +WeakXmppClient::WeakXmppClient(talk_base::TaskParent* parent) + : buzz::XmppClient(parent), + weak_ptr_factory_(this) {} + +WeakXmppClient::~WeakXmppClient() { + DCHECK(CalledOnValidThread()); + Invalidate(); +} + +void WeakXmppClient::Invalidate() { + DCHECK(CalledOnValidThread()); + // We don't want XmppClient raising any signals once its invalidated. + SignalStateChange.disconnect_all(); + SignalLogInput.disconnect_all(); + SignalLogOutput.disconnect_all(); + weak_ptr_factory_.InvalidateWeakPtrs(); +} + +base::WeakPtr<WeakXmppClient> WeakXmppClient::AsWeakPtr() { + DCHECK(CalledOnValidThread()); + return weak_ptr_factory_.GetWeakPtr(); +} + +void WeakXmppClient::Stop() { + DCHECK(CalledOnValidThread()); + // We don't want XmppClient used after it has been stopped. + Invalidate(); + buzz::XmppClient::Stop(); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/weak_xmpp_client.h b/chromium/jingle/notifier/base/weak_xmpp_client.h new file mode 100644 index 00000000000..d56e04514ef --- /dev/null +++ b/chromium/jingle/notifier/base/weak_xmpp_client.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 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. +// +// A thin wrapper around buzz::XmppClient that exposes weak pointers +// so that users know when the buzz::XmppClient becomes invalid to use +// (not necessarily only at destruction time). + +#ifndef JINGLE_NOTIFIER_BASE_WEAK_XMPP_CLIENT_H_ +#define JINGLE_NOTIFIER_BASE_WEAK_XMPP_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "talk/xmpp/xmppclient.h" + +namespace talk_base { +class TaskParent; +} // namespace + +namespace notifier { + +// buzz::XmppClient's destructor isn't marked virtual, but it inherits +// from talk_base::Task, whose destructor *is* marked virtual, so we +// can safely inherit from it. +class WeakXmppClient : public buzz::XmppClient, public base::NonThreadSafe { + public: + explicit WeakXmppClient(talk_base::TaskParent* parent); + + virtual ~WeakXmppClient(); + + // Returns a weak pointer that is invalidated when the XmppClient + // becomes invalid to use. + base::WeakPtr<WeakXmppClient> AsWeakPtr(); + + // Invalidates all weak pointers to this object. (This method is + // necessary as calling Abort() does not always lead to Stop() being + // called, so it's not a reliable way to cause an invalidation.) + void Invalidate(); + + protected: + virtual void Stop() OVERRIDE; + + private: + // We use our own WeakPtrFactory instead of inheriting from + // SupportsWeakPtr since we want to invalidate in other places + // besides the destructor. + base::WeakPtrFactory<WeakXmppClient> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(WeakXmppClient); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_WEAK_XMPP_CLIENT_H_ diff --git a/chromium/jingle/notifier/base/weak_xmpp_client_unittest.cc b/chromium/jingle/notifier/base/weak_xmpp_client_unittest.cc new file mode 100644 index 00000000000..c1058e93b4f --- /dev/null +++ b/chromium/jingle/notifier/base/weak_xmpp_client_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2012 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/weak_xmpp_client.h" + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/task_pump.h" +#include "talk/base/sigslot.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +namespace { + +class MockXmppDelegate : public sigslot::has_slots<> { + public: + virtual ~MockXmppDelegate() {} + + MOCK_METHOD1(OnStateChange, void(buzz::XmppEngine::State)); + MOCK_METHOD2(OnInputLog, void(const char*, int)); + MOCK_METHOD2(OnOutputLog, void(const char*, int)); +}; + +const buzz::XmppEngine::State kState = buzz::XmppEngine::STATE_OPEN; +const char kInputLog[] = "input log"; +const char kOutputLog[] = "output log"; + +class WeakXmppClientTest : public testing::Test { + protected: + WeakXmppClientTest() : task_pump_(new jingle_glue::TaskPump()) {} + + virtual ~WeakXmppClientTest() {} + + void ConnectSignals(buzz::XmppClient* xmpp_client) { + xmpp_client->SignalStateChange.connect( + &mock_xmpp_delegate_, &MockXmppDelegate::OnStateChange); + xmpp_client->SignalLogInput.connect( + &mock_xmpp_delegate_, &MockXmppDelegate::OnInputLog); + xmpp_client->SignalLogOutput.connect( + &mock_xmpp_delegate_, &MockXmppDelegate::OnOutputLog); + } + + void ExpectSignalCalls() { + EXPECT_CALL(mock_xmpp_delegate_, OnStateChange(kState)); + EXPECT_CALL(mock_xmpp_delegate_, + OnInputLog(kInputLog, arraysize(kInputLog))); + EXPECT_CALL(mock_xmpp_delegate_, + OnOutputLog(kOutputLog, arraysize(kOutputLog))); + } + + void RaiseSignals(buzz::XmppClient* xmpp_client) { + xmpp_client->SignalStateChange(kState); + xmpp_client->SignalLogInput(kInputLog, arraysize(kInputLog)); + xmpp_client->SignalLogOutput(kOutputLog, arraysize(kOutputLog)); + } + + // Needed by TaskPump. + base::MessageLoop message_loop_; + + scoped_ptr<jingle_glue::TaskPump> task_pump_; + MockXmppDelegate mock_xmpp_delegate_; +}; + +TEST_F(WeakXmppClientTest, InvalidationViaInvalidate) { + ExpectSignalCalls(); + + WeakXmppClient* weak_xmpp_client = new WeakXmppClient(task_pump_.get()); + ConnectSignals(weak_xmpp_client); + + weak_xmpp_client->Start(); + base::WeakPtr<WeakXmppClient> weak_ptr = weak_xmpp_client->AsWeakPtr(); + EXPECT_TRUE(weak_ptr.get()); + RaiseSignals(weak_ptr.get()); + + weak_xmpp_client->Invalidate(); + EXPECT_FALSE(weak_ptr.get()); + // We know that |weak_xmpp_client| is still valid at this point, + // although it should be entirely disconnected. + RaiseSignals(weak_xmpp_client); +} + +TEST_F(WeakXmppClientTest, InvalidationViaStop) { + ExpectSignalCalls(); + + WeakXmppClient* weak_xmpp_client = new WeakXmppClient(task_pump_.get()); + ConnectSignals(weak_xmpp_client); + + weak_xmpp_client->Start(); + base::WeakPtr<WeakXmppClient> weak_ptr = weak_xmpp_client->AsWeakPtr(); + EXPECT_TRUE(weak_ptr.get()); + RaiseSignals(weak_ptr.get()); + + weak_xmpp_client->Abort(); + EXPECT_FALSE(weak_ptr.get()); + // We know that |weak_xmpp_client| is still valid at this point, + // although it should be entirely disconnected. + RaiseSignals(weak_xmpp_client); +} + +TEST_F(WeakXmppClientTest, InvalidationViaDestructor) { + ExpectSignalCalls(); + + WeakXmppClient* weak_xmpp_client = new WeakXmppClient(task_pump_.get()); + ConnectSignals(weak_xmpp_client); + + weak_xmpp_client->Start(); + base::WeakPtr<WeakXmppClient> weak_ptr = weak_xmpp_client->AsWeakPtr(); + EXPECT_TRUE(weak_ptr.get()); + RaiseSignals(weak_ptr.get()); + + task_pump_.reset(); + EXPECT_FALSE(weak_ptr.get()); + // |weak_xmpp_client| is truly invalid at this point so we can't + // RaiseSignals() with it. +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/xmpp_connection.cc b/chromium/jingle/notifier/base/xmpp_connection.cc new file mode 100644 index 00000000000..08a8ee370a2 --- /dev/null +++ b/chromium/jingle/notifier/base/xmpp_connection.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2012 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/xmpp_connection.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_piece.h" +#include "jingle/glue/chrome_async_socket.h" +#include "jingle/glue/task_pump.h" +#include "jingle/glue/xmpp_client_socket_factory.h" +#include "jingle/notifier/base/weak_xmpp_client.h" +#include "net/socket/client_socket_factory.h" +#include "net/ssl/ssl_config_service.h" +#include "net/url_request/url_request_context.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +XmppConnection::Delegate::~Delegate() {} + +namespace { + +buzz::AsyncSocket* CreateSocket( + const buzz::XmppClientSettings& xmpp_client_settings, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) { + bool use_fake_ssl_client_socket = + (xmpp_client_settings.protocol() == cricket::PROTO_SSLTCP); + // The default SSLConfig is good enough for us for now. + const net::SSLConfig ssl_config; + // These numbers were taken from similar numbers in + // XmppSocketAdapter. + const size_t kReadBufSize = 64U * 1024U; + const size_t kWriteBufSize = 64U * 1024U; + jingle_glue::XmppClientSocketFactory* const client_socket_factory = + new jingle_glue::XmppClientSocketFactory( + net::ClientSocketFactory::GetDefaultFactory(), + ssl_config, + request_context_getter, + use_fake_ssl_client_socket); + return new jingle_glue::ChromeAsyncSocket(client_socket_factory, + kReadBufSize, kWriteBufSize); +} + +} // namespace + +XmppConnection::XmppConnection( + const buzz::XmppClientSettings& xmpp_client_settings, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, + Delegate* delegate, + buzz::PreXmppAuth* pre_xmpp_auth) + : task_pump_(new jingle_glue::TaskPump()), + on_connect_called_(false), + delegate_(delegate) { + DCHECK(delegate_); + // Owned by |task_pump_|, but is guaranteed to live at least as long + // as this function. + WeakXmppClient* weak_xmpp_client = new WeakXmppClient(task_pump_.get()); + weak_xmpp_client->SignalStateChange.connect( + this, &XmppConnection::OnStateChange); + weak_xmpp_client->SignalLogInput.connect( + this, &XmppConnection::OnInputLog); + weak_xmpp_client->SignalLogOutput.connect( + this, &XmppConnection::OnOutputLog); + const char kLanguage[] = "en"; + buzz::XmppReturnStatus connect_status = + weak_xmpp_client->Connect(xmpp_client_settings, kLanguage, + CreateSocket(xmpp_client_settings, + request_context_getter), + pre_xmpp_auth); + // buzz::XmppClient::Connect() should never fail. + DCHECK_EQ(connect_status, buzz::XMPP_RETURN_OK); + weak_xmpp_client->Start(); + weak_xmpp_client_ = weak_xmpp_client->AsWeakPtr(); +} + +XmppConnection::~XmppConnection() { + DCHECK(CalledOnValidThread()); + ClearClient(); + task_pump_->Stop(); + base::MessageLoop* current_message_loop = base::MessageLoop::current(); + CHECK(current_message_loop); + // We do this because XmppConnection may get destroyed as a result + // of a signal from XmppClient. If we delete |task_pump_| here, bad + // things happen when the stack pops back up to the XmppClient's + // (which is deleted by |task_pump_|) function. + current_message_loop->DeleteSoon(FROM_HERE, task_pump_.release()); +} + +void XmppConnection::OnStateChange(buzz::XmppEngine::State state) { + DCHECK(CalledOnValidThread()); + VLOG(1) << "XmppClient state changed to " << state; + if (!weak_xmpp_client_.get()) { + LOG(DFATAL) << "weak_xmpp_client_ unexpectedly NULL"; + return; + } + if (!delegate_) { + LOG(DFATAL) << "delegate_ unexpectedly NULL"; + return; + } + switch (state) { + case buzz::XmppEngine::STATE_OPEN: + if (on_connect_called_) { + LOG(DFATAL) << "State changed to STATE_OPEN more than once"; + } else { + delegate_->OnConnect(weak_xmpp_client_); + on_connect_called_ = true; + } + break; + case buzz::XmppEngine::STATE_CLOSED: { + int subcode = 0; + buzz::XmppEngine::Error error = + weak_xmpp_client_->GetError(&subcode); + const buzz::XmlElement* stream_error = + weak_xmpp_client_->GetStreamError(); + ClearClient(); + Delegate* delegate = delegate_; + delegate_ = NULL; + delegate->OnError(error, subcode, stream_error); + break; + } + default: + // Do nothing. + break; + } +} + +void XmppConnection::OnInputLog(const char* data, int len) { + DCHECK(CalledOnValidThread()); + VLOG(2) << "XMPP Input: " << base::StringPiece(data, len); +} + +void XmppConnection::OnOutputLog(const char* data, int len) { + DCHECK(CalledOnValidThread()); + VLOG(2) << "XMPP Output: " << base::StringPiece(data, len); +} + +void XmppConnection::ClearClient() { + if (weak_xmpp_client_.get()) { + weak_xmpp_client_->Invalidate(); + DCHECK(!weak_xmpp_client_.get()); + } +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/base/xmpp_connection.h b/chromium/jingle/notifier/base/xmpp_connection.h new file mode 100644 index 00000000000..b783764a4d1 --- /dev/null +++ b/chromium/jingle/notifier/base/xmpp_connection.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 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. +// +// A class that manages a connection to an XMPP server. + +#ifndef JINGLE_NOTIFIER_BASE_XMPP_CONNECTION_H_ +#define JINGLE_NOTIFIER_BASE_XMPP_CONNECTION_H_ + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "net/url_request/url_request_context_getter.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class PreXmppAuth; +class XmlElement; +class XmppClientSettings; +class XmppTaskParentInterface; +} // namespace + +namespace jingle_glue { +class TaskPump; +} // namespace jingle_glue + +namespace notifier { + +class WeakXmppClient; + +class XmppConnection + : public sigslot::has_slots<>, + public base::NonThreadSafe { + public: + class Delegate { + public: + // Called (at most once) when a connection has been established. + // |base_task| can be used by the client as the parent of any Task + // it creates as long as it is valid (i.e., non-NULL). + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) = 0; + + // Called if an error has occurred (either before or after a call + // to OnConnect()). No calls to the delegate will be made after + // this call. Invalidates any weak pointers passed to the client + // by OnConnect(). + // + // |error| is the code for the raised error. |subcode| is an + // error-dependent subcode (0 if not applicable). |stream_error| + // is non-NULL iff |error| == ERROR_STREAM. |stream_error| is + // valid only for the lifetime of this function. + // + // Ideally, |error| would always be set to something that is not + // ERROR_NONE, but due to inconsistent error-handling this doesn't + // always happen. + virtual void OnError(buzz::XmppEngine::Error error, int subcode, + const buzz::XmlElement* stream_error) = 0; + + protected: + virtual ~Delegate(); + }; + + // Does not take ownership of |delegate|, which must not be + // NULL. Takes ownership of |pre_xmpp_auth|, which may be NULL. + // + // TODO(akalin): Avoid the need for |pre_xmpp_auth|. + XmppConnection(const buzz::XmppClientSettings& xmpp_client_settings, + const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter, + Delegate* delegate, + buzz::PreXmppAuth* pre_xmpp_auth); + + // Invalidates any weak pointers passed to the delegate by + // OnConnect(), but does not trigger a call to the delegate's + // OnError() function. + virtual ~XmppConnection(); + + private: + void OnStateChange(buzz::XmppEngine::State state); + void OnInputLog(const char* data, int len); + void OnOutputLog(const char* data, int len); + + void ClearClient(); + + scoped_ptr<jingle_glue::TaskPump> task_pump_; + base::WeakPtr<WeakXmppClient> weak_xmpp_client_; + bool on_connect_called_; + Delegate* delegate_; + + FRIEND_TEST(XmppConnectionTest, RaisedError); + FRIEND_TEST(XmppConnectionTest, Connect); + FRIEND_TEST(XmppConnectionTest, MultipleConnect); + FRIEND_TEST(XmppConnectionTest, ConnectThenError); + FRIEND_TEST(XmppConnectionTest, TasksDontRunAfterXmppConnectionDestructor); + + DISALLOW_COPY_AND_ASSIGN(XmppConnection); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_XMPP_CONNECTION_H_ diff --git a/chromium/jingle/notifier/base/xmpp_connection_unittest.cc b/chromium/jingle/notifier/base/xmpp_connection_unittest.cc new file mode 100644 index 00000000000..3212abcbb86 --- /dev/null +++ b/chromium/jingle/notifier/base/xmpp_connection_unittest.cc @@ -0,0 +1,253 @@ +// Copyright (c) 2012 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/xmpp_connection.h" + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/mock_task.h" +#include "jingle/glue/task_pump.h" +#include "jingle/notifier/base/weak_xmpp_client.h" +#include "net/cert/cert_verifier.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_test_util.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace buzz { +class CaptchaChallenge; +class Jid; +} // namespace buzz + +namespace talk_base { +class CryptString; +class SocketAddress; +class Task; +} // namespace talk_base + +namespace notifier { + +using ::testing::_; +using ::testing::Return; +using ::testing::SaveArg; + +class MockPreXmppAuth : public buzz::PreXmppAuth { + public: + virtual ~MockPreXmppAuth() {} + + MOCK_METHOD2(ChooseBestSaslMechanism, + std::string(const std::vector<std::string>&, bool)); + MOCK_METHOD1(CreateSaslMechanism, + buzz::SaslMechanism*(const std::string&)); + MOCK_METHOD5(StartPreXmppAuth, + void(const buzz::Jid&, + const talk_base::SocketAddress&, + const talk_base::CryptString&, + const std::string&, + const std::string&)); + MOCK_CONST_METHOD0(IsAuthDone, bool()); + MOCK_CONST_METHOD0(IsAuthorized, bool()); + MOCK_CONST_METHOD0(HadError, bool()); + MOCK_CONST_METHOD0(GetError, int()); + MOCK_CONST_METHOD0(GetCaptchaChallenge, buzz::CaptchaChallenge()); + MOCK_CONST_METHOD0(GetAuthToken, std::string()); + MOCK_CONST_METHOD0(GetAuthMechanism, std::string()); +}; + +class MockXmppConnectionDelegate : public XmppConnection::Delegate { + public: + virtual ~MockXmppConnectionDelegate() {} + + MOCK_METHOD1(OnConnect, void(base::WeakPtr<buzz::XmppTaskParentInterface>)); + MOCK_METHOD3(OnError, + void(buzz::XmppEngine::Error, int, const buzz::XmlElement*)); +}; + +class XmppConnectionTest : public testing::Test { + protected: + XmppConnectionTest() + : mock_pre_xmpp_auth_(new MockPreXmppAuth()), + url_request_context_getter_(new net::TestURLRequestContextGetter( + message_loop_.message_loop_proxy())) {} + + virtual ~XmppConnectionTest() {} + + virtual void TearDown() { + // Clear out any messages posted by XmppConnection's destructor. + message_loop_.RunUntilIdle(); + } + + // Needed by XmppConnection. + base::MessageLoop message_loop_; + MockXmppConnectionDelegate mock_xmpp_connection_delegate_; + scoped_ptr<MockPreXmppAuth> mock_pre_xmpp_auth_; + scoped_refptr<net::TestURLRequestContextGetter> url_request_context_getter_; +}; + +TEST_F(XmppConnectionTest, CreateDestroy) { + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); +} + +#if !defined(_MSC_VER) || _MSC_VER < 1700 // http://crbug.com/158570 +TEST_F(XmppConnectionTest, ImmediateFailure) { + // ChromeAsyncSocket::Connect() will always return false since we're + // not setting a valid host, but this gets bubbled up as ERROR_NONE + // due to XmppClient's inconsistent error-handling. + EXPECT_CALL(mock_xmpp_connection_delegate_, + OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL)); + + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); + + // We need to do this *before* |xmpp_connection| gets destroyed or + // our delegate won't be called. + message_loop_.RunUntilIdle(); +} + +TEST_F(XmppConnectionTest, PreAuthFailure) { + EXPECT_CALL(*mock_pre_xmpp_auth_, StartPreXmppAuth(_, _, _, _,_)); + EXPECT_CALL(*mock_pre_xmpp_auth_, IsAuthDone()).WillOnce(Return(true)); + EXPECT_CALL(*mock_pre_xmpp_auth_, IsAuthorized()).WillOnce(Return(false)); + EXPECT_CALL(*mock_pre_xmpp_auth_, HadError()).WillOnce(Return(true)); + EXPECT_CALL(*mock_pre_xmpp_auth_, GetError()).WillOnce(Return(5)); + + EXPECT_CALL(mock_xmpp_connection_delegate_, + OnError(buzz::XmppEngine::ERROR_AUTH, 5, NULL)); + + XmppConnection xmpp_connection( + buzz::XmppClientSettings(), url_request_context_getter_, + &mock_xmpp_connection_delegate_, mock_pre_xmpp_auth_.release()); + + // We need to do this *before* |xmpp_connection| gets destroyed or + // our delegate won't be called. + message_loop_.RunUntilIdle(); +} + +TEST_F(XmppConnectionTest, FailureAfterPreAuth) { + EXPECT_CALL(*mock_pre_xmpp_auth_, StartPreXmppAuth(_, _, _, _,_)); + EXPECT_CALL(*mock_pre_xmpp_auth_, IsAuthDone()).WillOnce(Return(true)); + EXPECT_CALL(*mock_pre_xmpp_auth_, IsAuthorized()).WillOnce(Return(true)); + EXPECT_CALL(*mock_pre_xmpp_auth_, GetAuthMechanism()).WillOnce(Return("")); + EXPECT_CALL(*mock_pre_xmpp_auth_, GetAuthToken()).WillOnce(Return("")); + + EXPECT_CALL(mock_xmpp_connection_delegate_, + OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL)); + + XmppConnection xmpp_connection( + buzz::XmppClientSettings(), url_request_context_getter_, + &mock_xmpp_connection_delegate_, mock_pre_xmpp_auth_.release()); + + // We need to do this *before* |xmpp_connection| gets destroyed or + // our delegate won't be called. + message_loop_.RunUntilIdle(); +} + +TEST_F(XmppConnectionTest, RaisedError) { + EXPECT_CALL(mock_xmpp_connection_delegate_, + OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL)); + + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); + + xmpp_connection.weak_xmpp_client_-> + SignalStateChange(buzz::XmppEngine::STATE_CLOSED); +} +#endif + +TEST_F(XmppConnectionTest, Connect) { + base::WeakPtr<talk_base::Task> weak_ptr; + EXPECT_CALL(mock_xmpp_connection_delegate_, OnConnect(_)). + WillOnce(SaveArg<0>(&weak_ptr)); + + { + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); + + xmpp_connection.weak_xmpp_client_-> + SignalStateChange(buzz::XmppEngine::STATE_OPEN); + EXPECT_EQ(xmpp_connection.weak_xmpp_client_.get(), weak_ptr.get()); + } + + EXPECT_EQ(NULL, weak_ptr.get()); +} + +TEST_F(XmppConnectionTest, MultipleConnect) { + EXPECT_DEBUG_DEATH({ + base::WeakPtr<talk_base::Task> weak_ptr; + EXPECT_CALL(mock_xmpp_connection_delegate_, OnConnect(_)). + WillOnce(SaveArg<0>(&weak_ptr)); + + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); + + xmpp_connection.weak_xmpp_client_-> + SignalStateChange(buzz::XmppEngine::STATE_OPEN); + for (int i = 0; i < 3; ++i) { + xmpp_connection.weak_xmpp_client_-> + SignalStateChange(buzz::XmppEngine::STATE_OPEN); + } + + EXPECT_EQ(xmpp_connection.weak_xmpp_client_.get(), weak_ptr.get()); + }, "more than once"); +} + +#if !defined(_MSC_VER) || _MSC_VER < 1700 // http://crbug.com/158570 +TEST_F(XmppConnectionTest, ConnectThenError) { + base::WeakPtr<talk_base::Task> weak_ptr; + EXPECT_CALL(mock_xmpp_connection_delegate_, OnConnect(_)). + WillOnce(SaveArg<0>(&weak_ptr)); + EXPECT_CALL(mock_xmpp_connection_delegate_, + OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL)); + + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); + + xmpp_connection.weak_xmpp_client_-> + SignalStateChange(buzz::XmppEngine::STATE_OPEN); + EXPECT_EQ(xmpp_connection.weak_xmpp_client_.get(), weak_ptr.get()); + + xmpp_connection.weak_xmpp_client_-> + SignalStateChange(buzz::XmppEngine::STATE_CLOSED); + EXPECT_EQ(NULL, weak_ptr.get()); +} +#endif + +// We don't destroy XmppConnection's task pump on destruction, but it +// should still not run any more tasks. +TEST_F(XmppConnectionTest, TasksDontRunAfterXmppConnectionDestructor) { + { + XmppConnection xmpp_connection(buzz::XmppClientSettings(), + url_request_context_getter_, + &mock_xmpp_connection_delegate_, NULL); + + jingle_glue::MockTask* task = + new jingle_glue::MockTask(xmpp_connection.task_pump_.get()); + // We have to do this since the state enum is protected in + // talk_base::Task. + const int TASK_STATE_ERROR = 3; + ON_CALL(*task, ProcessStart()) + .WillByDefault(Return(TASK_STATE_ERROR)); + EXPECT_CALL(*task, ProcessStart()).Times(0); + task->Start(); + } + + // This should destroy |task_pump|, but |task| still shouldn't run. + message_loop_.RunUntilIdle(); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/DEPS b/chromium/jingle/notifier/communicator/DEPS new file mode 100644 index 00000000000..fe7e23ffe20 --- /dev/null +++ b/chromium/jingle/notifier/communicator/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # Need for cricket::ProtocolType. + "+talk/p2p/base/port.h", +] diff --git a/chromium/jingle/notifier/communicator/connection_settings.cc b/chromium/jingle/notifier/communicator/connection_settings.cc new file mode 100644 index 00000000000..862f905800f --- /dev/null +++ b/chromium/jingle/notifier/communicator/connection_settings.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2012 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/communicator/connection_settings.h" + +#include "base/logging.h" + +// Ideally we shouldn't include anything from talk/p2p, but we need +// the definition of ProtocolType. Don't use any functions from +// port.h, since it won't link. +#include "talk/p2p/base/port.h" + +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +const uint16 kSslTcpPort = 443; + +ConnectionSettings::ConnectionSettings( + const talk_base::SocketAddress& server, + SslTcpMode ssltcp_mode, + SslTcpSupport ssltcp_support) + : server(server), + ssltcp_mode(ssltcp_mode), + ssltcp_support(ssltcp_support) {} + +ConnectionSettings::ConnectionSettings() + : ssltcp_mode(DO_NOT_USE_SSLTCP), + ssltcp_support(DOES_NOT_SUPPORT_SSLTCP) {} + +ConnectionSettings::~ConnectionSettings() {} + +bool ConnectionSettings::Equals(const ConnectionSettings& settings) const { + return + server == settings.server && + ssltcp_mode == settings.ssltcp_mode && + ssltcp_support == settings.ssltcp_support; +} + +namespace { + +const char* SslTcpModeToString(SslTcpMode ssltcp_mode) { + return (ssltcp_mode == USE_SSLTCP) ? "USE_SSLTCP" : "DO_NOT_USE_SSLTCP"; +} + +const char* SslTcpSupportToString(SslTcpSupport ssltcp_support) { + return + (ssltcp_support == SUPPORTS_SSLTCP) ? + "SUPPORTS_SSLTCP" : + "DOES_NOT_SUPPORT_SSLTCP"; +} + +} // namespace + +std::string ConnectionSettings::ToString() const { + return + server.ToString() + ":" + SslTcpModeToString(ssltcp_mode) + ":" + + SslTcpSupportToString(ssltcp_support); +} + +void ConnectionSettings::FillXmppClientSettings( + buzz::XmppClientSettings* client_settings) const { + client_settings->set_protocol( + (ssltcp_mode == USE_SSLTCP) ? + cricket::PROTO_SSLTCP : + cricket::PROTO_TCP); + client_settings->set_server(server); +} + +ConnectionSettingsList MakeConnectionSettingsList( + const ServerList& servers, + bool try_ssltcp_first) { + ConnectionSettingsList settings_list; + + for (ServerList::const_iterator it = servers.begin(); + it != servers.end(); ++it) { + const ConnectionSettings settings( + talk_base::SocketAddress(it->server.host(), it->server.port()), + DO_NOT_USE_SSLTCP, it->ssltcp_support); + + if (it->ssltcp_support == SUPPORTS_SSLTCP) { + const ConnectionSettings settings_with_ssltcp( + talk_base::SocketAddress(it->server.host(), kSslTcpPort), + USE_SSLTCP, it->ssltcp_support); + + if (try_ssltcp_first) { + settings_list.push_back(settings_with_ssltcp); + settings_list.push_back(settings); + } else { + settings_list.push_back(settings); + settings_list.push_back(settings_with_ssltcp); + } + } else { + settings_list.push_back(settings); + } + } + + return settings_list; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/connection_settings.h b/chromium/jingle/notifier/communicator/connection_settings.h new file mode 100644 index 00000000000..fe0f6b03d18 --- /dev/null +++ b/chromium/jingle/notifier/communicator/connection_settings.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "jingle/notifier/base/server_information.h" +#include "talk/base/socketaddress.h" + +namespace buzz { +class XmppClientSettings; +} // namespace + +namespace notifier { + +// The port for SSLTCP (just the regular port for SSL). +extern const uint16 kSslTcpPort; + +enum SslTcpMode { DO_NOT_USE_SSLTCP, USE_SSLTCP }; + +struct ConnectionSettings { + public: + ConnectionSettings(const talk_base::SocketAddress& server, + SslTcpMode ssltcp_mode, + SslTcpSupport ssltcp_support); + ConnectionSettings(); + ~ConnectionSettings(); + + bool Equals(const ConnectionSettings& settings) const; + + std::string ToString() const; + + // Fill in the connection-related fields of |client_settings|. + void FillXmppClientSettings(buzz::XmppClientSettings* client_settings) const; + + talk_base::SocketAddress server; + SslTcpMode ssltcp_mode; + SslTcpSupport ssltcp_support; +}; + +typedef std::vector<ConnectionSettings> ConnectionSettingsList; + +// Given a list of servers in priority order, generate a list of +// ConnectionSettings to try in priority order. If |try_ssltcp_first| +// is set, for each server that supports SSLTCP, the +// ConnectionSettings using SSLTCP will come first. If it is not set, +// the ConnectionSettings using SSLTCP will come last. +ConnectionSettingsList MakeConnectionSettingsList( + const ServerList& servers, + bool try_ssltcp_first); + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_ diff --git a/chromium/jingle/notifier/communicator/connection_settings_unittest.cc b/chromium/jingle/notifier/communicator/connection_settings_unittest.cc new file mode 100644 index 00000000000..c7e0c4c93bf --- /dev/null +++ b/chromium/jingle/notifier/communicator/connection_settings_unittest.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2012 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/communicator/connection_settings.h" + +#include "jingle/notifier/base/server_information.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +namespace { + +class ConnectionSettingsTest : public ::testing::Test { + protected: + ConnectionSettingsTest() { + servers_.push_back( + ServerInformation( + net::HostPortPair("supports_ssltcp.com", 100), + SUPPORTS_SSLTCP)); + servers_.push_back( + ServerInformation( + net::HostPortPair("does_not_support_ssltcp.com", 200), + DOES_NOT_SUPPORT_SSLTCP)); + } + + ServerList servers_; +}; + +// An empty server list should always map to an empty connection +// settings list. +TEST_F(ConnectionSettingsTest, Empty) { + EXPECT_TRUE(MakeConnectionSettingsList(ServerList(), false).empty()); + EXPECT_TRUE(MakeConnectionSettingsList(ServerList(), true).empty()); +} + +// Make sure that servers that support SSLTCP get mapped to two +// settings entries (with the SSLTCP one coming last) whereas those +// that don't map to only one. +TEST_F(ConnectionSettingsTest, Basic) { + const ConnectionSettingsList settings_list = + MakeConnectionSettingsList(servers_, false /* try_ssltcp_first */); + + ConnectionSettingsList expected_settings_list; + expected_settings_list.push_back( + ConnectionSettings( + talk_base::SocketAddress("supports_ssltcp.com", 100), + DO_NOT_USE_SSLTCP, + SUPPORTS_SSLTCP)); + expected_settings_list.push_back( + ConnectionSettings( + talk_base::SocketAddress("supports_ssltcp.com", 443), + USE_SSLTCP, + SUPPORTS_SSLTCP)); + expected_settings_list.push_back( + ConnectionSettings( + talk_base::SocketAddress("does_not_support_ssltcp.com", 200), + DO_NOT_USE_SSLTCP, + DOES_NOT_SUPPORT_SSLTCP)); + + ASSERT_EQ(expected_settings_list.size(), settings_list.size()); + for (size_t i = 0; i < settings_list.size(); ++i) { + EXPECT_TRUE(settings_list[i].Equals(expected_settings_list[i])); + } +} + +// Make sure that servers that support SSLTCP get mapped to two +// settings entries (with the SSLTCP one coming first) when +// try_ssltcp_first is set. +TEST_F(ConnectionSettingsTest, TrySslTcpFirst) { + const ConnectionSettingsList settings_list = + MakeConnectionSettingsList(servers_, true /* try_ssltcp_first */); + + ConnectionSettingsList expected_settings_list; + expected_settings_list.push_back( + ConnectionSettings( + talk_base::SocketAddress("supports_ssltcp.com", 443), + USE_SSLTCP, + SUPPORTS_SSLTCP)); + expected_settings_list.push_back( + ConnectionSettings( + talk_base::SocketAddress("supports_ssltcp.com", 100), + DO_NOT_USE_SSLTCP, + SUPPORTS_SSLTCP)); + expected_settings_list.push_back( + ConnectionSettings( + talk_base::SocketAddress("does_not_support_ssltcp.com", 200), + DO_NOT_USE_SSLTCP, + DOES_NOT_SUPPORT_SSLTCP)); + + ASSERT_EQ(expected_settings_list.size(), settings_list.size()); + for (size_t i = 0; i < settings_list.size(); ++i) { + EXPECT_TRUE(settings_list[i].Equals(expected_settings_list[i])); + } +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/login.cc b/chromium/jingle/notifier/communicator/login.cc new file mode 100644 index 00000000000..6c508627874 --- /dev/null +++ b/chromium/jingle/notifier/communicator/login.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2012 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/communicator/login.h" + +#include <string> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/time/time.h" +#include "net/base/host_port_pair.h" +#include "talk/base/common.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/logging.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/taskrunner.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +// Redirect valid for 5 minutes. +static const int kRedirectTimeoutMinutes = 5; + +Login::Delegate::~Delegate() {} + +Login::Login(Delegate* delegate, + const buzz::XmppClientSettings& user_settings, + const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter, + const ServerList& servers, + bool try_ssltcp_first, + const std::string& auth_mechanism) + : delegate_(delegate), + login_settings_(user_settings, + request_context_getter, + servers, + try_ssltcp_first, + auth_mechanism) { + net::NetworkChangeNotifier::AddIPAddressObserver(this); + net::NetworkChangeNotifier::AddConnectionTypeObserver(this); + // TODO(akalin): Add as DNSObserver once bug 130610 is fixed. + ResetReconnectState(); +} + +Login::~Login() { + net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); + net::NetworkChangeNotifier::RemoveIPAddressObserver(this); +} + +void Login::StartConnection() { + DVLOG(1) << "Starting connection..."; + single_attempt_.reset(new SingleLoginAttempt(login_settings_, this)); +} + +void Login::UpdateXmppSettings(const buzz::XmppClientSettings& user_settings) { + DVLOG(1) << "XMPP settings updated"; + login_settings_.set_user_settings(user_settings); +} + +// In the code below, we assume that calling a delegate method may end +// up in ourselves being deleted, so we always call it last. +// +// TODO(akalin): Add unit tests to enforce the behavior above. + +void Login::OnConnect(base::WeakPtr<buzz::XmppTaskParentInterface> base_task) { + DVLOG(1) << "Connected"; + ResetReconnectState(); + delegate_->OnConnect(base_task); +} + +void Login::OnRedirect(const ServerInformation& redirect_server) { + DVLOG(1) << "Redirected"; + login_settings_.SetRedirectServer(redirect_server); + // Drop the current connection, and start the login process again. + StartConnection(); + delegate_->OnTransientDisconnection(); +} + +void Login::OnCredentialsRejected() { + DVLOG(1) << "Credentials rejected"; + TryReconnect(); + delegate_->OnCredentialsRejected(); +} + +void Login::OnSettingsExhausted() { + DVLOG(1) << "Settings exhausted"; + TryReconnect(); + delegate_->OnTransientDisconnection(); +} + +void Login::OnIPAddressChanged() { + DVLOG(1) << "IP address changed"; + OnNetworkEvent(); +} + +void Login::OnConnectionTypeChanged( + net::NetworkChangeNotifier::ConnectionType type) { + DVLOG(1) << "Connection type changed"; + OnNetworkEvent(); +} + +void Login::OnDNSChanged() { + DVLOG(1) << "DNS changed"; + OnNetworkEvent(); +} + +void Login::OnNetworkEvent() { + // Reconnect in 1 to 9 seconds (vary the time a little to try to + // avoid spikey behavior on network hiccups). + reconnect_interval_ = base::TimeDelta::FromSeconds(base::RandInt(1, 9)); + TryReconnect(); + delegate_->OnTransientDisconnection(); +} + +void Login::ResetReconnectState() { + reconnect_interval_ = + base::TimeDelta::FromSeconds(base::RandInt(5, 25)); + reconnect_timer_.Stop(); +} + +void Login::TryReconnect() { + DCHECK_GT(reconnect_interval_.InSeconds(), 0); + single_attempt_.reset(); + reconnect_timer_.Stop(); + DVLOG(1) << "Reconnecting in " + << reconnect_interval_.InSeconds() << " seconds"; + reconnect_timer_.Start( + FROM_HERE, reconnect_interval_, this, &Login::DoReconnect); +} + +void Login::DoReconnect() { + // Double reconnect time up to 30 minutes. + const base::TimeDelta kMaxReconnectInterval = + base::TimeDelta::FromMinutes(30); + reconnect_interval_ *= 2; + if (reconnect_interval_ > kMaxReconnectInterval) + reconnect_interval_ = kMaxReconnectInterval; + DVLOG(1) << "Reconnecting..."; + StartConnection(); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/login.h b/chromium/jingle/notifier/communicator/login.h new file mode 100644 index 00000000000..463eb1a88a2 --- /dev/null +++ b/chromium/jingle/notifier/communicator/login.h @@ -0,0 +1,131 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "jingle/notifier/base/server_information.h" +#include "jingle/notifier/communicator/login_settings.h" +#include "jingle/notifier/communicator/single_login_attempt.h" +#include "net/base/network_change_notifier.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class XmppClient; +class XmppClientSettings; +class XmppTaskParentInterface; +} // namespace buzz + +namespace net { +class URLRequestContextGetter; +} // namespace net + +namespace notifier { + +class LoginSettings; + +// Does the login, keeps it alive (with refreshing cookies and +// reattempting login when disconnected), and figures out what actions +// to take on the various errors that may occur. +// +// TODO(akalin): Make this observe proxy config changes also. +class Login : public net::NetworkChangeNotifier::IPAddressObserver, + public net::NetworkChangeNotifier::ConnectionTypeObserver, + public net::NetworkChangeNotifier::DNSObserver, + public SingleLoginAttempt::Delegate { + public: + class Delegate { + public: + // Called when a connection has been successfully established. + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) = 0; + + // Called when there's no connection to the server but we expect + // it to come back come back eventually. The connection will be + // retried with exponential backoff. + virtual void OnTransientDisconnection() = 0; + + // Called when the current login credentials have been rejected. + // The connection will still be retried with exponential backoff; + // it's up to the delegate to stop connecting and/or prompt for + // new credentials. + virtual void OnCredentialsRejected() = 0; + + protected: + virtual ~Delegate(); + }; + + // Does not take ownership of |delegate|, which must not be NULL. + Login(Delegate* delegate, + const buzz::XmppClientSettings& user_settings, + const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter, + const ServerList& servers, + bool try_ssltcp_first, + const std::string& auth_mechanism); + virtual ~Login(); + + // Starts connecting (or forces a reconnection if we're backed off). + void StartConnection(); + + // The updated settings take effect only the next time when a + // connection is attempted (either via reconnection or a call to + // StartConnection()). + void UpdateXmppSettings(const buzz::XmppClientSettings& user_settings); + + // net::NetworkChangeNotifier::IPAddressObserver implementation. + virtual void OnIPAddressChanged() OVERRIDE; + + // net::NetworkChangeNotifier::ConnectionTypeObserver implementation. + virtual void OnConnectionTypeChanged( + net::NetworkChangeNotifier::ConnectionType type) OVERRIDE; + + // net::NetworkChangeNotifier::DNSObserver implementation. + virtual void OnDNSChanged() OVERRIDE; + + // SingleLoginAttempt::Delegate implementation. + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) OVERRIDE; + virtual void OnRedirect(const ServerInformation& redirect_server) OVERRIDE; + virtual void OnCredentialsRejected() OVERRIDE; + virtual void OnSettingsExhausted() OVERRIDE; + + private: + // Called by the various network notifications. + void OnNetworkEvent(); + + // Stops any existing reconnect timer and sets an initial reconnect + // interval. + void ResetReconnectState(); + + // Tries to reconnect in some point in the future. If called + // repeatedly, will wait longer and longer until reconnecting. + void TryReconnect(); + + // The actual function (called by |reconnect_timer_|) that does the + // reconnection. + void DoReconnect(); + + Delegate* const delegate_; + LoginSettings login_settings_; + scoped_ptr<SingleLoginAttempt> single_attempt_; + + // reconnection state. + base::TimeDelta reconnect_interval_; + base::OneShotTimer<Login> reconnect_timer_; + + DISALLOW_COPY_AND_ASSIGN(Login); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_H_ diff --git a/chromium/jingle/notifier/communicator/login_settings.cc b/chromium/jingle/notifier/communicator/login_settings.cc new file mode 100644 index 00000000000..3dacbea5dde --- /dev/null +++ b/chromium/jingle/notifier/communicator/login_settings.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2012 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 <string> + +#include "jingle/notifier/communicator/login_settings.h" + +#include "base/logging.h" +#include "jingle/notifier/base/server_information.h" +#include "net/cert/cert_verifier.h" +#include "talk/base/common.h" +#include "talk/base/socketaddress.h" + +namespace notifier { + +LoginSettings::LoginSettings(const buzz::XmppClientSettings& user_settings, + const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter, + const ServerList& default_servers, + bool try_ssltcp_first, + const std::string& auth_mechanism) + : user_settings_(user_settings), + request_context_getter_(request_context_getter), + default_servers_(default_servers), + try_ssltcp_first_(try_ssltcp_first), + auth_mechanism_(auth_mechanism) { + DCHECK_GT(default_servers_.size(), 0u); +} + +LoginSettings::~LoginSettings() {} + +void LoginSettings::set_user_settings( + const buzz::XmppClientSettings& user_settings) { + user_settings_ = user_settings; +} + +ServerList LoginSettings::GetServers() const { + return GetServersForTime(base::Time::Now()); +} + +namespace { + +// How long a redirect is valid for. +const int kRedirectExpirationTimeMinutes = 5; + +} // namespace + +void LoginSettings::SetRedirectServer( + const ServerInformation& redirect_server) { + redirect_server_ = redirect_server; + redirect_expiration_ = + base::Time::Now() + + base::TimeDelta::FromMinutes(kRedirectExpirationTimeMinutes); +} + +ServerList LoginSettings::GetServersForTimeForTest(base::Time now) const { + return GetServersForTime(now); +} + +base::Time LoginSettings::GetRedirectExpirationForTest() const { + return redirect_expiration_; +} + +ServerList LoginSettings::GetServersForTime(base::Time now) const { + return + (now < redirect_expiration_) ? + ServerList(1, redirect_server_) : + default_servers_; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/login_settings.h b/chromium/jingle/notifier/communicator/login_settings.h new file mode 100644 index 00000000000..04ab92d4f4d --- /dev/null +++ b/chromium/jingle/notifier/communicator/login_settings.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_ +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "jingle/notifier/base/server_information.h" +#include "net/url_request/url_request_context_getter.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +class LoginSettings { + public: + LoginSettings(const buzz::XmppClientSettings& user_settings, + const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter, + const ServerList& default_servers, + bool try_ssltcp_first, + const std::string& auth_mechanism); + + ~LoginSettings(); + + // Copy constructor and assignment operator welcome. + + const buzz::XmppClientSettings& user_settings() const { + return user_settings_; + } + + void set_user_settings(const buzz::XmppClientSettings& user_settings); + + scoped_refptr<net::URLRequestContextGetter> request_context_getter() const { + return request_context_getter_; + } + + bool try_ssltcp_first() const { + return try_ssltcp_first_; + } + + const std::string& auth_mechanism() const { + return auth_mechanism_; + } + + ServerList GetServers() const; + + // The redirect server will eventually expire. + void SetRedirectServer(const ServerInformation& redirect_server); + + ServerList GetServersForTimeForTest(base::Time now) const; + + base::Time GetRedirectExpirationForTest() const; + + private: + ServerList GetServersForTime(base::Time now) const; + + buzz::XmppClientSettings user_settings_; + scoped_refptr<net::URLRequestContextGetter> request_context_getter_; + ServerList default_servers_; + bool try_ssltcp_first_; + std::string auth_mechanism_; + + // Used to handle redirects + ServerInformation redirect_server_; + base::Time redirect_expiration_; + +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_ diff --git a/chromium/jingle/notifier/communicator/login_settings_unittest.cc b/chromium/jingle/notifier/communicator/login_settings_unittest.cc new file mode 100644 index 00000000000..5919d1d5f3f --- /dev/null +++ b/chromium/jingle/notifier/communicator/login_settings_unittest.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2012 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/communicator/login_settings.h" + +#include <cstddef> + +#include "talk/xmpp/xmppclientsettings.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +namespace { + +const char kAuthMechanism[] = "auth_mechanism"; + +class LoginSettingsTest : public ::testing::Test { + protected: + LoginSettingsTest() { + servers_.push_back( + ServerInformation( + net::HostPortPair("default.com", 100), + DOES_NOT_SUPPORT_SSLTCP)); + } + + ServerList servers_; +}; + +TEST_F(LoginSettingsTest, Basic) { + const LoginSettings login_settings(buzz::XmppClientSettings(), + NULL, + servers_, + false /* try_ssltcp_first */, + kAuthMechanism); + EXPECT_EQ(base::Time(), login_settings.GetRedirectExpirationForTest()); + const ServerList& servers = login_settings.GetServers(); + ASSERT_EQ(servers_.size(), servers.size()); + for (size_t i = 0; i < servers.size(); ++i) { + EXPECT_TRUE(servers[i].Equals(servers_[i])); + } +} + +TEST_F(LoginSettingsTest, Redirect) { + LoginSettings login_settings(buzz::XmppClientSettings(), + NULL, + servers_, + false /* try_ssltcp_first */, + kAuthMechanism); + const ServerInformation redirect_server( + net::HostPortPair("redirect.com", 200), + SUPPORTS_SSLTCP); + login_settings.SetRedirectServer(redirect_server); + + { + const ServerList& servers = + login_settings.GetServersForTimeForTest( + login_settings.GetRedirectExpirationForTest() - + base::TimeDelta::FromMilliseconds(1)); + ASSERT_EQ(servers_.size(), 1u); + EXPECT_TRUE(servers[0].Equals(redirect_server)); + } + + { + const ServerList& servers = + login_settings.GetServersForTimeForTest( + login_settings.GetRedirectExpirationForTest()); + ASSERT_EQ(servers_.size(), servers.size()); + for (size_t i = 0; i < servers.size(); ++i) { + EXPECT_TRUE(servers[i].Equals(servers_[i])); + } + } +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/single_login_attempt.cc b/chromium/jingle/notifier/communicator/single_login_attempt.cc new file mode 100644 index 00000000000..67268c34b16 --- /dev/null +++ b/chromium/jingle/notifier/communicator/single_login_attempt.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2012 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 <string> + +#include "jingle/notifier/communicator/single_login_attempt.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "jingle/notifier/base/const_communicator.h" +#include "jingle/notifier/base/gaia_token_pre_xmpp_auth.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "net/base/host_port_pair.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +SingleLoginAttempt::Delegate::~Delegate() {} + +SingleLoginAttempt::SingleLoginAttempt(const LoginSettings& login_settings, + Delegate* delegate) + : login_settings_(login_settings), + delegate_(delegate), + settings_list_( + MakeConnectionSettingsList(login_settings_.GetServers(), + login_settings_.try_ssltcp_first())), + current_settings_(settings_list_.begin()) { + if (settings_list_.empty()) { + NOTREACHED(); + return; + } + TryConnect(*current_settings_); +} + +SingleLoginAttempt::~SingleLoginAttempt() {} + +// In the code below, we assume that calling a delegate method may end +// up in ourselves being deleted, so we always call it last. +// +// TODO(akalin): Add unit tests to enforce the behavior above. + +void SingleLoginAttempt::OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) { + DVLOG(1) << "Connected to " << current_settings_->ToString(); + delegate_->OnConnect(base_task); +} + +namespace { + +// This function is more permissive than +// net::HostPortPair::FromString(). If the port is missing or +// unparseable, it assumes the default XMPP port. The hostname may be +// empty. +net::HostPortPair ParseRedirectText(const std::string& redirect_text) { + std::vector<std::string> parts; + base::SplitString(redirect_text, ':', &parts); + net::HostPortPair redirect_server; + redirect_server.set_port(kDefaultXmppPort); + if (parts.empty()) { + return redirect_server; + } + redirect_server.set_host(parts[0]); + if (parts.size() <= 1) { + return redirect_server; + } + // Try to parse the port, falling back to kDefaultXmppPort. + int port = kDefaultXmppPort; + if (!base::StringToInt(parts[1], &port)) { + port = kDefaultXmppPort; + } + if (port <= 0 || port > kuint16max) { + port = kDefaultXmppPort; + } + redirect_server.set_port(port); + return redirect_server; +} + +} // namespace + +void SingleLoginAttempt::OnError(buzz::XmppEngine::Error error, int subcode, + const buzz::XmlElement* stream_error) { + DVLOG(1) << "Error: " << error << ", subcode: " << subcode + << (stream_error + ? (", stream error: " + XmlElementToString(*stream_error)) + : std::string()); + + DCHECK_EQ(error == buzz::XmppEngine::ERROR_STREAM, stream_error != NULL); + + // Check for redirection. We expect something like: + // + // <stream:error><see-other-host xmlns="urn:ietf:params:xml:ns:xmpp-streams"/><str:text xmlns:str="urn:ietf:params:xml:ns:xmpp-streams">talk.google.com</str:text></stream:error> [2] + // + // There are some differences from the spec [1]: + // + // - we expect a separate text element with the redirection info + // (which is the format Google Talk's servers use), whereas the + // spec puts the redirection info directly in the see-other-host + // element; + // - we check for redirection only during login, whereas the + // server can send down a redirection at any time according to + // the spec. (TODO(akalin): Figure out whether we need to handle + // redirection at any other point.) + // + // [1]: http://xmpp.org/internet-drafts/draft-saintandre-rfc3920bis-08.html#streams-error-conditions-see-other-host + // [2]: http://forums.miranda-im.org/showthread.php?24376-GoogleTalk-drops + if (stream_error) { + const buzz::XmlElement* other = + stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST); + if (other) { + const buzz::XmlElement* text = + stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT); + if (text) { + // Yep, its a "stream:error" with "see-other-host" text, + // let's parse out the server:port, and then reconnect + // with that. + const net::HostPortPair& redirect_server = + ParseRedirectText(text->BodyText()); + // ParseRedirectText shouldn't return a zero port. + DCHECK_NE(redirect_server.port(), 0u); + // If we don't have a host, ignore the redirection and treat + // it like a regular error. + if (!redirect_server.host().empty()) { + delegate_->OnRedirect( + ServerInformation( + redirect_server, + current_settings_->ssltcp_support)); + // May be deleted at this point. + return; + } + } + } + } + + if (error == buzz::XmppEngine::ERROR_UNAUTHORIZED) { + DVLOG(1) << "Credentials rejected"; + delegate_->OnCredentialsRejected(); + return; + } + + if (current_settings_ == settings_list_.end()) { + NOTREACHED(); + return; + } + + ++current_settings_; + if (current_settings_ == settings_list_.end()) { + DVLOG(1) << "Could not connect to any XMPP server"; + delegate_->OnSettingsExhausted(); + return; + } + + TryConnect(*current_settings_); +} + +void SingleLoginAttempt::TryConnect( + const ConnectionSettings& connection_settings) { + DVLOG(1) << "Trying to connect to " << connection_settings.ToString(); + // Copy the user settings and fill in the connection parameters from + // |connection_settings|. + buzz::XmppClientSettings client_settings = login_settings_.user_settings(); + connection_settings.FillXmppClientSettings(&client_settings); + + buzz::Jid jid(client_settings.user(), client_settings.host(), + buzz::STR_EMPTY); + buzz::PreXmppAuth* pre_xmpp_auth = + new GaiaTokenPreXmppAuth( + jid.Str(), client_settings.auth_token(), + client_settings.token_service(), + login_settings_.auth_mechanism()); + xmpp_connection_.reset( + new XmppConnection(client_settings, + login_settings_.request_context_getter(), + this, + pre_xmpp_auth)); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/communicator/single_login_attempt.h b/chromium/jingle/notifier/communicator/single_login_attempt.h new file mode 100644 index 00000000000..baf45e15e89 --- /dev/null +++ b/chromium/jingle/notifier/communicator/single_login_attempt.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "jingle/notifier/base/xmpp_connection.h" +#include "jingle/notifier/communicator/connection_settings.h" +#include "jingle/notifier/communicator/login_settings.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class XmppTaskParentInterface; +} // namespace buzz + +namespace notifier { + +struct ServerInformation; + +// Handles all of the aspects of a single login attempt. By +// containing this within one class, when another login attempt is +// made, this class can be destroyed to immediately stop the previous +// login attempt. +class SingleLoginAttempt : public XmppConnection::Delegate { + public: + // At most one delegate method will be called, depending on the + // result of the login attempt. After the delegate method is + // called, this class won't do anything anymore until it is + // destroyed, at which point it will disconnect if necessary. + class Delegate { + public: + // Called when the login attempt is successful. + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) = 0; + + // Called when the server responds with a redirect. A new login + // attempt should be made to the given redirect server. + virtual void OnRedirect(const ServerInformation& redirect_server) = 0; + + // Called when a server rejects the client's login credentials. A + // new login attempt should be made once the client provides new + // credentials. + virtual void OnCredentialsRejected() = 0; + + // Called when no server could be logged into for reasons other + // than redirection or rejected credentials. A new login attempt + // may be created, but it should be done with exponential backoff. + virtual void OnSettingsExhausted() = 0; + + protected: + virtual ~Delegate(); + }; + + // Does not take ownership of |delegate|, which must not be NULL. + SingleLoginAttempt(const LoginSettings& login_settings, Delegate* delegate); + + virtual ~SingleLoginAttempt(); + + // XmppConnection::Delegate implementation. + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> parent) OVERRIDE; + virtual void OnError(buzz::XmppEngine::Error error, + int error_subcode, + const buzz::XmlElement* stream_error) OVERRIDE; + + private: + void TryConnect(const ConnectionSettings& new_settings); + + const LoginSettings login_settings_; + Delegate* const delegate_; + const ConnectionSettingsList settings_list_; + ConnectionSettingsList::const_iterator current_settings_; + scoped_ptr<XmppConnection> xmpp_connection_; + + DISALLOW_COPY_AND_ASSIGN(SingleLoginAttempt); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ diff --git a/chromium/jingle/notifier/communicator/single_login_attempt_unittest.cc b/chromium/jingle/notifier/communicator/single_login_attempt_unittest.cc new file mode 100644 index 00000000000..f731fd9871b --- /dev/null +++ b/chromium/jingle/notifier/communicator/single_login_attempt_unittest.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2012 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/communicator/single_login_attempt.h" + +#include <cstddef> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/notifier/base/const_communicator.h" +#include "jingle/notifier/base/fake_base_task.h" +#include "jingle/notifier/communicator/login_settings.h" +#include "net/dns/mock_host_resolver.h" +#include "net/url_request/url_request_test_util.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppengine.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace buzz { +class XmppTaskParentInterface; +} // namespace buzz + +namespace notifier { + +namespace { + +enum DelegateState { + IDLE, CONNECTED, REDIRECTED, CREDENTIALS_REJECTED, SETTINGS_EXHAUSTED +}; + +class FakeDelegate : public SingleLoginAttempt::Delegate { + public: + FakeDelegate() : state_(IDLE) {} + + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) OVERRIDE { + state_ = CONNECTED; + base_task_ = base_task; + } + + virtual void OnRedirect(const ServerInformation& redirect_server) OVERRIDE { + state_ = REDIRECTED; + redirect_server_ = redirect_server; + } + + virtual void OnCredentialsRejected() OVERRIDE { + state_ = CREDENTIALS_REJECTED; + } + + virtual void OnSettingsExhausted() OVERRIDE { + state_ = SETTINGS_EXHAUSTED; + } + + DelegateState state() const { return state_; } + + base::WeakPtr<buzz::XmppTaskParentInterface> base_task() const { + return base_task_; + } + + const ServerInformation& redirect_server() const { + return redirect_server_; + } + + private: + DelegateState state_; + base::WeakPtr<buzz::XmppTaskParentInterface> base_task_; + ServerInformation redirect_server_; +}; + +class MyTestURLRequestContext : public net::TestURLRequestContext { + public: + MyTestURLRequestContext() : TestURLRequestContext(true) { + context_storage_.set_host_resolver( + scoped_ptr<net::HostResolver>(new net::HangingHostResolver())); + Init(); + } + virtual ~MyTestURLRequestContext() {} +}; + +class SingleLoginAttemptTest : public ::testing::Test { + protected: + SingleLoginAttemptTest() + : login_settings_( + buzz::XmppClientSettings(), + new net::TestURLRequestContextGetter( + base::MessageLoopProxy::current(), + scoped_ptr<net::TestURLRequestContext>( + new MyTestURLRequestContext())), + ServerList( + 1, + ServerInformation( + net::HostPortPair("example.com", 100), SUPPORTS_SSLTCP)), + false /* try_ssltcp_first */, + "auth_mechanism"), + attempt_(new SingleLoginAttempt(login_settings_, &fake_delegate_)) {} + + virtual void TearDown() OVERRIDE { + message_loop_.RunUntilIdle(); + } + + void FireRedirect(buzz::XmlElement* redirect_error) { + attempt_->OnError(buzz::XmppEngine::ERROR_STREAM, 0, redirect_error); + } + + virtual ~SingleLoginAttemptTest() { + attempt_.reset(); + message_loop_.RunUntilIdle(); + } + + private: + base::MessageLoop message_loop_; + const LoginSettings login_settings_; + + protected: + scoped_ptr<SingleLoginAttempt> attempt_; + FakeDelegate fake_delegate_; + FakeBaseTask fake_base_task_; +}; + +// Fire OnConnect and make sure the base task gets passed to the +// delegate properly. +TEST_F(SingleLoginAttemptTest, Basic) { + attempt_->OnConnect(fake_base_task_.AsWeakPtr()); + EXPECT_EQ(CONNECTED, fake_delegate_.state()); + EXPECT_EQ(fake_base_task_.AsWeakPtr().get(), + fake_delegate_.base_task().get()); +} + +// Fire OnErrors and make sure the delegate gets the +// OnSettingsExhausted() event. +TEST_F(SingleLoginAttemptTest, Error) { + for (int i = 0; i < 2; ++i) { + EXPECT_EQ(IDLE, fake_delegate_.state()); + attempt_->OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL); + } + EXPECT_EQ(SETTINGS_EXHAUSTED, fake_delegate_.state()); +} + +// Fire OnErrors but replace the last one with OnConnect, and make +// sure the delegate still gets the OnConnect message. +TEST_F(SingleLoginAttemptTest, ErrorThenSuccess) { + attempt_->OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL); + attempt_->OnConnect(fake_base_task_.AsWeakPtr()); + EXPECT_EQ(CONNECTED, fake_delegate_.state()); + EXPECT_EQ(fake_base_task_.AsWeakPtr().get(), + fake_delegate_.base_task().get()); +} + +buzz::XmlElement* MakeRedirectError(const std::string& redirect_server) { + buzz::XmlElement* stream_error = + new buzz::XmlElement(buzz::QN_STREAM_ERROR, true); + stream_error->AddElement( + new buzz::XmlElement(buzz::QN_XSTREAM_SEE_OTHER_HOST, true)); + buzz::XmlElement* text = + new buzz::XmlElement(buzz::QN_XSTREAM_TEXT, true); + stream_error->AddElement(text); + text->SetBodyText(redirect_server); + return stream_error; +} + +// Fire a redirect and make sure the delegate gets the proper redirect +// server info. +TEST_F(SingleLoginAttemptTest, Redirect) { + const ServerInformation redirect_server( + net::HostPortPair("example.com", 1000), + SUPPORTS_SSLTCP); + + scoped_ptr<buzz::XmlElement> redirect_error( + MakeRedirectError(redirect_server.server.ToString())); + FireRedirect(redirect_error.get()); + + EXPECT_EQ(REDIRECTED, fake_delegate_.state()); + EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server)); +} + +// Fire a redirect with the host only and make sure the delegate gets +// the proper redirect server info with the default XMPP port. +TEST_F(SingleLoginAttemptTest, RedirectHostOnly) { + const ServerInformation redirect_server( + net::HostPortPair("example.com", kDefaultXmppPort), + SUPPORTS_SSLTCP); + + scoped_ptr<buzz::XmlElement> redirect_error( + MakeRedirectError(redirect_server.server.host())); + FireRedirect(redirect_error.get()); + + EXPECT_EQ(REDIRECTED, fake_delegate_.state()); + EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server)); +} + +// Fire a redirect with a zero port and make sure the delegate gets +// the proper redirect server info with the default XMPP port. +TEST_F(SingleLoginAttemptTest, RedirectZeroPort) { + const ServerInformation redirect_server( + net::HostPortPair("example.com", kDefaultXmppPort), + SUPPORTS_SSLTCP); + + scoped_ptr<buzz::XmlElement> redirect_error( + MakeRedirectError(redirect_server.server.host() + ":0")); + FireRedirect(redirect_error.get()); + + EXPECT_EQ(REDIRECTED, fake_delegate_.state()); + EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server)); +} + +// Fire a redirect with an invalid port and make sure the delegate +// gets the proper redirect server info with the default XMPP port. +TEST_F(SingleLoginAttemptTest, RedirectInvalidPort) { + const ServerInformation redirect_server( + net::HostPortPair("example.com", kDefaultXmppPort), + SUPPORTS_SSLTCP); + + scoped_ptr<buzz::XmlElement> redirect_error( + MakeRedirectError(redirect_server.server.host() + ":invalidport")); + FireRedirect(redirect_error.get()); + + EXPECT_EQ(REDIRECTED, fake_delegate_.state()); + EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server)); +} + +// Fire an empty redirect and make sure the delegate does not get a +// redirect. +TEST_F(SingleLoginAttemptTest, RedirectEmpty) { + scoped_ptr<buzz::XmlElement> redirect_error(MakeRedirectError(std::string())); + FireRedirect(redirect_error.get()); + EXPECT_EQ(IDLE, fake_delegate_.state()); +} + +// Fire a redirect with a missing text element and make sure the +// delegate does not get a redirect. +TEST_F(SingleLoginAttemptTest, RedirectMissingText) { + scoped_ptr<buzz::XmlElement> redirect_error(MakeRedirectError(std::string())); + redirect_error->RemoveChildAfter(redirect_error->FirstChild()); + FireRedirect(redirect_error.get()); + EXPECT_EQ(IDLE, fake_delegate_.state()); +} + +// Fire a redirect with a missing see-other-host element and make sure +// the delegate does not get a redirect. +TEST_F(SingleLoginAttemptTest, RedirectMissingSeeOtherHost) { + scoped_ptr<buzz::XmlElement> redirect_error(MakeRedirectError(std::string())); + redirect_error->RemoveChildAfter(NULL); + FireRedirect(redirect_error.get()); + EXPECT_EQ(IDLE, fake_delegate_.state()); +} + +// Fire 'Unauthorized' errors and make sure the delegate gets the +// OnCredentialsRejected() event. +TEST_F(SingleLoginAttemptTest, CredentialsRejected) { + attempt_->OnError(buzz::XmppEngine::ERROR_UNAUTHORIZED, 0, NULL); + EXPECT_EQ(CREDENTIALS_REJECTED, fake_delegate_.state()); +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/fake_push_client.cc b/chromium/jingle/notifier/listener/fake_push_client.cc new file mode 100644 index 00000000000..1106c932b7f --- /dev/null +++ b/chromium/jingle/notifier/listener/fake_push_client.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 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/listener/fake_push_client.h" + +#include "jingle/notifier/listener/push_client_observer.h" + +namespace notifier { + +FakePushClient::FakePushClient() : sent_pings_(0) {} + +FakePushClient::~FakePushClient() {} + +void FakePushClient::AddObserver(PushClientObserver* observer) { + observers_.AddObserver(observer); +} + +void FakePushClient::RemoveObserver(PushClientObserver* observer) { + observers_.RemoveObserver(observer); +} + +void FakePushClient::UpdateSubscriptions( + const SubscriptionList& subscriptions) { + subscriptions_ = subscriptions; +} + +void FakePushClient::UpdateCredentials( + const std::string& email, const std::string& token) { + email_ = email; + token_ = token; +} + +void FakePushClient::SendNotification(const Notification& notification) { + sent_notifications_.push_back(notification); +} + +void FakePushClient::SendPing() { + sent_pings_++; +} + +void FakePushClient::EnableNotifications() { + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsEnabled()); +} + +void FakePushClient::DisableNotifications( + NotificationsDisabledReason reason) { + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsDisabled(reason)); +} + +void FakePushClient::SimulateIncomingNotification( + const Notification& notification) { + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnIncomingNotification(notification)); +} + +const SubscriptionList& FakePushClient::subscriptions() const { + return subscriptions_; +} + +const std::string& FakePushClient::email() const { + return email_; +} + +const std::string& FakePushClient::token() const { + return token_; +} + +const std::vector<Notification>& FakePushClient::sent_notifications() const { + return sent_notifications_; +} + +int FakePushClient::sent_pings() const { + return sent_pings_; +} + +} // namespace notifier + diff --git a/chromium/jingle/notifier/listener/fake_push_client.h b/chromium/jingle/notifier/listener/fake_push_client.h new file mode 100644 index 00000000000..2c1340de88b --- /dev/null +++ b/chromium/jingle/notifier/listener/fake_push_client.h @@ -0,0 +1,62 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_FAKE_PUSH_CLIENT_H_ +#define JINGLE_NOTIFIER_LISTENER_FAKE_PUSH_CLIENT_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "jingle/notifier/listener/push_client.h" +#include "jingle/notifier/listener/push_client_observer.h" + +namespace notifier { + +// PushClient implementation that can be used for testing. +class FakePushClient : public PushClient { + public: + FakePushClient(); + virtual ~FakePushClient(); + + // PushClient implementation. + virtual void AddObserver(PushClientObserver* observer) OVERRIDE; + virtual void RemoveObserver(PushClientObserver* observer) OVERRIDE; + virtual void UpdateSubscriptions( + const SubscriptionList& subscriptions) OVERRIDE; + virtual void UpdateCredentials( + const std::string& email, const std::string& token) OVERRIDE; + virtual void SendNotification(const Notification& notification) OVERRIDE; + virtual void SendPing() OVERRIDE; + + // Triggers OnNotificationsEnabled on all observers. + void EnableNotifications(); + + // Triggers OnNotificationsDisabled on all observers. + void DisableNotifications(NotificationsDisabledReason reason); + + // Triggers OnIncomingNotification on all observers. + void SimulateIncomingNotification(const Notification& notification); + + const SubscriptionList& subscriptions() const; + const std::string& email() const; + const std::string& token() const; + const std::vector<Notification>& sent_notifications() const; + int sent_pings() const; + + private: + ObserverList<PushClientObserver> observers_; + SubscriptionList subscriptions_; + std::string email_; + std::string token_; + std::vector<Notification> sent_notifications_; + int sent_pings_; + + DISALLOW_COPY_AND_ASSIGN(FakePushClient); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_FAKE_PUSH_CLIENT_H_ diff --git a/chromium/jingle/notifier/listener/fake_push_client_observer.cc b/chromium/jingle/notifier/listener/fake_push_client_observer.cc new file mode 100644 index 00000000000..7b907d0fbed --- /dev/null +++ b/chromium/jingle/notifier/listener/fake_push_client_observer.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2012 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/listener/fake_push_client_observer.h" + +namespace notifier { + +FakePushClientObserver::FakePushClientObserver() + :last_notifications_disabled_reason_(DEFAULT_NOTIFICATION_ERROR) {} + +FakePushClientObserver::~FakePushClientObserver() {} + +void FakePushClientObserver::OnNotificationsEnabled() { + last_notifications_disabled_reason_ = NO_NOTIFICATION_ERROR; +} + +void FakePushClientObserver::OnNotificationsDisabled( + NotificationsDisabledReason reason) { + last_notifications_disabled_reason_ = reason; +} + +void FakePushClientObserver::OnIncomingNotification( + const Notification& notification) { + last_incoming_notification_ = notification; +} + +NotificationsDisabledReason +FakePushClientObserver::last_notifications_disabled_reason() const { + return last_notifications_disabled_reason_; +} + +const Notification& +FakePushClientObserver::last_incoming_notification() const { + return last_incoming_notification_; +} + +} // namespace notifier + diff --git a/chromium/jingle/notifier/listener/fake_push_client_observer.h b/chromium/jingle/notifier/listener/fake_push_client_observer.h new file mode 100644 index 00000000000..d3500213910 --- /dev/null +++ b/chromium/jingle/notifier/listener/fake_push_client_observer.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_FAKE_PUSH_CLIENT_OBSERVER_H_ +#define JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_FAKE_PUSH_CLIENT_OBSERVER_H_ + +#include "base/compiler_specific.h" +#include "jingle/notifier/listener/push_client_observer.h" + +namespace notifier { + +// PushClientObserver implementation that can be used for testing. +class FakePushClientObserver : public PushClientObserver { + public: + FakePushClientObserver(); + virtual ~FakePushClientObserver(); + + // PushClientObserver implementation. + virtual void OnNotificationsEnabled() OVERRIDE; + virtual void OnNotificationsDisabled( + NotificationsDisabledReason reason) OVERRIDE; + virtual void OnIncomingNotification( + const Notification& notification) OVERRIDE; + + NotificationsDisabledReason last_notifications_disabled_reason() const; + const Notification& last_incoming_notification() const; + + private: + NotificationsDisabledReason last_notifications_disabled_reason_; + Notification last_incoming_notification_; +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_FAKE_PUSH_CLIENT_OBSERVER_H_ diff --git a/chromium/jingle/notifier/listener/non_blocking_push_client.cc b/chromium/jingle/notifier/listener/non_blocking_push_client.cc new file mode 100644 index 00000000000..277cf4955ee --- /dev/null +++ b/chromium/jingle/notifier/listener/non_blocking_push_client.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2012 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/listener/non_blocking_push_client.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "jingle/notifier/listener/push_client_observer.h" + +namespace notifier { + +// All methods are called on the delegate thread unless specified +// otherwise. +class NonBlockingPushClient::Core + : public base::RefCountedThreadSafe<NonBlockingPushClient::Core>, + public PushClientObserver { + public: + // Called on the parent thread. + explicit Core( + const scoped_refptr<base::SingleThreadTaskRunner>& + delegate_task_runner, + const base::WeakPtr<NonBlockingPushClient>& parent_push_client); + + // Must be called after being created. + // + // This is separated out from the constructor since posting tasks + // from the constructor is dangerous. + void CreateOnDelegateThread( + const CreateBlockingPushClientCallback& + create_blocking_push_client_callback); + + // Must be called before being destroyed. + void DestroyOnDelegateThread(); + + void UpdateSubscriptions(const SubscriptionList& subscriptions); + void UpdateCredentials(const std::string& email, const std::string& token); + void SendNotification(const Notification& data); + void SendPing(); + + virtual void OnNotificationsEnabled() OVERRIDE; + virtual void OnNotificationsDisabled( + NotificationsDisabledReason reason) OVERRIDE; + virtual void OnIncomingNotification( + const Notification& notification) OVERRIDE; + virtual void OnPingResponse() OVERRIDE; + + private: + friend class base::RefCountedThreadSafe<NonBlockingPushClient::Core>; + + // Called on either the parent thread or the delegate thread. + virtual ~Core(); + + const scoped_refptr<base::SingleThreadTaskRunner> parent_task_runner_; + const scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_; + + const base::WeakPtr<NonBlockingPushClient> parent_push_client_; + scoped_ptr<PushClient> delegate_push_client_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +NonBlockingPushClient::Core::Core( + const scoped_refptr<base::SingleThreadTaskRunner>& delegate_task_runner, + const base::WeakPtr<NonBlockingPushClient>& parent_push_client) + : parent_task_runner_(base::MessageLoopProxy::current()), + delegate_task_runner_(delegate_task_runner), + parent_push_client_(parent_push_client) {} + +NonBlockingPushClient::Core::~Core() { + DCHECK(parent_task_runner_->BelongsToCurrentThread() || + delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(!delegate_push_client_.get()); +} + +void NonBlockingPushClient::Core::CreateOnDelegateThread( + const CreateBlockingPushClientCallback& + create_blocking_push_client_callback) { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(!delegate_push_client_.get()); + delegate_push_client_ = create_blocking_push_client_callback.Run(); + delegate_push_client_->AddObserver(this); +} + +void NonBlockingPushClient::Core::DestroyOnDelegateThread() { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(delegate_push_client_.get()); + delegate_push_client_->RemoveObserver(this); + delegate_push_client_.reset(); +} + +void NonBlockingPushClient::Core::UpdateSubscriptions( + const SubscriptionList& subscriptions) { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(delegate_push_client_.get()); + delegate_push_client_->UpdateSubscriptions(subscriptions); +} + +void NonBlockingPushClient::Core::UpdateCredentials( + const std::string& email, const std::string& token) { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(delegate_push_client_.get()); + delegate_push_client_->UpdateCredentials(email, token); +} + +void NonBlockingPushClient::Core::SendNotification( + const Notification& notification) { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(delegate_push_client_.get()); + delegate_push_client_->SendNotification(notification); +} + +void NonBlockingPushClient::Core::SendPing() { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + DCHECK(delegate_push_client_.get()); + delegate_push_client_->SendPing(); +} + +void NonBlockingPushClient::Core::OnNotificationsEnabled() { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + parent_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::OnNotificationsEnabled, + parent_push_client_)); +} + +void NonBlockingPushClient::Core::OnNotificationsDisabled( + NotificationsDisabledReason reason) { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + parent_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::OnNotificationsDisabled, + parent_push_client_, reason)); +} + +void NonBlockingPushClient::Core::OnIncomingNotification( + const Notification& notification) { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + parent_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::OnIncomingNotification, + parent_push_client_, notification)); +} + +void NonBlockingPushClient::Core::OnPingResponse() { + DCHECK(delegate_task_runner_->BelongsToCurrentThread()); + parent_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::OnPingResponse, + parent_push_client_)); +} + +NonBlockingPushClient::NonBlockingPushClient( + const scoped_refptr<base::SingleThreadTaskRunner>& delegate_task_runner, + const CreateBlockingPushClientCallback& + create_blocking_push_client_callback) + : weak_ptr_factory_(this), + delegate_task_runner_(delegate_task_runner), + core_(new Core(delegate_task_runner_, + weak_ptr_factory_.GetWeakPtr())) { + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::Core::CreateOnDelegateThread, + core_.get(), create_blocking_push_client_callback)); +} + +NonBlockingPushClient::~NonBlockingPushClient() { + DCHECK(thread_checker_.CalledOnValidThread()); + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::Core::DestroyOnDelegateThread, + core_.get())); +} + +void NonBlockingPushClient::AddObserver(PushClientObserver* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + observers_.AddObserver(observer); +} + +void NonBlockingPushClient::RemoveObserver(PushClientObserver* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + observers_.RemoveObserver(observer); +} + +void NonBlockingPushClient::UpdateSubscriptions( + const SubscriptionList& subscriptions) { + DCHECK(thread_checker_.CalledOnValidThread()); + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::Core::UpdateSubscriptions, + core_.get(), subscriptions)); +} + +void NonBlockingPushClient::UpdateCredentials( + const std::string& email, const std::string& token) { + DCHECK(thread_checker_.CalledOnValidThread()); + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::Core::UpdateCredentials, + core_.get(), email, token)); +} + +void NonBlockingPushClient::SendNotification( + const Notification& notification) { + DCHECK(thread_checker_.CalledOnValidThread()); + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::Core::SendNotification, core_.get(), + notification)); +} + +void NonBlockingPushClient::SendPing() { + DCHECK(thread_checker_.CalledOnValidThread()); + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NonBlockingPushClient::Core::SendPing, core_.get())); +} + +void NonBlockingPushClient::OnNotificationsEnabled() { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsEnabled()); +} + +void NonBlockingPushClient::OnNotificationsDisabled( + NotificationsDisabledReason reason) { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsDisabled(reason)); +} + +void NonBlockingPushClient::OnIncomingNotification( + const Notification& notification) { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnIncomingNotification(notification)); +} + +void NonBlockingPushClient::OnPingResponse() { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, OnPingResponse()); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/non_blocking_push_client.h b/chromium/jingle/notifier/listener/non_blocking_push_client.h new file mode 100644 index 00000000000..f5c6bc48e09 --- /dev/null +++ b/chromium/jingle/notifier/listener/non_blocking_push_client.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_PUSH_CLIENT_H_ +#define JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_PUSH_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "jingle/notifier/listener/push_client.h" +#include "jingle/notifier/listener/push_client_observer.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace notifier { + +// This class implements a PushClient that doesn't block; it delegates +// to another blocking PushClient on a separate thread. +// +// This class must be used on a single thread. +class NonBlockingPushClient : public PushClient { + public: + // The type for a function that creates a (blocking) PushClient. + // Will be called on the delegate task runner. + typedef base::Callback<scoped_ptr<PushClient>()> + CreateBlockingPushClientCallback; + + // Runs the given callback on the given task runner, and delegates + // to that PushClient. + explicit NonBlockingPushClient( + const scoped_refptr<base::SingleThreadTaskRunner>& delegate_task_runner, + const CreateBlockingPushClientCallback& + create_blocking_push_client_callback); + virtual ~NonBlockingPushClient(); + + // PushClient implementation. + virtual void AddObserver(PushClientObserver* observer) OVERRIDE; + virtual void RemoveObserver(PushClientObserver* observer) OVERRIDE; + virtual void UpdateSubscriptions( + const SubscriptionList& subscriptions) OVERRIDE; + virtual void UpdateCredentials( + const std::string& email, const std::string& token) OVERRIDE; + virtual void SendNotification(const Notification& notification) OVERRIDE; + virtual void SendPing() OVERRIDE; + + private: + class Core; + + void OnNotificationsEnabled(); + void OnNotificationsDisabled(NotificationsDisabledReason reason); + void OnIncomingNotification(const Notification& notification); + void OnPingResponse(); + + base::ThreadChecker thread_checker_; + base::WeakPtrFactory<NonBlockingPushClient> weak_ptr_factory_; + const scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_; + const scoped_refptr<Core> core_; + + ObserverList<PushClientObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(NonBlockingPushClient); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_PUSH_CLIENT_H_ diff --git a/chromium/jingle/notifier/listener/non_blocking_push_client_unittest.cc b/chromium/jingle/notifier/listener/non_blocking_push_client_unittest.cc new file mode 100644 index 00000000000..a26bba1428c --- /dev/null +++ b/chromium/jingle/notifier/listener/non_blocking_push_client_unittest.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2012 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/listener/non_blocking_push_client.h" + +#include <cstddef> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/notifier/base/fake_base_task.h" +#include "jingle/notifier/listener/fake_push_client.h" +#include "jingle/notifier/listener/fake_push_client_observer.h" +#include "jingle/notifier/listener/push_client_observer.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +namespace { + +class NonBlockingPushClientTest : public testing::Test { + protected: + NonBlockingPushClientTest() : fake_push_client_(NULL) {} + + virtual ~NonBlockingPushClientTest() {} + + virtual void SetUp() OVERRIDE { + push_client_.reset( + new NonBlockingPushClient( + base::MessageLoopProxy::current(), + base::Bind(&NonBlockingPushClientTest::CreateFakePushClient, + base::Unretained(this)))); + push_client_->AddObserver(&fake_observer_); + // Pump message loop to run CreateFakePushClient. + message_loop_.RunUntilIdle(); + } + + virtual void TearDown() OVERRIDE { + // Clear out any pending notifications before removing observers. + message_loop_.RunUntilIdle(); + push_client_->RemoveObserver(&fake_observer_); + push_client_.reset(); + // Then pump message loop to run + // NonBlockingPushClient::DestroyOnDelegateThread(). + message_loop_.RunUntilIdle(); + } + + scoped_ptr<PushClient> CreateFakePushClient() { + if (fake_push_client_) { + ADD_FAILURE(); + return scoped_ptr<PushClient>(); + } + fake_push_client_ = new FakePushClient(); + return scoped_ptr<PushClient>(fake_push_client_); + } + + base::MessageLoop message_loop_; + FakePushClientObserver fake_observer_; + scoped_ptr<NonBlockingPushClient> push_client_; + // Owned by |push_client_|. + FakePushClient* fake_push_client_; +}; + +// Make sure UpdateSubscriptions() gets delegated properly. +TEST_F(NonBlockingPushClientTest, UpdateSubscriptions) { + SubscriptionList subscriptions(10); + subscriptions[0].channel = "channel"; + subscriptions[9].from = "from"; + + push_client_->UpdateSubscriptions(subscriptions); + EXPECT_TRUE(fake_push_client_->subscriptions().empty()); + message_loop_.RunUntilIdle(); + EXPECT_TRUE( + SubscriptionListsEqual( + fake_push_client_->subscriptions(), subscriptions)); +} + +// Make sure UpdateCredentials() gets delegated properly. +TEST_F(NonBlockingPushClientTest, UpdateCredentials) { + const char kEmail[] = "foo@bar.com"; + const char kToken[] = "baz"; + + push_client_->UpdateCredentials(kEmail, kToken); + EXPECT_TRUE(fake_push_client_->email().empty()); + EXPECT_TRUE(fake_push_client_->token().empty()); + message_loop_.RunUntilIdle(); + EXPECT_EQ(kEmail, fake_push_client_->email()); + EXPECT_EQ(kToken, fake_push_client_->token()); +} + +Notification MakeTestNotification() { + Notification notification; + notification.channel = "channel"; + notification.recipients.resize(10); + notification.recipients[0].to = "to"; + notification.recipients[9].user_specific_data = "user_specific_data"; + notification.data = "data"; + return notification; +} + +// Make sure SendNotification() gets delegated properly. +TEST_F(NonBlockingPushClientTest, SendNotification) { + const Notification notification = MakeTestNotification(); + + push_client_->SendNotification(notification); + EXPECT_TRUE(fake_push_client_->sent_notifications().empty()); + message_loop_.RunUntilIdle(); + ASSERT_EQ(1u, fake_push_client_->sent_notifications().size()); + EXPECT_TRUE( + fake_push_client_->sent_notifications()[0].Equals(notification)); +} + +// Make sure SendPing() gets delegated properly. +TEST_F(NonBlockingPushClientTest, SendPing) { + push_client_->SendPing(); + EXPECT_EQ(0, fake_push_client_->sent_pings()); + message_loop_.RunUntilIdle(); + ASSERT_EQ(1, fake_push_client_->sent_pings()); +} + +// Make sure notification state changes get propagated back to the +// parent. +TEST_F(NonBlockingPushClientTest, NotificationStateChange) { + EXPECT_EQ(DEFAULT_NOTIFICATION_ERROR, + fake_observer_.last_notifications_disabled_reason()); + fake_push_client_->EnableNotifications(); + message_loop_.RunUntilIdle(); + EXPECT_EQ(NO_NOTIFICATION_ERROR, + fake_observer_.last_notifications_disabled_reason()); + fake_push_client_->DisableNotifications( + NOTIFICATION_CREDENTIALS_REJECTED); + message_loop_.RunUntilIdle(); + EXPECT_EQ(NOTIFICATION_CREDENTIALS_REJECTED, + fake_observer_.last_notifications_disabled_reason()); +} + +// Make sure incoming notifications get propagated back to the parent. +TEST_F(NonBlockingPushClientTest, OnIncomingNotification) { + const Notification notification = MakeTestNotification(); + + fake_push_client_->SimulateIncomingNotification(notification); + message_loop_.RunUntilIdle(); + EXPECT_TRUE( + fake_observer_.last_incoming_notification().Equals(notification)); +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/notification_constants.cc b/chromium/jingle/notifier/listener/notification_constants.cc new file mode 100644 index 00000000000..28edc84ada4 --- /dev/null +++ b/chromium/jingle/notifier/listener/notification_constants.cc @@ -0,0 +1,11 @@ +// 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/listener/notification_constants.h" + +namespace notifier { + +const char kPushNotificationsNamespace[] = "google:push"; + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/notification_constants.h b/chromium/jingle/notifier/listener/notification_constants.h new file mode 100644 index 00000000000..7b0bbfa3963 --- /dev/null +++ b/chromium/jingle/notifier/listener/notification_constants.h @@ -0,0 +1,14 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_ +#define JINGLE_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_ + +namespace notifier { + +extern const char kPushNotificationsNamespace[]; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_ diff --git a/chromium/jingle/notifier/listener/notification_defines.cc b/chromium/jingle/notifier/listener/notification_defines.cc new file mode 100644 index 00000000000..f4f3e408a5b --- /dev/null +++ b/chromium/jingle/notifier/listener/notification_defines.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2012 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/listener/notification_defines.h" + +#include <cstddef> + +#include "base/json/string_escape.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/values.h" + +namespace notifier { + +Subscription::Subscription() {} +Subscription::~Subscription() {} + +bool Subscription::Equals(const Subscription& other) const { + return channel == other.channel && from == other.from; +} + +namespace { + +template <typename T> +bool ListsEqual(const T& t1, const T& t2) { + if (t1.size() != t2.size()) { + return false; + } + for (size_t i = 0; i < t1.size(); ++i) { + if (!t1[i].Equals(t2[i])) { + return false; + } + } + return true; +} + +} // namespace + +bool SubscriptionListsEqual(const SubscriptionList& subscriptions1, + const SubscriptionList& subscriptions2) { + return ListsEqual(subscriptions1, subscriptions2); +} + +Recipient::Recipient() {} +Recipient::~Recipient() {} + +bool Recipient::Equals(const Recipient& other) const { + return to == other.to && user_specific_data == other.user_specific_data; +} + +bool RecipientListsEqual(const RecipientList& recipients1, + const RecipientList& recipients2) { + return ListsEqual(recipients1, recipients2); +} + +Notification::Notification() {} +Notification::~Notification() {} + +bool Notification::Equals(const Notification& other) const { + return + channel == other.channel && + data == other.data && + RecipientListsEqual(recipients, other.recipients); +} + +std::string Notification::ToString() const { + // |channel| or |data| could hold binary data, so use GetDoubleQuotedJson() + // to escape them. + const std::string& printable_channel = base::GetDoubleQuotedJson(channel); + const std::string& printable_data = base::GetDoubleQuotedJson(data); + return + "{ channel: " + printable_channel + ", data: " + printable_data + " }"; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/notification_defines.h b/chromium/jingle/notifier/listener/notification_defines.h new file mode 100644 index 00000000000..5d4a0c83dc1 --- /dev/null +++ b/chromium/jingle/notifier/listener/notification_defines.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_ +#define JINGLE_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_ + +#include <string> +#include <vector> + +namespace notifier { + +struct Subscription { + Subscription(); + ~Subscription(); + bool Equals(const Subscription& other) const; + + // The name of the channel to subscribe to; usually but not always + // a URL. + std::string channel; + // A sender, which could be a domain or a bare JID, from which we + // will accept pushes. + std::string from; +}; + +typedef std::vector<Subscription> SubscriptionList; + +bool SubscriptionListsEqual(const SubscriptionList& subscriptions1, + const SubscriptionList& subscriptions2); + +// A structure representing a <recipient/> block within a push message. +struct Recipient { + Recipient(); + ~Recipient(); + bool Equals(const Recipient& other) const; + + // The bare jid of the recipient. + std::string to; + // User-specific data for the recipient. + std::string user_specific_data; +}; + +typedef std::vector<Recipient> RecipientList; + +bool RecipientListsEqual(const RecipientList& recipients1, + const RecipientList& recipients2); + +struct Notification { + Notification(); + ~Notification(); + + // The channel the notification is coming in on. + std::string channel; + // Recipients for this notification (may be empty). + RecipientList recipients; + // The notification data payload. + std::string data; + + bool Equals(const Notification& other) const; + std::string ToString() const; +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_ diff --git a/chromium/jingle/notifier/listener/notification_defines_unittest.cc b/chromium/jingle/notifier/listener/notification_defines_unittest.cc new file mode 100644 index 00000000000..389f3ccdf7a --- /dev/null +++ b/chromium/jingle/notifier/listener/notification_defines_unittest.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 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 <string> + +#include "base/strings/string_util.h" +#include "jingle/notifier/listener/notification_defines.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { +namespace { + +class NotificationTest : public testing::Test {}; + +// Create a notification with binary data in the data field. +// Converting it to string shouldn't cause a crash. +TEST_F(NotificationTest, BinaryData) { + const char kNonUtf8Data[] = { '\xff', '\0' }; + EXPECT_FALSE(IsStringUTF8(kNonUtf8Data)); + Notification notification; + notification.data = kNonUtf8Data; + EXPECT_EQ("{ channel: \"\", data: \"\\u00FF\" }", notification.ToString()); +} + +} // namespace +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_client.cc b/chromium/jingle/notifier/listener/push_client.cc new file mode 100644 index 00000000000..f18ed52d30b --- /dev/null +++ b/chromium/jingle/notifier/listener/push_client.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2012 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/listener/push_client.h" + +#include <cstddef> + +#include "base/bind.h" +#include "base/single_thread_task_runner.h" +#include "jingle/notifier/listener/non_blocking_push_client.h" +#include "jingle/notifier/listener/xmpp_push_client.h" + +namespace notifier { + +PushClient::~PushClient() {} + +namespace { + +scoped_ptr<PushClient> CreateXmppPushClient( + const NotifierOptions& notifier_options) { + return scoped_ptr<PushClient>(new XmppPushClient(notifier_options)); +} + +} // namespace + +scoped_ptr<PushClient> PushClient::CreateDefault( + const NotifierOptions& notifier_options) { + return scoped_ptr<PushClient>(new NonBlockingPushClient( + notifier_options.request_context_getter->GetNetworkTaskRunner(), + base::Bind(&CreateXmppPushClient, notifier_options))); +} + +scoped_ptr<PushClient> PushClient::CreateDefaultOnIOThread( + const NotifierOptions& notifier_options) { + CHECK(notifier_options.request_context_getter->GetNetworkTaskRunner()-> + BelongsToCurrentThread()); + return CreateXmppPushClient(notifier_options); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_client.h b/chromium/jingle/notifier/listener/push_client.h new file mode 100644 index 00000000000..fd673373e20 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_client.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_PUSH_CLIENT_H_ +#define JINGLE_NOTIFIER_LISTENER_PUSH_CLIENT_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "jingle/notifier/listener/notification_defines.h" + +namespace notifier { + +struct NotifierOptions; +class PushClientObserver; + +// A PushClient is an interface for classes that implement a push +// mechanism, where a client can push notifications to and receive +// notifications from other clients. +class PushClient { + public: + virtual ~PushClient(); + + // Creates a default non-blocking PushClient implementation from the + // given options. + static scoped_ptr<PushClient> CreateDefault( + const NotifierOptions& notifier_options); + + // Creates a default blocking PushClient implementation from the + // given options. Must be called from the IO thread (according to + // |notifier_options|). + static scoped_ptr<PushClient> CreateDefaultOnIOThread( + const NotifierOptions& notifier_options); + + // Manage the list of observers for incoming notifications. + virtual void AddObserver(PushClientObserver* observer) = 0; + virtual void RemoveObserver(PushClientObserver* observer) = 0; + + // Implementors are required to have this take effect only on the + // next (re-)connection. Therefore, clients should call this before + // UpdateCredentials(). + virtual void UpdateSubscriptions(const SubscriptionList& subscriptions) = 0; + + // If not connected, connects with the given credentials. If + // already connected, the next connection attempt will use the given + // credentials. + virtual void UpdateCredentials( + const std::string& email, const std::string& token) = 0; + + // Sends a notification (with no reliability guarantees). + virtual void SendNotification(const Notification& notification) = 0; + + // Sends a ping (with no reliability guarantees). + virtual void SendPing() = 0; +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_PUSH_CLIENT_H_ diff --git a/chromium/jingle/notifier/listener/push_client_observer.cc b/chromium/jingle/notifier/listener/push_client_observer.cc new file mode 100644 index 00000000000..a958eea1abd --- /dev/null +++ b/chromium/jingle/notifier/listener/push_client_observer.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 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/listener/push_client_observer.h" + +namespace notifier { + +PushClientObserver::~PushClientObserver() {} + +void PushClientObserver::OnPingResponse() {} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_client_observer.h b/chromium/jingle/notifier/listener/push_client_observer.h new file mode 100644 index 00000000000..d153ff0cc97 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_client_observer.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_PUSH_CLIENT_OBSERVER_H_ +#define JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_PUSH_CLIENT_OBSERVER_H_ + +#include "jingle/notifier/listener/notification_defines.h" + +namespace notifier { + +enum NotificationsDisabledReason { + // There is an underlying transient problem (e.g., network- or + // XMPP-related). + TRANSIENT_NOTIFICATION_ERROR, + DEFAULT_NOTIFICATION_ERROR = TRANSIENT_NOTIFICATION_ERROR, + // Our credentials have been rejected. + NOTIFICATION_CREDENTIALS_REJECTED, + // No error (useful for avoiding keeping a separate bool for + // notifications enabled/disabled). + NO_NOTIFICATION_ERROR +}; + +// A PushClientObserver is notified when notifications are enabled or +// disabled, and when a notification is received. +class PushClientObserver { + protected: + virtual ~PushClientObserver(); + + public: + // Called when notifications are enabled. + virtual void OnNotificationsEnabled() = 0; + + // Called when notifications are disabled, with the reason (not + // equal to NO_ERROR) in |reason|. + virtual void OnNotificationsDisabled( + NotificationsDisabledReason reason) = 0; + + // Called when a notification is received. The details of the + // notification are in |notification|. + virtual void OnIncomingNotification(const Notification& notification) = 0; + + // Called when a ping response is received. Default implementation does + // nothing. + virtual void OnPingResponse(); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_NON_BLOCKING_PUSH_CLIENT_OBSERVER_H_ diff --git a/chromium/jingle/notifier/listener/push_client_unittest.cc b/chromium/jingle/notifier/listener/push_client_unittest.cc new file mode 100644 index 00000000000..0357cf260f4 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_client_unittest.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2012 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/listener/push_client.h" + +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "jingle/notifier/base/notifier_options.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +namespace { + +class PushClientTest : public testing::Test { + protected: + PushClientTest() { + notifier_options_.request_context_getter = + new net::TestURLRequestContextGetter( + message_loop_.message_loop_proxy()); + } + + virtual ~PushClientTest() {} + + // The sockets created by the XMPP code expect an IO loop. + base::MessageLoopForIO message_loop_; + NotifierOptions notifier_options_; +}; + +// Make sure calling CreateDefault on the IO thread doesn't blow up. +TEST_F(PushClientTest, CreateDefaultOnIOThread) { + const scoped_ptr<PushClient> push_client( + PushClient::CreateDefault(notifier_options_)); +} + +// Make sure calling CreateDefault on a non-IO thread doesn't blow up. +TEST_F(PushClientTest, CreateDefaultOffIOThread) { + base::Thread thread("Non-IO thread"); + EXPECT_TRUE(thread.Start()); + thread.message_loop()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&PushClient::CreateDefault), + notifier_options_)); + thread.Stop(); +} + +// Make sure calling CreateDefaultOnIOThread on the IO thread doesn't +// blow up. +TEST_F(PushClientTest, CreateDefaultOnIOThreadOnIOThread) { + const scoped_ptr<PushClient> push_client( + PushClient::CreateDefaultOnIOThread(notifier_options_)); +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_notifications_listen_task.cc b/chromium/jingle/notifier/listener/push_notifications_listen_task.cc new file mode 100644 index 00000000000..2e6b2ca0429 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_listen_task.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2011 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/listener/push_notifications_listen_task.h" + +#include "base/base64.h" +#include "base/logging.h" +#include "jingle/notifier/listener/notification_constants.h" +#include "jingle/notifier/listener/notification_defines.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/base/task.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +PushNotificationsListenTask::Delegate::~Delegate() { +} + +PushNotificationsListenTask::PushNotificationsListenTask( + buzz::XmppTaskParentInterface* parent, Delegate* delegate) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_TYPE), + delegate_(delegate) { + DCHECK(delegate_); +} + +PushNotificationsListenTask::~PushNotificationsListenTask() { +} + +int PushNotificationsListenTask::ProcessStart() { + return STATE_RESPONSE; +} + +int PushNotificationsListenTask::ProcessResponse() { + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + + DVLOG(1) << "Received stanza " << XmlElementToString(*stanza); + + // The push notifications service does not need us to acknowledge receipt of + // the notification to the buzz server. + + // TODO(sanjeevr): Write unittests to cover this. + // Extract the service URL and service-specific data from the stanza. + // Note that we treat the channel name as service URL. + // The response stanza has the following format. + // <message from="{url or bare jid}" to={full jid}> + // <push xmlns="google:push" channel={channel name}> + // <recipient to={bare jid}>{base-64 encoded data}</recipient> + // <data>{base-64 encoded data}</data> + // </push> + // </message> + + const buzz::QName kQnPush(kPushNotificationsNamespace, "push"); + const buzz::QName kQnChannel(buzz::STR_EMPTY, "channel"); + const buzz::QName kQnData(kPushNotificationsNamespace, "data"); + + const buzz::XmlElement* push_element = stanza->FirstNamed(kQnPush); + if (push_element) { + Notification notification; + notification.channel = push_element->Attr(kQnChannel); + const buzz::XmlElement* data_element = push_element->FirstNamed(kQnData); + if (data_element) { + const std::string& base64_encoded_data = data_element->BodyText(); + if (!base::Base64Decode(base64_encoded_data, ¬ification.data)) { + LOG(WARNING) << "Could not base64-decode " << base64_encoded_data; + } + } else { + LOG(WARNING) << "No data element found in push element " + << XmlElementToString(*push_element); + } + DVLOG(1) << "Received notification " << notification.ToString(); + delegate_->OnNotificationReceived(notification); + } else { + LOG(WARNING) << "No push element found in stanza " + << XmlElementToString(*stanza); + } + return STATE_RESPONSE; +} + +bool PushNotificationsListenTask::HandleStanza(const buzz::XmlElement* stanza) { + if (IsValidNotification(stanza)) { + QueueStanza(stanza); + return true; + } + return false; +} + +bool PushNotificationsListenTask::IsValidNotification( + const buzz::XmlElement* stanza) { + // We don't do much validation here, just check if the stanza is a message + // stanza. + return (stanza->Name() == buzz::QN_MESSAGE); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_notifications_listen_task.h b/chromium/jingle/notifier/listener/push_notifications_listen_task.h new file mode 100644 index 00000000000..62d408b2ed2 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_listen_task.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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. +// +// This class listens for notifications from the Google Push notifications +// service, and signals when they arrive. It checks all incoming stanzas to +// see if they look like notifications, and filters out those which are not +// valid. +// +// The task is deleted automatically by the buzz::XmppClient. This occurs in the +// destructor of TaskRunner, which is a superclass of buzz::XmppClient. + +#ifndef JINGLE_NOTIFIER_PUSH_NOTIFICATIONS_LISTENER_LISTEN_TASK_H_ +#define JINGLE_NOTIFIER_PUSH_NOTIFICATIONS_LISTENER_LISTEN_TASK_H_ + +#include "base/compiler_specific.h" +#include "talk/xmpp/xmpptask.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { + +struct Notification; + +class PushNotificationsListenTask : public buzz::XmppTask { + public: + class Delegate { + public: + virtual void OnNotificationReceived(const Notification& notification) = 0; + + protected: + virtual ~Delegate(); + }; + + PushNotificationsListenTask(buzz::XmppTaskParentInterface* parent, + Delegate* delegate); + virtual ~PushNotificationsListenTask(); + + // Overriden from buzz::XmppTask. + virtual int ProcessStart() OVERRIDE; + virtual int ProcessResponse() OVERRIDE; + virtual bool HandleStanza(const buzz::XmlElement* stanza) OVERRIDE; + + private: + bool IsValidNotification(const buzz::XmlElement* stanza); + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(PushNotificationsListenTask); +}; + +typedef PushNotificationsListenTask::Delegate + PushNotificationsListenTaskDelegate; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_PUSH_NOTIFICATIONS_LISTENER_LISTEN_TASK_H_ diff --git a/chromium/jingle/notifier/listener/push_notifications_send_update_task.cc b/chromium/jingle/notifier/listener/push_notifications_send_update_task.cc new file mode 100644 index 00000000000..4e0c12782ad --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_send_update_task.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2012 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/listener/push_notifications_send_update_task.h" + +#include <string> + +#include "base/base64.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "jingle/notifier/listener/notification_constants.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/xmppclient.h" + +namespace notifier { + +PushNotificationsSendUpdateTask::PushNotificationsSendUpdateTask( + buzz::XmppTaskParentInterface* parent, const Notification& notification) + : XmppTask(parent), notification_(notification) {} + +PushNotificationsSendUpdateTask::~PushNotificationsSendUpdateTask() {} + +int PushNotificationsSendUpdateTask::ProcessStart() { + scoped_ptr<buzz::XmlElement> stanza( + MakeUpdateMessage(notification_, + GetClient()->jid().BareJid())); + DVLOG(1) << "Sending notification " << notification_.ToString() + << " as stanza " << XmlElementToString(*stanza); + if (SendStanza(stanza.get()) != buzz::XMPP_RETURN_OK) { + DLOG(WARNING) << "Could not send stanza " << XmlElementToString(*stanza); + } + return STATE_DONE; +} + +buzz::XmlElement* PushNotificationsSendUpdateTask::MakeUpdateMessage( + const Notification& notification, + const buzz::Jid& to_jid_bare) { + DCHECK(to_jid_bare.IsBare()); + const buzz::QName kQnPush(kPushNotificationsNamespace, "push"); + const buzz::QName kQnChannel(buzz::STR_EMPTY, "channel"); + const buzz::QName kQnData(kPushNotificationsNamespace, "data"); + const buzz::QName kQnRecipient(kPushNotificationsNamespace, "recipient"); + + // Create our update stanza. The message is constructed as: + // <message from='{full jid}' to='{bare jid}' type='headline'> + // <push xmlns='google:push' channel='{channel}'> + // [<recipient to='{bare jid}'>{base-64 encoded data}</data>]* + // <data>{base-64 encoded data}</data> + // </push> + // </message> + + buzz::XmlElement* message = new buzz::XmlElement(buzz::QN_MESSAGE); + message->AddAttr(buzz::QN_TO, to_jid_bare.Str()); + message->AddAttr(buzz::QN_TYPE, "headline"); + + buzz::XmlElement* push = new buzz::XmlElement(kQnPush, true); + push->AddAttr(kQnChannel, notification.channel); + message->AddElement(push); + + const RecipientList& recipients = notification.recipients; + for (size_t i = 0; i < recipients.size(); ++i) { + const Recipient& recipient = recipients[i]; + buzz::XmlElement* recipient_element = + new buzz::XmlElement(kQnRecipient, true); + push->AddElement(recipient_element); + recipient_element->AddAttr(buzz::QN_TO, recipient.to); + if (!recipient.user_specific_data.empty()) { + std::string base64_data; + if (!base::Base64Encode(recipient.user_specific_data, &base64_data)) { + DLOG(WARNING) << "Could not encode data " + << recipient.user_specific_data; + } else { + recipient_element->SetBodyText(base64_data); + } + } + } + + buzz::XmlElement* data = new buzz::XmlElement(kQnData, true); + std::string base64_data; + if (!base::Base64Encode(notification.data, &base64_data)) { + DLOG(WARNING) << "Could not encode data " << notification.data; + } + data->SetBodyText(base64_data); + push->AddElement(data); + + return message; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_notifications_send_update_task.h b/chromium/jingle/notifier/listener/push_notifications_send_update_task.h new file mode 100644 index 00000000000..36bb3c35540 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_send_update_task.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011 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. +// +// Methods for sending the update stanza to notify peers via xmpp. + +#ifndef JINGLE_NOTIFIER_LISTENER_PUSH_NOTIFICATIONS_SEND_UPDATE_TASK_H_ +#define JINGLE_NOTIFIER_LISTENER_PUSH_NOTIFICATIONS_SEND_UPDATE_TASK_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "jingle/notifier/listener/notification_defines.h" +#include "talk/xmpp/xmpptask.h" + +namespace buzz { +class Jid; +class XmlElement; +} // namespace + +namespace notifier { + +class PushNotificationsSendUpdateTask : public buzz::XmppTask { + public: + PushNotificationsSendUpdateTask( + buzz::XmppTaskParentInterface* parent, const Notification& notification); + virtual ~PushNotificationsSendUpdateTask(); + + // Overridden from buzz::XmppTask. + virtual int ProcessStart() OVERRIDE; + + private: + // Allocates and constructs an buzz::XmlElement containing the update stanza. + static buzz::XmlElement* MakeUpdateMessage( + const Notification& notification, const buzz::Jid& to_jid_bare); + + const Notification notification_; + + FRIEND_TEST_ALL_PREFIXES(PushNotificationsSendUpdateTaskTest, + MakeUpdateMessage); + + DISALLOW_COPY_AND_ASSIGN(PushNotificationsSendUpdateTask); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_PUSH_NOTIFICATIONS_SEND_UPDATE_TASK_H_ diff --git a/chromium/jingle/notifier/listener/push_notifications_send_update_task_unittest.cc b/chromium/jingle/notifier/listener/push_notifications_send_update_task_unittest.cc new file mode 100644 index 00000000000..c57bd93c2da --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_send_update_task_unittest.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2011 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/listener/push_notifications_send_update_task.h" + +#include "base/base64.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/stringprintf.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/xmpp/jid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { + +class PushNotificationsSendUpdateTaskTest : public testing::Test { + public: + PushNotificationsSendUpdateTaskTest() : to_jid_bare_("to@jid.com") { + EXPECT_EQ(to_jid_bare_.Str(), to_jid_bare_.BareJid().Str()); + } + + protected: + const buzz::Jid to_jid_bare_; + + private: + DISALLOW_COPY_AND_ASSIGN(PushNotificationsSendUpdateTaskTest); +}; + +TEST_F(PushNotificationsSendUpdateTaskTest, MakeUpdateMessage) { + Notification notification; + notification.channel = "test_channel"; + notification.data = "test_data"; + + std::string base64_data; + EXPECT_TRUE(base::Base64Encode(notification.data, &base64_data)); + + scoped_ptr<buzz::XmlElement> message( + PushNotificationsSendUpdateTask::MakeUpdateMessage( + notification, to_jid_bare_)); + + std::string expected_xml_string = + base::StringPrintf( + "<cli:message to=\"%s\" type=\"headline\" " + "xmlns:cli=\"jabber:client\">" + "<push xmlns=\"google:push\" channel=\"%s\">" + "<data xmlns=\"google:push\">%s</data>" + "</push>" + "</cli:message>", + to_jid_bare_.Str().c_str(), notification.channel.c_str(), + base64_data.c_str()); + EXPECT_EQ(expected_xml_string, XmlElementToString(*message)); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_notifications_subscribe_task.cc b/chromium/jingle/notifier/listener/push_notifications_subscribe_task.cc new file mode 100644 index 00000000000..b069c9e4fc3 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_subscribe_task.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2012 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/listener/push_notifications_subscribe_task.h" + +#include <string> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "jingle/notifier/listener/notification_constants.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/base/task.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +PushNotificationsSubscribeTask::PushNotificationsSubscribeTask( + buzz::XmppTaskParentInterface* parent, + const SubscriptionList& subscriptions, + Delegate* delegate) + : XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + subscriptions_(subscriptions), delegate_(delegate) { +} + +PushNotificationsSubscribeTask::~PushNotificationsSubscribeTask() { +} + +bool PushNotificationsSubscribeTask::HandleStanza( + const buzz::XmlElement* stanza) { + if (!MatchResponseIq(stanza, GetClient()->jid().BareJid(), task_id())) + return false; + QueueStanza(stanza); + return true; +} + +int PushNotificationsSubscribeTask::ProcessStart() { + DVLOG(1) << "Push notifications: Subscription task started."; + scoped_ptr<buzz::XmlElement> iq_stanza( + MakeSubscriptionMessage(subscriptions_, GetClient()->jid(), + task_id())); + DVLOG(1) << "Push notifications: Subscription stanza: " + << XmlElementToString(*iq_stanza.get()); + + if (SendStanza(iq_stanza.get()) != buzz::XMPP_RETURN_OK) { + if (delegate_) + delegate_->OnSubscriptionError(); + return STATE_ERROR; + } + return STATE_RESPONSE; +} + +int PushNotificationsSubscribeTask::ProcessResponse() { + DVLOG(1) << "Push notifications: Subscription response received."; + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + DVLOG(1) << "Push notifications: Subscription response: " + << XmlElementToString(*stanza); + // We've received a response to our subscription request. + if (stanza->HasAttr(buzz::QN_TYPE) && + stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) { + if (delegate_) + delegate_->OnSubscribed(); + return STATE_DONE; + } + // An error response was received. + if (delegate_) + delegate_->OnSubscriptionError(); + return STATE_ERROR; +} + +buzz::XmlElement* PushNotificationsSubscribeTask::MakeSubscriptionMessage( + const SubscriptionList& subscriptions, + const buzz::Jid& jid, const std::string& task_id) { + DCHECK(jid.IsFull()); + const buzz::QName kQnSubscribe( + kPushNotificationsNamespace, "subscribe"); + + // Create the subscription stanza using the notifications protocol. + // <iq from={full_jid} to={bare_jid} type="set" id={id}> + // <subscribe xmlns="google:push"> + // <item channel={channel_name} from={domain_name or bare_jid}/> + // <item channel={channel_name2} from={domain_name or bare_jid}/> + // <item channel={channel_name3} from={domain_name or bare_jid}/> + // </subscribe> + // </iq> + buzz::XmlElement* iq = MakeIq(buzz::STR_SET, jid.BareJid(), task_id); + buzz::XmlElement* subscribe = new buzz::XmlElement(kQnSubscribe, true); + iq->AddElement(subscribe); + + for (SubscriptionList::const_iterator iter = + subscriptions.begin(); iter != subscriptions.end(); ++iter) { + buzz::XmlElement* item = new buzz::XmlElement( + buzz::QName(kPushNotificationsNamespace, "item")); + item->AddAttr(buzz::QName(buzz::STR_EMPTY, "channel"), + iter->channel.c_str()); + item->AddAttr(buzz::QN_FROM, iter->from.c_str()); + subscribe->AddElement(item); + } + return iq; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/push_notifications_subscribe_task.h b/chromium/jingle/notifier/listener/push_notifications_subscribe_task.h new file mode 100644 index 00000000000..4c34740144e --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_subscribe_task.h @@ -0,0 +1,60 @@ +// Copyright (c) 2011 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. +// +// This class handles subscribing to the new Google push notifications. + +#ifndef JINGLE_NOTIFIER_LISTENER_PUSH_NOTIFICATIONS_SUBSCRIBE_TASK_H_ +#define JINGLE_NOTIFIER_LISTENER_PUSH_NOTIFICATIONS_SUBSCRIBE_TASK_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "jingle/notifier/listener/notification_defines.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/xmpptask.h" + +namespace notifier { +class PushNotificationsSubscribeTask : public buzz::XmppTask { + public: + class Delegate { + public: + virtual ~Delegate() {} + virtual void OnSubscribed() = 0; + virtual void OnSubscriptionError() = 0; + }; + + PushNotificationsSubscribeTask(buzz::XmppTaskParentInterface* parent, + const SubscriptionList& subscriptions, + Delegate* delegate); + virtual ~PushNotificationsSubscribeTask(); + + // Overridden from XmppTask. + virtual int ProcessStart() OVERRIDE; + virtual int ProcessResponse() OVERRIDE; + virtual bool HandleStanza(const buzz::XmlElement* stanza) OVERRIDE; + + private: + // Assembles an Xmpp stanza which can be sent to subscribe to notifications. + static buzz::XmlElement* MakeSubscriptionMessage( + const SubscriptionList& subscriptions, + const buzz::Jid& jid, const std::string& task_id); + + SubscriptionList subscriptions_; + Delegate* delegate_; + + FRIEND_TEST_ALL_PREFIXES(PushNotificationsSubscribeTaskTest, + MakeSubscriptionMessage); + + DISALLOW_COPY_AND_ASSIGN(PushNotificationsSubscribeTask); +}; + +typedef PushNotificationsSubscribeTask::Delegate + PushNotificationsSubscribeTaskDelegate; + + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_PUSH_NOTIFICATIONS_SUBSCRIBE_TASK_H_ diff --git a/chromium/jingle/notifier/listener/push_notifications_subscribe_task_unittest.cc b/chromium/jingle/notifier/listener/push_notifications_subscribe_task_unittest.cc new file mode 100644 index 00000000000..32522f21a77 --- /dev/null +++ b/chromium/jingle/notifier/listener/push_notifications_subscribe_task_unittest.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2011 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/listener/push_notifications_subscribe_task.h" + +#include "base/memory/scoped_ptr.h" +#include "base/strings/stringprintf.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/xmpp/jid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { + +class PushNotificationsSubscribeTaskTest : public testing::Test { + public: + PushNotificationsSubscribeTaskTest() + : jid_("to@jid.com/test123"), task_id_("taskid") { + EXPECT_NE(jid_.Str(), jid_.BareJid().Str()); + } + + protected: + const buzz::Jid jid_; + const std::string task_id_; + + private: + DISALLOW_COPY_AND_ASSIGN(PushNotificationsSubscribeTaskTest); +}; + +TEST_F(PushNotificationsSubscribeTaskTest, MakeSubscriptionMessage) { + SubscriptionList subscriptions; + + Subscription subscription; + subscription.channel = "test_channel1"; + subscription.from = "from.test.com"; + subscriptions.push_back(subscription); + subscription.channel = "test_channel2"; + subscription.from = "from.test2.com"; + subscriptions.push_back(subscription); + scoped_ptr<buzz::XmlElement> message( + PushNotificationsSubscribeTask::MakeSubscriptionMessage( + subscriptions, jid_, task_id_)); + std::string expected_xml_string = + base::StringPrintf( + "<cli:iq type=\"set\" to=\"%s\" id=\"%s\" " + "xmlns:cli=\"jabber:client\">" + "<subscribe xmlns=\"google:push\">" + "<item channel=\"test_channel1\" from=\"from.test.com\"/>" + "<item channel=\"test_channel2\" from=\"from.test2.com\"/>" + "</subscribe>" + "</cli:iq>", + jid_.BareJid().Str().c_str(), task_id_.c_str()); + + EXPECT_EQ(expected_xml_string, XmlElementToString(*message)); +} + +} // namespace notifier + diff --git a/chromium/jingle/notifier/listener/send_ping_task.cc b/chromium/jingle/notifier/listener/send_ping_task.cc new file mode 100644 index 00000000000..90b7196d7f3 --- /dev/null +++ b/chromium/jingle/notifier/listener/send_ping_task.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 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/listener/send_ping_task.h" + +#include <string> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/xmppclient.h" + +namespace notifier { + +SendPingTask::Delegate::~Delegate() { +} + +SendPingTask::SendPingTask(buzz::XmppTaskParentInterface* parent, + Delegate* delegate) + : XmppTask(parent, buzz::XmppEngine::HL_SINGLE), delegate_(delegate) { +} + +SendPingTask::~SendPingTask() { +} + +int SendPingTask::ProcessStart() { + ping_task_id_ = task_id(); + scoped_ptr<buzz::XmlElement> stanza(MakePingStanza(ping_task_id_)); + DVLOG(1) << "Sending ping stanza " << XmlElementToString(*stanza); + if (SendStanza(stanza.get()) != buzz::XMPP_RETURN_OK) { + DLOG(WARNING) << "Could not send stanza " << XmlElementToString(*stanza); + return STATE_ERROR; + } + return STATE_RESPONSE; +} + +int SendPingTask::ProcessResponse() { + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + + DVLOG(1) << "Received stanza " << XmlElementToString(*stanza); + + std::string type = stanza->Attr(buzz::QN_TYPE); + if (type != buzz::STR_RESULT) { + DLOG(WARNING) << "No type=\"result\" attribute found in stanza " + << XmlElementToString(*stanza); + return STATE_ERROR; + } + + delegate_->OnPingResponseReceived(); + return STATE_DONE; +} + +bool SendPingTask::HandleStanza(const buzz::XmlElement* stanza) { + // MatchResponseIq() matches the given Jid with the "from" field of the given + // stanza, which in this case should be the empty string + // (signifying the server). + if (MatchResponseIq(stanza, buzz::Jid(buzz::STR_EMPTY), ping_task_id_)) { + QueueStanza(stanza); + return true; + } + return false; +} + +buzz::XmlElement* SendPingTask::MakePingStanza(const std::string& task_id) { + buzz::XmlElement* stanza = MakeIq(buzz::STR_GET, + buzz::Jid(buzz::STR_EMPTY), + task_id); + stanza->AddElement(new buzz::XmlElement(buzz::QN_PING)); + return stanza; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/send_ping_task.h b/chromium/jingle/notifier/listener/send_ping_task.h new file mode 100644 index 00000000000..9cd8b228239 --- /dev/null +++ b/chromium/jingle/notifier/listener/send_ping_task.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 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. +// +// Methods for sending the update stanza to notify peers via xmpp. + +#ifndef JINGLE_NOTIFIER_LISTENER_SEND_PING_TASK_H_ +#define JINGLE_NOTIFIER_LISTENER_SEND_PING_TASK_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "talk/xmpp/xmpptask.h" + +namespace buzz { +class XmlElement; +} // namespace + +namespace notifier { + +class SendPingTask : public buzz::XmppTask { + public: + class Delegate { + public: + virtual void OnPingResponseReceived() = 0; + + protected: + virtual ~Delegate(); + }; + + SendPingTask(buzz::XmppTaskParentInterface* parent, Delegate* delegate); + virtual ~SendPingTask(); + + // Overridden from buzz::XmppTask. + virtual int ProcessStart() OVERRIDE; + virtual int ProcessResponse() OVERRIDE; + virtual bool HandleStanza(const buzz::XmlElement* stanza) OVERRIDE; + + private: + static buzz::XmlElement* MakePingStanza(const std::string& task_id); + + FRIEND_TEST_ALL_PREFIXES(SendPingTaskTest, MakePingStanza); + + std::string ping_task_id_; + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(SendPingTask); +}; + +typedef SendPingTask::Delegate SendPingTaskDelegate; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_SEND_PING_TASK_H_ diff --git a/chromium/jingle/notifier/listener/send_ping_task_unittest.cc b/chromium/jingle/notifier/listener/send_ping_task_unittest.cc new file mode 100644 index 00000000000..1a2dd4bae83 --- /dev/null +++ b/chromium/jingle/notifier/listener/send_ping_task_unittest.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2012 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/listener/send_ping_task.h" + +#include "base/base64.h" +#include "base/memory/scoped_ptr.h" +#include "jingle/notifier/listener/xml_element_util.h" +#include "talk/xmpp/jid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { + +class SendPingTaskTest : public testing::Test { + public: + SendPingTaskTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(SendPingTaskTest); +}; + +TEST_F(SendPingTaskTest, MakePingStanza) { + std::string task_id = "42"; + + scoped_ptr<buzz::XmlElement> message(SendPingTask::MakePingStanza(task_id)); + + std::string expected_xml_string("<cli:iq type=\"get\" id=\""); + expected_xml_string += task_id; + expected_xml_string += + "\" xmlns:cli=\"jabber:client\">" + "<ping:ping xmlns:ping=\"urn:xmpp:ping\"/></cli:iq>"; + + EXPECT_EQ(expected_xml_string, XmlElementToString(*message)); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/xml_element_util.cc b/chromium/jingle/notifier/listener/xml_element_util.cc new file mode 100644 index 00000000000..7419a1e129a --- /dev/null +++ b/chromium/jingle/notifier/listener/xml_element_util.cc @@ -0,0 +1,51 @@ +// 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/listener/xml_element_util.h" + +#include <sstream> +#include <string> + +#include "base/strings/string_number_conversions.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlconstants.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" + +namespace notifier { + +std::string XmlElementToString(const buzz::XmlElement& xml_element) { + std::ostringstream xml_stream; + buzz::XmlPrinter::PrintXml(&xml_stream, &xml_element); + return xml_stream.str(); +} + +buzz::XmlElement* MakeBoolXmlElement(const char* name, bool value) { + const buzz::QName elementQName(buzz::STR_EMPTY, name); + const buzz::QName boolAttrQName(buzz::STR_EMPTY, "bool"); + buzz::XmlElement* bool_xml_element = + new buzz::XmlElement(elementQName, true); + bool_xml_element->AddAttr(boolAttrQName, value ? "true" : "false"); + return bool_xml_element; +} + +buzz::XmlElement* MakeIntXmlElement(const char* name, int value) { + const buzz::QName elementQName(buzz::STR_EMPTY, name); + const buzz::QName intAttrQName(buzz::STR_EMPTY, "int"); + buzz::XmlElement* int_xml_element = + new buzz::XmlElement(elementQName, true); + int_xml_element->AddAttr(intAttrQName, base::IntToString(value)); + return int_xml_element; +} + +buzz::XmlElement* MakeStringXmlElement(const char* name, const char* value) { + const buzz::QName elementQName(buzz::STR_EMPTY, name); + const buzz::QName dataAttrQName(buzz::STR_EMPTY, "data"); + buzz::XmlElement* data_xml_element = + new buzz::XmlElement(elementQName, true); + data_xml_element->AddAttr(dataAttrQName, value); + return data_xml_element; +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/xml_element_util.h b/chromium/jingle/notifier/listener/xml_element_util.h new file mode 100644 index 00000000000..bde86576cbf --- /dev/null +++ b/chromium/jingle/notifier/listener/xml_element_util.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_ +#define JINGLE_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_ + +#include <string> + +namespace buzz { +class XmlElement; +} + +namespace notifier { + +std::string XmlElementToString(const buzz::XmlElement& xml_element); + +// The functions below are helpful for building notifications-related +// XML stanzas. + +buzz::XmlElement* MakeBoolXmlElement(const char* name, bool value); + +buzz::XmlElement* MakeIntXmlElement(const char* name, int value); + +buzz::XmlElement* MakeStringXmlElement(const char* name, const char* value); + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_ diff --git a/chromium/jingle/notifier/listener/xml_element_util_unittest.cc b/chromium/jingle/notifier/listener/xml_element_util_unittest.cc new file mode 100644 index 00000000000..0db9645c5c0 --- /dev/null +++ b/chromium/jingle/notifier/listener/xml_element_util_unittest.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2011 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/listener/xml_element_util.h" + +#include <sstream> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { +namespace { + +class XmlElementUtilTest : public testing::Test {}; + +TEST_F(XmlElementUtilTest, XmlElementToString) { + const buzz::QName kQName("namespace", "element"); + const buzz::XmlElement kXmlElement(kQName, true); + std::ostringstream expected_xml_stream; + buzz::XmlPrinter::PrintXml(&expected_xml_stream, &kXmlElement); + EXPECT_EQ(expected_xml_stream.str(), XmlElementToString(kXmlElement)); +} + +TEST_F(XmlElementUtilTest, MakeBoolXmlElement) { + scoped_ptr<buzz::XmlElement> foo_false( + MakeBoolXmlElement("foo", false)); + EXPECT_EQ("<foo xmlns=\"\" bool=\"false\"/>", XmlElementToString(*foo_false)); + + scoped_ptr<buzz::XmlElement> bar_true( + MakeBoolXmlElement("bar", true)); + EXPECT_EQ("<bar xmlns=\"\" bool=\"true\"/>", XmlElementToString(*bar_true)); +} + +TEST_F(XmlElementUtilTest, MakeIntXmlElement) { + scoped_ptr<buzz::XmlElement> int_xml_element( + MakeIntXmlElement("foo", 35)); + EXPECT_EQ("<foo xmlns=\"\" int=\"35\"/>", + XmlElementToString(*int_xml_element)); +} + +TEST_F(XmlElementUtilTest, MakeStringXmlElement) { + scoped_ptr<buzz::XmlElement> string_xml_element( + MakeStringXmlElement("foo", "bar")); + EXPECT_EQ("<foo xmlns=\"\" data=\"bar\"/>", + XmlElementToString(*string_xml_element)); +} + +} // namespace +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/xmpp_push_client.cc b/chromium/jingle/notifier/listener/xmpp_push_client.cc new file mode 100644 index 00000000000..82622cec88e --- /dev/null +++ b/chromium/jingle/notifier/listener/xmpp_push_client.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2012 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/listener/xmpp_push_client.h" + +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "jingle/notifier/base/notifier_options_util.h" +#include "jingle/notifier/listener/push_client_observer.h" +#include "jingle/notifier/listener/send_ping_task.h" +#include "jingle/notifier/listener/push_notifications_send_update_task.h" + +namespace notifier { + +XmppPushClient::XmppPushClient(const NotifierOptions& notifier_options) + : notifier_options_(notifier_options) { + DCHECK(notifier_options_.request_context_getter-> + GetNetworkTaskRunner()->BelongsToCurrentThread()); +} + +XmppPushClient::~XmppPushClient() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void XmppPushClient::OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) { + DCHECK(thread_checker_.CalledOnValidThread()); + base_task_ = base_task; + + if (!base_task_.get()) { + NOTREACHED(); + return; + } + + // Listen for notifications. + { + // Owned by |base_task_|. + PushNotificationsListenTask* listener = + new PushNotificationsListenTask(base_task_.get(), this); + listener->Start(); + } + + // Send subscriptions. + { + // Owned by |base_task_|. + PushNotificationsSubscribeTask* subscribe_task = + new PushNotificationsSubscribeTask( + base_task_.get(), subscriptions_, this); + subscribe_task->Start(); + } + + std::vector<Notification> notifications_to_send; + notifications_to_send.swap(pending_notifications_to_send_); + for (std::vector<Notification>::const_iterator it = + notifications_to_send.begin(); + it != notifications_to_send.end(); ++it) { + DVLOG(1) << "Push: Sending pending notification " << it->ToString(); + SendNotification(*it); + } +} + +void XmppPushClient::OnTransientDisconnection() { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << "Push: Transient disconnection"; + base_task_.reset(); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); +} + +void XmppPushClient::OnCredentialsRejected() { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << "Push: Credentials rejected"; + base_task_.reset(); + FOR_EACH_OBSERVER( + PushClientObserver, observers_, + OnNotificationsDisabled(NOTIFICATION_CREDENTIALS_REJECTED)); +} + +void XmppPushClient::OnNotificationReceived( + const Notification& notification) { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnIncomingNotification(notification)); +} + +void XmppPushClient::OnPingResponseReceived() { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, OnPingResponse()); +} + +void XmppPushClient::OnSubscribed() { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsEnabled()); +} + +void XmppPushClient::OnSubscriptionError() { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(PushClientObserver, observers_, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); +} + +void XmppPushClient::AddObserver(PushClientObserver* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + observers_.AddObserver(observer); +} + +void XmppPushClient::RemoveObserver(PushClientObserver* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + observers_.RemoveObserver(observer); +} + +void XmppPushClient::UpdateSubscriptions( + const SubscriptionList& subscriptions) { + DCHECK(thread_checker_.CalledOnValidThread()); + subscriptions_ = subscriptions; +} + +void XmppPushClient::UpdateCredentials( + const std::string& email, const std::string& token) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << "Push: Updating credentials for " << email; + xmpp_settings_ = MakeXmppClientSettings(notifier_options_, email, token); + if (login_.get()) { + login_->UpdateXmppSettings(xmpp_settings_); + } else { + DVLOG(1) << "Push: Starting XMPP connection"; + base_task_.reset(); + login_.reset(new notifier::Login(this, + xmpp_settings_, + notifier_options_.request_context_getter, + GetServerList(notifier_options_), + notifier_options_.try_ssltcp_first, + notifier_options_.auth_mechanism)); + login_->StartConnection(); + } +} + +void XmppPushClient::SendNotification(const Notification& notification) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!base_task_.get()) { + // TODO(akalin): Figure out whether we really need to do this. + DVLOG(1) << "Push: Cannot send notification " + << notification.ToString() << "; sending later"; + pending_notifications_to_send_.push_back(notification); + return; + } + // Owned by |base_task_|. + PushNotificationsSendUpdateTask* task = + new PushNotificationsSendUpdateTask(base_task_.get(), notification); + task->Start(); +} + +void XmppPushClient::SendPing() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!base_task_.get()) { + DVLOG(1) << "Push: Cannot send ping"; + return; + } + // Owned by |base_task_|. + SendPingTask* task = new SendPingTask(base_task_.get(), this); + task->Start(); +} + +} // namespace notifier diff --git a/chromium/jingle/notifier/listener/xmpp_push_client.h b/chromium/jingle/notifier/listener/xmpp_push_client.h new file mode 100644 index 00000000000..08a1811706d --- /dev/null +++ b/chromium/jingle/notifier/listener/xmpp_push_client.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 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. + +#ifndef JINGLE_NOTIFIER_LISTENER_XMPP_PUSH_CLIENT_H_ +#define JINGLE_NOTIFIER_LISTENER_XMPP_PUSH_CLIENT_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "jingle/notifier/base/notifier_options.h" +#include "jingle/notifier/communicator/login.h" +#include "jingle/notifier/listener/notification_defines.h" +#include "jingle/notifier/listener/push_client.h" +#include "jingle/notifier/listener/push_notifications_listen_task.h" +#include "jingle/notifier/listener/push_notifications_subscribe_task.h" +#include "jingle/notifier/listener/send_ping_task.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace buzz { +class XmppTaskParentInterface; +} // namespace buzz + +namespace notifier { + +// This class implements a client for the XMPP google:push protocol. +// +// This class must be used on a single thread. +class XmppPushClient : + public PushClient, + public Login::Delegate, + public PushNotificationsListenTaskDelegate, + public PushNotificationsSubscribeTaskDelegate, + public SendPingTaskDelegate { + public: + explicit XmppPushClient(const NotifierOptions& notifier_options); + virtual ~XmppPushClient(); + + // PushClient implementation. + virtual void AddObserver(PushClientObserver* observer) OVERRIDE; + virtual void RemoveObserver(PushClientObserver* observer) OVERRIDE; + virtual void UpdateSubscriptions( + const SubscriptionList& subscriptions) OVERRIDE; + virtual void UpdateCredentials( + const std::string& email, const std::string& token) OVERRIDE; + virtual void SendNotification(const Notification& notification) OVERRIDE; + virtual void SendPing() OVERRIDE; + + // Login::Delegate implementation. + virtual void OnConnect( + base::WeakPtr<buzz::XmppTaskParentInterface> base_task) OVERRIDE; + virtual void OnTransientDisconnection() OVERRIDE; + virtual void OnCredentialsRejected() OVERRIDE; + + // PushNotificationsListenTaskDelegate implementation. + virtual void OnNotificationReceived( + const Notification& notification) OVERRIDE; + + // PushNotificationsSubscribeTaskDelegate implementation. + virtual void OnSubscribed() OVERRIDE; + virtual void OnSubscriptionError() OVERRIDE; + + // SendPingTaskDelegate implementation. + virtual void OnPingResponseReceived() OVERRIDE; + + private: + base::ThreadChecker thread_checker_; + const NotifierOptions notifier_options_; + ObserverList<PushClientObserver> observers_; + + // XMPP connection settings. + SubscriptionList subscriptions_; + buzz::XmppClientSettings xmpp_settings_; + + scoped_ptr<notifier::Login> login_; + + // The XMPP connection. + base::WeakPtr<buzz::XmppTaskParentInterface> base_task_; + + std::vector<Notification> pending_notifications_to_send_; + + DISALLOW_COPY_AND_ASSIGN(XmppPushClient); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_LISTENER_XMPP_PUSH_CLIENT_H_ diff --git a/chromium/jingle/notifier/listener/xmpp_push_client_unittest.cc b/chromium/jingle/notifier/listener/xmpp_push_client_unittest.cc new file mode 100644 index 00000000000..7d5d8204903 --- /dev/null +++ b/chromium/jingle/notifier/listener/xmpp_push_client_unittest.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2012 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/listener/xmpp_push_client.h" + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "jingle/notifier/base/fake_base_task.h" +#include "jingle/notifier/base/notifier_options.h" +#include "jingle/notifier/listener/push_client_observer.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +namespace { + +using ::testing::_; +using ::testing::Mock; +using ::testing::StrictMock; + +class MockObserver : public PushClientObserver { + public: + MOCK_METHOD0(OnNotificationsEnabled, void()); + MOCK_METHOD1(OnNotificationsDisabled, void(NotificationsDisabledReason)); + MOCK_METHOD1(OnIncomingNotification, void(const Notification&)); + MOCK_METHOD0(OnPingResponse, void()); +}; + +class XmppPushClientTest : public testing::Test { + protected: + XmppPushClientTest() { + notifier_options_.request_context_getter = + new net::TestURLRequestContextGetter( + message_loop_.message_loop_proxy()); + } + + virtual ~XmppPushClientTest() {} + + virtual void SetUp() OVERRIDE { + xmpp_push_client_.reset(new XmppPushClient(notifier_options_)); + xmpp_push_client_->AddObserver(&mock_observer_); + } + + virtual void TearDown() OVERRIDE { + // Clear out any messages posted by XmppPushClient. + message_loop_.RunUntilIdle(); + xmpp_push_client_->RemoveObserver(&mock_observer_); + xmpp_push_client_.reset(); + } + + // The sockets created by the XMPP code expect an IO loop. + base::MessageLoopForIO message_loop_; + NotifierOptions notifier_options_; + StrictMock<MockObserver> mock_observer_; + scoped_ptr<XmppPushClient> xmpp_push_client_; + FakeBaseTask fake_base_task_; +}; + +// Make sure the XMPP push client notifies its observers of incoming +// notifications properly. +TEST_F(XmppPushClientTest, OnIncomingNotification) { + EXPECT_CALL(mock_observer_, OnIncomingNotification(_)); + xmpp_push_client_->OnNotificationReceived(Notification()); +} + +// Make sure the XMPP push client notifies its observers of a +// successful connection properly. +TEST_F(XmppPushClientTest, ConnectAndSubscribe) { + EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); + xmpp_push_client_->OnConnect(fake_base_task_.AsWeakPtr()); + xmpp_push_client_->OnSubscribed(); +} + +// Make sure the XMPP push client notifies its observers of a +// terminated connection properly. +TEST_F(XmppPushClientTest, Disconnect) { + EXPECT_CALL(mock_observer_, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + xmpp_push_client_->OnTransientDisconnection(); +} + +// Make sure the XMPP push client notifies its observers of +// rejected credentials properly. +TEST_F(XmppPushClientTest, RejectCredentials) { + EXPECT_CALL(mock_observer_, + OnNotificationsDisabled(NOTIFICATION_CREDENTIALS_REJECTED)); + xmpp_push_client_->OnCredentialsRejected(); +} + +// Make sure the XMPP push client notifies its observers of a +// subscription error properly. +TEST_F(XmppPushClientTest, SubscriptionError) { + EXPECT_CALL(mock_observer_, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + xmpp_push_client_->OnSubscriptionError(); +} + +// Make sure nothing blows up when the XMPP push client sends a +// notification. +// +// TODO(akalin): Figure out how to test that the notification was +// actually sent. +TEST_F(XmppPushClientTest, SendNotification) { + EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); + + xmpp_push_client_->OnConnect(fake_base_task_.AsWeakPtr()); + xmpp_push_client_->OnSubscribed(); + xmpp_push_client_->SendNotification(Notification()); +} + +// Make sure nothing blows up when the XMPP push client sends a ping. +// +// TODO(akalin): Figure out how to test that the ping was actually sent. +TEST_F(XmppPushClientTest, SendPing) { + EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); + + xmpp_push_client_->OnConnect(fake_base_task_.AsWeakPtr()); + xmpp_push_client_->OnSubscribed(); + xmpp_push_client_->SendPing(); +} + +// Make sure nothing blows up when the XMPP push client sends a +// notification when disconnected, and the client connects. +// +// TODO(akalin): Figure out how to test that the notification was +// actually sent. +TEST_F(XmppPushClientTest, SendNotificationPending) { + xmpp_push_client_->SendNotification(Notification()); + + Mock::VerifyAndClearExpectations(&mock_observer_); + + EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); + + xmpp_push_client_->OnConnect(fake_base_task_.AsWeakPtr()); + xmpp_push_client_->OnSubscribed(); +} + +} // namespace + +} // namespace notifier diff --git a/chromium/jingle/run_all_unittests.cc b/chromium/jingle/run_all_unittests.cc new file mode 100644 index 00000000000..7fd6ef2e80e --- /dev/null +++ b/chromium/jingle/run_all_unittests.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2011 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 "base/test/test_suite.h" + +int main(int argc, char** argv) { + return base::TestSuite(argc, argv).Run(); +} |