summaryrefslogtreecommitdiff
path: root/chromium/jingle
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/jingle
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/jingle')
-rw-r--r--chromium/jingle/DEPS4
-rw-r--r--chromium/jingle/OWNERS6
-rw-r--r--chromium/jingle/PRESUBMIT.py16
-rw-r--r--chromium/jingle/glue/DEPS4
-rw-r--r--chromium/jingle/glue/channel_socket_adapter.cc194
-rw-r--r--chromium/jingle/glue/channel_socket_adapter.h82
-rw-r--r--chromium/jingle/glue/channel_socket_adapter_unittest.cc119
-rw-r--r--chromium/jingle/glue/chrome_async_socket.cc450
-rw-r--r--chromium/jingle/glue/chrome_async_socket.h213
-rw-r--r--chromium/jingle/glue/chrome_async_socket_unittest.cc1083
-rw-r--r--chromium/jingle/glue/fake_network_manager.cc51
-rw-r--r--chromium/jingle/glue/fake_network_manager.h41
-rw-r--r--chromium/jingle/glue/fake_socket_factory.cc195
-rw-r--r--chromium/jingle/glue/fake_socket_factory.h123
-rw-r--r--chromium/jingle/glue/fake_ssl_client_socket.cc347
-rw-r--r--chromium/jingle/glue/fake_ssl_client_socket.h111
-rw-r--r--chromium/jingle/glue/fake_ssl_client_socket_unittest.cc349
-rw-r--r--chromium/jingle/glue/jingle_glue_mock_objects.cc13
-rw-r--r--chromium/jingle/glue/jingle_glue_mock_objects.h32
-rw-r--r--chromium/jingle/glue/logging_unittest.cc161
-rw-r--r--chromium/jingle/glue/mock_task.cc13
-rw-r--r--chromium/jingle/glue/mock_task.h26
-rw-r--r--chromium/jingle/glue/proxy_resolving_client_socket.cc398
-rw-r--r--chromium/jingle/glue/proxy_resolving_client_socket.h106
-rw-r--r--chromium/jingle/glue/proxy_resolving_client_socket_unittest.cc118
-rw-r--r--chromium/jingle/glue/pseudotcp_adapter.cc598
-rw-r--r--chromium/jingle/glue/pseudotcp_adapter.h92
-rw-r--r--chromium/jingle/glue/pseudotcp_adapter_unittest.cc447
-rw-r--r--chromium/jingle/glue/resolving_client_socket_factory.h37
-rw-r--r--chromium/jingle/glue/task_pump.cc59
-rw-r--r--chromium/jingle/glue/task_pump.h42
-rw-r--r--chromium/jingle/glue/task_pump_unittest.cc50
-rw-r--r--chromium/jingle/glue/thread_wrapper.cc302
-rw-r--r--chromium/jingle/glue/thread_wrapper.h127
-rw-r--r--chromium/jingle/glue/thread_wrapper_unittest.cc308
-rw-r--r--chromium/jingle/glue/utils.cc95
-rw-r--r--chromium/jingle/glue/utils.h39
-rw-r--r--chromium/jingle/glue/xmpp_client_socket_factory.cc65
-rw-r--r--chromium/jingle/glue/xmpp_client_socket_factory.h56
-rw-r--r--chromium/jingle/jingle.gyp249
-rw-r--r--chromium/jingle/notifier/DEPS6
-rw-r--r--chromium/jingle/notifier/base/const_communicator.h13
-rw-r--r--chromium/jingle/notifier/base/fake_base_task.cc79
-rw-r--r--chromium/jingle/notifier/base/fake_base_task.h37
-rw-r--r--chromium/jingle/notifier/base/gaia_constants.cc12
-rw-r--r--chromium/jingle/notifier/base/gaia_constants.h14
-rw-r--r--chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.cc118
-rw-r--r--chromium/jingle/notifier/base/gaia_token_pre_xmpp_auth.h67
-rw-r--r--chromium/jingle/notifier/base/notification_method.cc42
-rw-r--r--chromium/jingle/notifier/base/notification_method.h32
-rw-r--r--chromium/jingle/notifier/base/notifier_options.cc20
-rw-r--r--chromium/jingle/notifier/base/notifier_options.h50
-rw-r--r--chromium/jingle/notifier/base/notifier_options_util.cc66
-rw-r--r--chromium/jingle/notifier/base/notifier_options_util.h28
-rw-r--r--chromium/jingle/notifier/base/server_information.cc29
-rw-r--r--chromium/jingle/notifier/base/server_information.h34
-rw-r--r--chromium/jingle/notifier/base/weak_xmpp_client.cc41
-rw-r--r--chromium/jingle/notifier/base/weak_xmpp_client.h56
-rw-r--r--chromium/jingle/notifier/base/weak_xmpp_client_unittest.cc124
-rw-r--r--chromium/jingle/notifier/base/xmpp_connection.cc147
-rw-r--r--chromium/jingle/notifier/base/xmpp_connection.h105
-rw-r--r--chromium/jingle/notifier/base/xmpp_connection_unittest.cc253
-rw-r--r--chromium/jingle/notifier/communicator/DEPS4
-rw-r--r--chromium/jingle/notifier/communicator/connection_settings.cc102
-rw-r--r--chromium/jingle/notifier/communicator/connection_settings.h59
-rw-r--r--chromium/jingle/notifier/communicator/connection_settings_unittest.cc99
-rw-r--r--chromium/jingle/notifier/communicator/login.cc148
-rw-r--r--chromium/jingle/notifier/communicator/login.h131
-rw-r--r--chromium/jingle/notifier/communicator/login_settings.cc72
-rw-r--r--chromium/jingle/notifier/communicator/login_settings.h74
-rw-r--r--chromium/jingle/notifier/communicator/login_settings_unittest.cc77
-rw-r--r--chromium/jingle/notifier/communicator/single_login_attempt.cc182
-rw-r--r--chromium/jingle/notifier/communicator/single_login_attempt.h83
-rw-r--r--chromium/jingle/notifier/communicator/single_login_attempt_unittest.cc259
-rw-r--r--chromium/jingle/notifier/listener/fake_push_client.cc80
-rw-r--r--chromium/jingle/notifier/listener/fake_push_client.h62
-rw-r--r--chromium/jingle/notifier/listener/fake_push_client_observer.cc39
-rw-r--r--chromium/jingle/notifier/listener/fake_push_client_observer.h36
-rw-r--r--chromium/jingle/notifier/listener/non_blocking_push_client.cc246
-rw-r--r--chromium/jingle/notifier/listener/non_blocking_push_client.h74
-rw-r--r--chromium/jingle/notifier/listener/non_blocking_push_client_unittest.cc151
-rw-r--r--chromium/jingle/notifier/listener/notification_constants.cc11
-rw-r--r--chromium/jingle/notifier/listener/notification_constants.h14
-rw-r--r--chromium/jingle/notifier/listener/notification_defines.cc76
-rw-r--r--chromium/jingle/notifier/listener/notification_defines.h65
-rw-r--r--chromium/jingle/notifier/listener/notification_defines_unittest.cc27
-rw-r--r--chromium/jingle/notifier/listener/push_client.cc41
-rw-r--r--chromium/jingle/notifier/listener/push_client.h60
-rw-r--r--chromium/jingle/notifier/listener/push_client_observer.cc13
-rw-r--r--chromium/jingle/notifier/listener/push_client_observer.h50
-rw-r--r--chromium/jingle/notifier/listener/push_client_unittest.cc62
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_listen_task.cc102
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_listen_task.h59
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_send_update_task.cc94
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_send_update_task.h47
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_send_update_task_unittest.cc58
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_subscribe_task.cc109
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_subscribe_task.h60
-rw-r--r--chromium/jingle/notifier/listener/push_notifications_subscribe_task_unittest.cc62
-rw-r--r--chromium/jingle/notifier/listener/send_ping_task.cc80
-rw-r--r--chromium/jingle/notifier/listener/send_ping_task.h54
-rw-r--r--chromium/jingle/notifier/listener/send_ping_task_unittest.cc41
-rw-r--r--chromium/jingle/notifier/listener/xml_element_util.cc51
-rw-r--r--chromium/jingle/notifier/listener/xml_element_util.h29
-rw-r--r--chromium/jingle/notifier/listener/xml_element_util_unittest.cc58
-rw-r--r--chromium/jingle/notifier/listener/xmpp_push_client.cc166
-rw-r--r--chromium/jingle/notifier/listener/xmpp_push_client.h94
-rw-r--r--chromium/jingle/notifier/listener/xmpp_push_client_unittest.cc144
-rw-r--r--chromium/jingle/run_all_unittests.cc9
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,
+ &current_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, &notification.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();
+}