summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authorsanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-04 23:46:46 +0000
committersanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-04 23:46:46 +0000
commit6ef414fef2fc84004a3a7da1b1e513700fb24018 (patch)
treee689c4521d09d38767f0ab80842c8649daa9f0e7 /chrome/common
parent1a6f614da2d7e0a6e60a7350eca02c29fc8c4c5d (diff)
downloadchromium_src-6ef414fef2fc84004a3a7da1b1e513700fb24018.zip
chromium_src-6ef414fef2fc84004a3a7da1b1e513700fb24018.tar.gz
chromium_src-6ef414fef2fc84004a3a7da1b1e513700fb24018.tar.bz2
Moved XMPP notifier library from chrome/browser/sync to chrome/common/net.
BUG=None TEST=No functional change Review URL: http://codereview.chromium.org/1956001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46413 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r--chrome/common/net/notifier/DEPS8
-rw-r--r--chrome/common/net/notifier/base/async_dns_lookup.cc141
-rw-r--r--chrome/common/net/notifier/base/async_dns_lookup.h51
-rw-r--r--chrome/common/net/notifier/base/async_network_alive.h53
-rw-r--r--chrome/common/net/notifier/base/fastalloc.h59
-rw-r--r--chrome/common/net/notifier/base/linux/async_network_alive_linux.cc144
-rw-r--r--chrome/common/net/notifier/base/mac/network_status_detector_task_mac.cc262
-rw-r--r--chrome/common/net/notifier/base/mac/network_status_detector_task_mac.h154
-rw-r--r--chrome/common/net/notifier/base/mac/network_status_detector_task_mac_unittest.cc111
-rw-r--r--chrome/common/net/notifier/base/nethelpers.cc43
-rw-r--r--chrome/common/net/notifier/base/nethelpers.h27
-rw-r--r--chrome/common/net/notifier/base/network_status_detector_task.cc30
-rw-r--r--chrome/common/net/notifier/base/network_status_detector_task.h58
-rw-r--r--chrome/common/net/notifier/base/network_status_detector_task_mt.cc48
-rw-r--r--chrome/common/net/notifier/base/network_status_detector_task_mt.h34
-rw-r--r--chrome/common/net/notifier/base/posix/time_posix.cc40
-rw-r--r--chrome/common/net/notifier/base/signal_thread_task.h93
-rw-r--r--chrome/common/net/notifier/base/sigslotrepeater.h83
-rw-r--r--chrome/common/net/notifier/base/ssl_adapter.cc27
-rw-r--r--chrome/common/net/notifier/base/ssl_adapter.h33
-rw-r--r--chrome/common/net/notifier/base/static_assert.h21
-rw-r--r--chrome/common/net/notifier/base/task_pump.cc41
-rw-r--r--chrome/common/net/notifier/base/task_pump.h34
-rw-r--r--chrome/common/net/notifier/base/time.cc30
-rw-r--r--chrome/common/net/notifier/base/time.h58
-rw-r--r--chrome/common/net/notifier/base/time_unittest.cc16
-rw-r--r--chrome/common/net/notifier/base/timer.cc33
-rw-r--r--chrome/common/net/notifier/base/timer.h39
-rw-r--r--chrome/common/net/notifier/base/utils.h90
-rw-r--r--chrome/common/net/notifier/base/win/async_network_alive_win32.cc251
-rw-r--r--chrome/common/net/notifier/base/win/time_win32.cc104
-rw-r--r--chrome/common/net/notifier/communicator/auto_reconnect.cc156
-rw-r--r--chrome/common/net/notifier/communicator/auto_reconnect.h75
-rw-r--r--chrome/common/net/notifier/communicator/connection_options.cc17
-rw-r--r--chrome/common/net/notifier/communicator/connection_options.h56
-rw-r--r--chrome/common/net/notifier/communicator/connection_settings.cc127
-rw-r--r--chrome/common/net/notifier/communicator/connection_settings.h76
-rw-r--r--chrome/common/net/notifier/communicator/const_communicator.h13
-rw-r--r--chrome/common/net/notifier/communicator/login.cc358
-rw-r--r--chrome/common/net/notifier/communicator/login.h154
-rw-r--r--chrome/common/net/notifier/communicator/login_failure.cc28
-rw-r--r--chrome/common/net/notifier/communicator/login_failure.h53
-rw-r--r--chrome/common/net/notifier/communicator/login_settings.cc55
-rw-r--r--chrome/common/net/notifier/communicator/login_settings.h91
-rw-r--r--chrome/common/net/notifier/communicator/mailbox.cc675
-rw-r--r--chrome/common/net/notifier/communicator/mailbox.h166
-rw-r--r--chrome/common/net/notifier/communicator/mailbox_unittest.cc119
-rw-r--r--chrome/common/net/notifier/communicator/product_info.cc15
-rw-r--r--chrome/common/net/notifier/communicator/product_info.h15
-rw-r--r--chrome/common/net/notifier/communicator/single_login_attempt.cc570
-rw-r--r--chrome/common/net/notifier/communicator/single_login_attempt.h138
-rw-r--r--chrome/common/net/notifier/communicator/ssl_socket_adapter.cc389
-rw-r--r--chrome/common/net/notifier/communicator/ssl_socket_adapter.h134
-rw-r--r--chrome/common/net/notifier/communicator/xmpp_connection_generator.cc210
-rw-r--r--chrome/common/net/notifier/communicator/xmpp_connection_generator.h85
-rw-r--r--chrome/common/net/notifier/communicator/xmpp_log.cc112
-rw-r--r--chrome/common/net/notifier/communicator/xmpp_log.h46
-rw-r--r--chrome/common/net/notifier/communicator/xmpp_socket_adapter.cc427
-rw-r--r--chrome/common/net/notifier/communicator/xmpp_socket_adapter.h87
-rw-r--r--chrome/common/net/notifier/listener/listen_task.cc144
-rw-r--r--chrome/common/net/notifier/listener/listen_task.h49
-rw-r--r--chrome/common/net/notifier/listener/mediator_thread.h55
-rw-r--r--chrome/common/net/notifier/listener/mediator_thread_impl.cc298
-rw-r--r--chrome/common/net/notifier/listener/mediator_thread_impl.h152
-rw-r--r--chrome/common/net/notifier/listener/mediator_thread_mock.h81
-rw-r--r--chrome/common/net/notifier/listener/notification_constants.cc11
-rw-r--r--chrome/common/net/notifier/listener/notification_constants.h14
-rw-r--r--chrome/common/net/notifier/listener/notification_defines.h34
-rw-r--r--chrome/common/net/notifier/listener/send_update_task.cc131
-rw-r--r--chrome/common/net/notifier/listener/send_update_task.h47
-rw-r--r--chrome/common/net/notifier/listener/send_update_task_unittest.cc116
-rw-r--r--chrome/common/net/notifier/listener/subscribe_task.cc109
-rw-r--r--chrome/common/net/notifier/listener/subscribe_task.h51
-rw-r--r--chrome/common/net/notifier/listener/subscribe_task_unittest.cc73
-rw-r--r--chrome/common/net/notifier/listener/talk_mediator.h76
-rw-r--r--chrome/common/net/notifier/listener/talk_mediator_impl.cc257
-rw-r--r--chrome/common/net/notifier/listener/talk_mediator_impl.h115
-rw-r--r--chrome/common/net/notifier/listener/talk_mediator_unittest.cc185
-rw-r--r--chrome/common/net/notifier/listener/xml_element_util.cc51
-rw-r--r--chrome/common/net/notifier/listener/xml_element_util.h29
-rw-r--r--chrome/common/net/notifier/listener/xml_element_util_unittest.cc59
81 files changed, 8800 insertions, 0 deletions
diff --git a/chrome/common/net/notifier/DEPS b/chrome/common/net/notifier/DEPS
new file mode 100644
index 0000000..95cce2f
--- /dev/null
+++ b/chrome/common/net/notifier/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ # notifier depends on libjingle.
+ "+talk/base",
+ "+talk/xmpp",
+ "+talk/xmllite",
+ "+talk/p2p/base", # TODO(ncarter): Determine if this is necessary/proper.
+
+]
diff --git a/chrome/common/net/notifier/base/async_dns_lookup.cc b/chrome/common/net/notifier/base/async_dns_lookup.cc
new file mode 100644
index 0000000..21b7928
--- /dev/null
+++ b/chrome/common/net/notifier/base/async_dns_lookup.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/async_dns_lookup.h"
+
+#include "build/build_config.h"
+
+#if defined(OS_POSIX)
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif // defined(OS_POSIX)
+
+// Apparently, inet_aton() is available for Windows, but just not
+// declared anywhere. We'd use inet_pton(), but it's Vista-only.
+#if defined(OS_WIN)
+int inet_aton(const char* cp, struct in_addr* inp);
+#endif // defined(OS_WIN)
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "chrome/common/net/notifier/base/nethelpers.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+
+enum { MSG_TIMEOUT = talk_base::SignalThread::ST_MSG_FIRST_AVAILABLE };
+
+#ifndef WIN32
+const int WSAHOST_NOT_FOUND = 11001; // Follows the format in winsock2.h.
+#endif // WIN32
+
+namespace notifier {
+
+AsyncDNSLookup::AsyncDNSLookup(const talk_base::SocketAddress& server)
+ : server_(new talk_base::SocketAddress(server)),
+ error_(0) {
+ // Timeout after 5 seconds.
+ talk_base::Thread::Current()->PostDelayed(5000, this, MSG_TIMEOUT);
+}
+
+AsyncDNSLookup::~AsyncDNSLookup() {
+}
+
+void AsyncDNSLookup::DoWork() {
+ std::string hostname(server_->IPAsString());
+
+ in_addr addr;
+ if (inet_aton(hostname.c_str(), &addr)) {
+ talk_base::CritScope scope(&cs_);
+ ip_list_.push_back(talk_base::NetworkToHost32(addr.s_addr));
+ } else {
+ LOG(INFO) << "(" << hostname << ")";
+ hostent ent;
+ char buffer[8192];
+ int errcode = 0;
+ hostent* host = SafeGetHostByName(hostname.c_str(), &ent,
+ buffer, sizeof(buffer),
+ &errcode);
+ talk_base::Thread::Current()->Clear(this, MSG_TIMEOUT);
+ if (host) {
+ talk_base::CritScope scope(&cs_);
+
+ // Check to see if this already timed out.
+ if (error_ == 0) {
+ for (int index = 0; true; ++index) {
+ uint32* addr = reinterpret_cast<uint32*>(host->h_addr_list[index]);
+ if (addr == 0) { // 0 = end of list.
+ break;
+ }
+ uint32 ip = talk_base::NetworkToHost32(*addr);
+ LOG(INFO) << "(" << hostname << ") resolved to: "
+ << talk_base::SocketAddress::IPToString(ip);
+ ip_list_.push_back(ip);
+ }
+ // Maintain the invariant that either the list is not empty or the
+ // error is non zero when we are done with processing the dnslookup.
+ if (ip_list_.empty() && error_ == 0) {
+ error_ = WSAHOST_NOT_FOUND;
+ }
+ }
+ FreeHostEnt(host);
+ } else {
+ { // Scoping for the critical section.
+ talk_base::CritScope scope(&cs_);
+
+ // Check to see if this already timed out.
+ if (error_ == 0) {
+ error_ = errcode;
+ }
+ }
+ LOG(ERROR) << "(" << hostname << ") error: " << error_;
+ }
+ }
+}
+
+void AsyncDNSLookup::OnMessage(talk_base::Message* message) {
+ ASSERT(message);
+ if (message->message_id == MSG_TIMEOUT) {
+ OnTimeout();
+ } else {
+ talk_base::SignalThread::OnMessage(message);
+ }
+}
+
+void AsyncDNSLookup::OnTimeout() {
+ // Allow the scope for the critical section to be the whole method, just to
+ // be sure that the worker thread can't exit while we are doing
+ // SignalWorkDone (because that could possibly cause the class to be
+ // deleted).
+ talk_base::CritScope scope(&cs_);
+
+ // Check to see if the ip list was already filled (or errored out).
+ if (!ip_list_.empty() || error_ != 0) {
+ return;
+ }
+
+ // Worker thread is taking too long so timeout.
+ error_ = WSAHOST_NOT_FOUND;
+
+ // Rely on the caller to do the Release/Destroy.
+ //
+ // Doing this signal while holding cs_ won't cause a deadlock because the
+ // AsyncDNSLookup::DoWork thread doesn't have any locks at this point, and it
+ // is the only thread being held up by this.
+ SignalWorkDone(this);
+
+ // Ensure that no more "WorkDone" signaling is done.
+ // Don't call Release or Destroy since that was already done by the callback.
+ SignalWorkDone.disconnect_all();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/async_dns_lookup.h b/chrome/common/net/notifier/base/async_dns_lookup.h
new file mode 100644
index 0000000..276eadeb5
--- /dev/null
+++ b/chrome/common/net/notifier/base/async_dns_lookup.h
@@ -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.
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_BASE_ASYNC_DNS_LOOKUP_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_ASYNC_DNS_LOOKUP_H_
+
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+class SocketAddress;
+class Task;
+}
+
+namespace notifier {
+
+class AsyncDNSLookup : public talk_base::SignalThread {
+ public:
+ explicit AsyncDNSLookup(const talk_base::SocketAddress& server);
+ virtual ~AsyncDNSLookup();
+
+ int error() const {
+ return error_;
+ }
+
+ const std::vector<uint32>& ip_list() const {
+ return ip_list_;
+ }
+
+ protected:
+ // SignalThread Interface.
+ virtual void DoWork();
+ virtual void OnMessage(talk_base::Message* message);
+
+ private:
+ void OnTimeout();
+
+ scoped_ptr<talk_base::SocketAddress> server_;
+ talk_base::CriticalSection cs_;
+ int error_;
+ std::vector<uint32> ip_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncDNSLookup);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_ASYNC_DNS_LOOKUP_H_
diff --git a/chrome/common/net/notifier/base/async_network_alive.h b/chrome/common/net/notifier/base/async_network_alive.h
new file mode 100644
index 0000000..5a2c707
--- /dev/null
+++ b/chrome/common/net/notifier/base/async_network_alive.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_ASYNC_NETWORK_ALIVE_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_ASYNC_NETWORK_ALIVE_H_
+
+#include "talk/base/signalthread.h"
+
+namespace notifier {
+
+// System specific info needed for changes.
+class PlatformNetworkInfo;
+
+class AsyncNetworkAlive : public talk_base::SignalThread {
+ public:
+ static AsyncNetworkAlive* Create();
+
+ virtual ~AsyncNetworkAlive() {}
+
+ bool alive() const {
+ return alive_;
+ }
+
+ bool error() const {
+ return error_;
+ }
+
+ void SetWaitForNetworkChange(PlatformNetworkInfo* previous_info) {
+ network_info_ = previous_info;
+ }
+
+ PlatformNetworkInfo* ReleaseInfo() {
+ PlatformNetworkInfo* info = network_info_;
+ network_info_ = NULL;
+ return info;
+ }
+
+ protected:
+ AsyncNetworkAlive() : network_info_(NULL), alive_(false), error_(false) {
+ }
+
+ PlatformNetworkInfo* network_info_;
+ bool alive_;
+ bool error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsyncNetworkAlive);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_ASYNC_NETWORK_ALIVE_H_
diff --git a/chrome/common/net/notifier/base/fastalloc.h b/chrome/common/net/notifier/base/fastalloc.h
new file mode 100644
index 0000000..bfd294d
--- /dev/null
+++ b/chrome/common/net/notifier/base/fastalloc.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_FASTALLOC_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_FASTALLOC_H_
+
+#include <assert.h>
+
+namespace notifier {
+
+template<class T, size_t Size>
+class FastAlloc {
+ public:
+ FastAlloc() : buffer_(NULL), size_(0) { }
+ ~FastAlloc() { freeBuffer(); }
+ T* get_buffer(size_t size) {
+ if (size_ != 0) {
+ // We only allow one call to get_buffer. This makes the logic here
+ // simpler, and the user has to worry less about someone else calling
+ // get_buffer again on the same FastAlloc object and invalidating the
+ // memory they were using.
+ assert(false && "get_buffer may one be called once");
+ return NULL;
+ }
+
+ if (size <= Size) {
+ buffer_ = internal_buffer_;
+ } else {
+ buffer_ = new T[size];
+ }
+
+ if (buffer_ != NULL) {
+ size_ = size;
+ }
+ return buffer_;
+ }
+
+ private:
+ void freeBuffer() {
+#if defined(DEBUG)
+ memset(buffer_, 0xCC, size_ * sizeof(T));
+#endif
+
+ if (buffer_ != NULL && buffer_ != internal_buffer_) {
+ delete[] buffer_;
+ }
+ buffer_ = NULL;
+ size_ = 0;
+ }
+
+ T* buffer_;
+ T internal_buffer_[Size];
+ size_t size_;
+};
+
+}
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_FASTALLOC_H_
diff --git a/chrome/common/net/notifier/base/linux/async_network_alive_linux.cc b/chrome/common/net/notifier/base/linux/async_network_alive_linux.cc
new file mode 100644
index 0000000..4accbf3
--- /dev/null
+++ b/chrome/common/net/notifier/base/linux/async_network_alive_linux.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/async_network_alive.h"
+
+#include <sys/socket.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "base/logging.h"
+#include "base/platform_file.h"
+#include "talk/base/physicalsocketserver.h"
+
+using base::kInvalidPlatformFileValue;
+
+namespace notifier {
+
+class AsyncNetworkAliveLinux : public AsyncNetworkAlive {
+ public:
+ AsyncNetworkAliveLinux() {
+ if (pipe(exit_pipe_) == -1) {
+ PLOG(ERROR) << "Could not create pipe for exit signal.";
+ exit_pipe_[0] = kInvalidPlatformFileValue;
+ exit_pipe_[1] = kInvalidPlatformFileValue;
+ }
+ }
+
+ virtual ~AsyncNetworkAliveLinux() {
+ if (exit_pipe_[1] != kInvalidPlatformFileValue) {
+ // Ensure that we've signalled the thread to quit.
+ char data = 0;
+ if (write(exit_pipe_[1], &data, 1) == -1) {
+ PLOG(WARNING) << "Error sending error signal to AsyncNetworkAliveLinux";
+ }
+ close(exit_pipe_[1]);
+ exit_pipe_[1] = kInvalidPlatformFileValue;
+ }
+ if (exit_pipe_[0] != kInvalidPlatformFileValue) {
+ close(exit_pipe_[0]);
+ exit_pipe_[0] = kInvalidPlatformFileValue;
+ }
+ }
+
+ protected:
+ // SignalThread Interface
+ virtual void DoWork() {
+ if (exit_pipe_[0] == kInvalidPlatformFileValue) {
+ PLOG(ERROR) << "No exit flag to listen for.";
+ // If we don't have an exit flag to listen for, set the error flag and
+ // abort.
+ error_ = true;
+ return;
+ }
+
+ // This function listens for changes to network interfaces, and link state.
+ // It's copied from syncapi.cc.
+ struct sockaddr_nl socket_address;
+
+ memset(&socket_address, 0, sizeof(socket_address));
+ socket_address.nl_family = AF_NETLINK;
+ socket_address.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
+
+ // NETLINK_ROUTE is the protocol used to update the kernel routing table.
+ int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ bind(fd, (struct sockaddr *) &socket_address, sizeof(socket_address));
+
+ fd_set rdfs;
+ FD_ZERO(&rdfs);
+ FD_SET(fd, &rdfs);
+ FD_SET(exit_pipe_[0], &rdfs);
+
+ int max_fd = fd > exit_pipe_[0] ? fd : exit_pipe_[0];
+
+ int result = select(max_fd + 1, &rdfs, NULL, NULL, NULL);
+
+ if (result <= 0) {
+ error_ = true;
+ PLOG(ERROR) << "select() returned unexpected result " << result;
+ close(fd);
+ close(exit_pipe_[0]);
+ exit_pipe_[0] = kInvalidPlatformFileValue;
+ return;
+ }
+
+ // Since we received a change from the socket, read the change in.
+ if (FD_ISSET(fd, &rdfs)) {
+ char buf[4096];
+ struct iovec iov = { buf, sizeof(buf) };
+ struct sockaddr_nl sa;
+
+ struct msghdr msg = { (void *)&sa, sizeof(sa), &iov, 1, NULL, 0, 0 };
+ recvmsg(fd, &msg, 0);
+ }
+
+ close(fd);
+
+ // If exit_pipe was written to, we must be shutting down.
+ if (exit_pipe_[0] == kInvalidPlatformFileValue ||
+ FD_ISSET(exit_pipe_[0], &rdfs)) {
+ alive_ = false;
+ error_ = true;
+ close(exit_pipe_[0]);
+ exit_pipe_[0] = kInvalidPlatformFileValue;
+ return;
+ }
+
+ // If there is an active connection, check that talk.google.com:5222
+ // is reachable.
+ talk_base::PhysicalSocketServer physical;
+ scoped_ptr<talk_base::Socket> socket(physical.CreateSocket(SOCK_STREAM));
+ if (socket->Connect(talk_base::SocketAddress("talk.google.com", 5222))) {
+ alive_ = false;
+ } else {
+ alive_ = true;
+ }
+
+ close(exit_pipe_[0]);
+ exit_pipe_[0] = kInvalidPlatformFileValue;
+ }
+
+ virtual void OnWorkStop() {
+ if (exit_pipe_[1] != kInvalidPlatformFileValue) {
+ char data = 0;
+ // We can't ignore the return value on write(), since that generates a
+ // compile warning. However, since we're exiting, there's nothing we can
+ // do if this fails except to log it.
+ if (write(exit_pipe_[1], &data, 1) == -1) {
+ PLOG(WARNING) << "Error sending error signal to AsyncNetworkAliveLinux";
+ }
+ }
+ }
+
+ private:
+ int exit_pipe_[2];
+ DISALLOW_COPY_AND_ASSIGN(AsyncNetworkAliveLinux);
+};
+
+AsyncNetworkAlive* AsyncNetworkAlive::Create() {
+ return new AsyncNetworkAliveLinux();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/mac/network_status_detector_task_mac.cc b/chrome/common/net/notifier/base/mac/network_status_detector_task_mac.cc
new file mode 100644
index 0000000..acebcd3
--- /dev/null
+++ b/chrome/common/net/notifier/base/mac/network_status_detector_task_mac.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/mac/network_status_detector_task_mac.h"
+
+#include <SystemConfiguration/SCNetworkReachability.h>
+
+#include "base/logging.h"
+#include "base/scoped_cftyperef.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socket.h"
+#include "talk/base/thread.h"
+
+namespace notifier {
+
+NetworkStatusDetectorTaskMac::WorkerInfo::WorkerInfo(
+ PlatformThreadId thread_id)
+ : thread_state(WORKER_THREAD_STOPPED),
+ thread_id(thread_id),
+ thread_run_loop(NULL) {}
+
+NetworkStatusDetectorTaskMac::WorkerInfo::WorkerInfo(
+ WorkerThreadState thread_state,
+ PlatformThreadId thread_id,
+ CFRunLoopRef thread_run_loop)
+ : thread_state(thread_state),
+ thread_id(thread_id),
+ thread_run_loop(thread_run_loop) {
+ DCHECK_EQ(thread_state == WORKER_THREAD_RUNNING, thread_run_loop != NULL);
+}
+
+NetworkStatusDetectorTaskMac::NetworkStatusDetectorTaskMac(
+ talk_base::Task* parent)
+ : NetworkStatusDetectorTask(parent),
+ parent_thread_id_(PlatformThread::CurrentId()),
+ parent_thread_(talk_base::Thread::Current()),
+ worker_thread_(kNullThreadHandle),
+ worker_thread_not_stopped_(&worker_lock_),
+ worker_shared_info_(parent_thread_id_) {
+ DCHECK(parent_thread_);
+ DCHECK(IsOnParentThread());
+}
+
+NetworkStatusDetectorTaskMac::~NetworkStatusDetectorTaskMac() {
+ ClearWorker();
+}
+
+void NetworkStatusDetectorTaskMac::ClearWorker() {
+ DCHECK(IsOnParentThread());
+ // Sadly, there's no Lock::AssertNotAcquired().
+ WorkerThreadState worker_thread_state;
+ CFRunLoopRef worker_thread_run_loop;
+ {
+ AutoLock auto_lock(worker_lock_);
+ worker_thread_state = worker_shared_info_.thread_state;
+ worker_thread_run_loop = worker_shared_info_.thread_run_loop;
+ }
+ if (worker_thread_state == WORKER_THREAD_RUNNING) {
+ CFRunLoopStop(worker_thread_run_loop);
+ }
+ if (worker_thread_ != kNullThreadHandle) {
+ DCHECK_NE(worker_thread_state, WORKER_THREAD_STOPPED);
+ PlatformThread::Join(worker_thread_);
+ }
+
+ worker_thread_ = kNullThreadHandle;
+ worker_shared_info_ = WorkerInfo(parent_thread_id_);
+}
+
+bool NetworkStatusDetectorTaskMac::IsOnParentThread() const {
+ return PlatformThread::CurrentId() == parent_thread_id_;
+}
+
+bool NetworkStatusDetectorTaskMac::IsOnWorkerThread() {
+ PlatformThreadId current_thread_id = PlatformThread::CurrentId();
+ AutoLock auto_lock(worker_lock_);
+ return
+ (worker_shared_info_.thread_id != parent_thread_id_) &&
+ (current_thread_id == worker_shared_info_.thread_id);
+}
+
+int NetworkStatusDetectorTaskMac::ProcessStart() {
+ DCHECK(IsOnParentThread());
+ if (logging::DEBUG_MODE) {
+ AutoLock auto_lock(worker_lock_);
+ DCHECK_EQ(worker_shared_info_.thread_state, WORKER_THREAD_STOPPED);
+ DCHECK(!worker_shared_info_.thread_run_loop);
+ DCHECK_EQ(worker_shared_info_.thread_id, parent_thread_id_);
+ }
+
+ if (!PlatformThread::Create(0, this, &worker_thread_)) {
+ LOG(WARNING) << "Could not create network reachability thread";
+ ClearWorker();
+ return STATE_ERROR;
+ }
+
+ // Wait for the just-created worker thread to start up and
+ // initialize itself.
+ WorkerThreadState worker_thread_state;
+ {
+ AutoLock auto_lock(worker_lock_);
+ while (worker_shared_info_.thread_state == WORKER_THREAD_STOPPED) {
+ worker_thread_not_stopped_.Wait();
+ }
+ worker_thread_state = worker_shared_info_.thread_state;
+ }
+
+ if (worker_thread_state == WORKER_THREAD_ERROR) {
+ ClearWorker();
+ return STATE_ERROR;
+ }
+
+ if (logging::DEBUG_MODE) {
+ AutoLock auto_lock(worker_lock_);
+ DCHECK_EQ(worker_shared_info_.thread_state, WORKER_THREAD_RUNNING);
+ DCHECK(worker_shared_info_.thread_run_loop);
+ DCHECK_NE(worker_shared_info_.thread_id, parent_thread_id_);
+ }
+
+ return STATE_RESPONSE;
+}
+
+void NetworkStatusDetectorTaskMac::Stop() {
+ ClearWorker();
+ NetworkStatusDetectorTask::Stop();
+}
+
+void NetworkStatusDetectorTaskMac::OnMessage(talk_base::Message* message) {
+ DCHECK(IsOnParentThread());
+ bool alive = message->message_id;
+ SetNetworkAlive(alive);
+}
+
+NetworkStatusDetectorTask* NetworkStatusDetectorTask::Create(
+ talk_base::Task* parent) {
+ return new NetworkStatusDetectorTaskMac(parent);
+}
+
+// Everything below is run in the worker thread.
+
+namespace {
+
+// TODO(akalin): Use these constants across all platform
+// implementations.
+const char kTalkHost[] = "talk.google.com";
+const int kTalkPort = 5222;
+
+CFStringRef NetworkReachabilityCopyDescription(const void *info) {
+ return base::SysUTF8ToCFStringRef(
+ StringPrintf("NetworkStatusDetectorTaskMac(0x%p)", info));
+}
+
+void NetworkReachabilityChangedCallback(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void* info) {
+ bool network_active = ((flags & (kSCNetworkFlagsReachable |
+ kSCNetworkFlagsConnectionRequired |
+ kSCNetworkFlagsConnectionAutomatic |
+ kSCNetworkFlagsInterventionRequired)) ==
+ kSCNetworkFlagsReachable);
+ NetworkStatusDetectorTaskMac* network_status_detector_task_mac =
+ static_cast<NetworkStatusDetectorTaskMac*>(info);
+ network_status_detector_task_mac->NetworkReachabilityChanged(
+ network_active);
+}
+
+
+SCNetworkReachabilityRef CreateAndScheduleNetworkReachability(
+ SCNetworkReachabilityContext* network_reachability_context) {
+ scoped_cftyperef<SCNetworkReachabilityRef> network_reachability(
+ SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, kTalkHost));
+ if (!network_reachability.get()) {
+ LOG(WARNING) << "Could not create network reachability object";
+ return NULL;
+ }
+
+ if (!SCNetworkReachabilitySetCallback(network_reachability.get(),
+ &NetworkReachabilityChangedCallback,
+ network_reachability_context)) {
+ LOG(WARNING) << "Could not set network reachability callback";
+ return NULL;
+ }
+
+ if (!SCNetworkReachabilityScheduleWithRunLoop(network_reachability.get(),
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode)) {
+ LOG(WARNING) << "Could not schedule network reachability with run loop";
+ return NULL;
+ }
+
+ return network_reachability.release();
+}
+
+} // namespace
+
+void NetworkStatusDetectorTaskMac::ThreadMain() {
+ DCHECK(!IsOnParentThread());
+ PlatformThread::SetName("NetworkStatusDetectorTaskMac worker thread");
+
+ SCNetworkReachabilityContext network_reachability_context;
+ network_reachability_context.version = 0;
+ network_reachability_context.info = static_cast<void *>(this);
+ network_reachability_context.retain = NULL;
+ network_reachability_context.release = NULL;
+ network_reachability_context.copyDescription =
+ &NetworkReachabilityCopyDescription;
+
+ PlatformThreadId worker_thread_id = PlatformThread::CurrentId();
+
+ scoped_cftyperef<SCNetworkReachabilityRef> network_reachability(
+ CreateAndScheduleNetworkReachability(&network_reachability_context));
+ if (!network_reachability.get()) {
+ {
+ AutoLock auto_lock(worker_lock_);
+ worker_shared_info_ =
+ WorkerInfo(WORKER_THREAD_ERROR, worker_thread_id, NULL);
+ }
+ worker_thread_not_stopped_.Signal();
+ return;
+ }
+
+ CFRunLoopRef run_loop = CFRunLoopGetCurrent();
+ {
+ AutoLock auto_lock(worker_lock_);
+ worker_shared_info_ =
+ WorkerInfo(WORKER_THREAD_RUNNING, worker_thread_id, run_loop);
+ }
+ worker_thread_not_stopped_.Signal();
+
+ DCHECK(IsOnWorkerThread());
+ CFRunLoopRun();
+
+ // We reach here only when our run loop is stopped (usually by the
+ // parent thread). The parent thread is responsible for resetting
+ // worker_thread_shared_info_, et al. to appropriate values.
+}
+
+void NetworkStatusDetectorTaskMac::NetworkReachabilityChanged(
+ bool network_active) {
+ DCHECK(IsOnWorkerThread());
+
+ bool alive = network_active;
+ if (alive) {
+ talk_base::PhysicalSocketServer physical;
+ scoped_ptr<talk_base::Socket> socket(physical.CreateSocket(SOCK_STREAM));
+ alive =
+ (socket->Connect(talk_base::SocketAddress(kTalkHost, kTalkPort)) == 0);
+ LOG(INFO) << "network is " << (alive ? "alive" : "not alive")
+ << " based on connecting to " << kTalkHost << ":" << kTalkPort;
+ } else {
+ LOG(INFO) << "network is not alive";
+ }
+
+ parent_thread_->Send(this, alive);
+}
+
+} // namespace notifier
+
diff --git a/chrome/common/net/notifier/base/mac/network_status_detector_task_mac.h b/chrome/common/net/notifier/base/mac/network_status_detector_task_mac.h
new file mode 100644
index 0000000..c844a33
--- /dev/null
+++ b/chrome/common/net/notifier/base/mac/network_status_detector_task_mac.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_MAC_NETWORK_STATUS_DETECTOR_TASK_MAC_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_MAC_NETWORK_STATUS_DETECTOR_TASK_MAC_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/basictypes.h"
+#include "base/condition_variable.h"
+#include "base/lock.h"
+#include "base/platform_thread.h"
+#include "chrome/common/net/notifier/base/network_status_detector_task.h"
+#include "talk/base/messagequeue.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace talk_base {
+class Message;
+class Task;
+class Thread;
+} // namespace talk_base
+
+namespace notifier {
+
+// The Mac OS X network status detector works as follows: a worker
+// (Chrome platform) thread is spawned which simply sets up a Cocoa
+// run loop and attaches the network reachability monitor to it.
+// Whenever the network reachability changes, (e.g., changed wireless
+// networks, unplugged ethernet cable) a callback on the worker
+// thread is triggered which then tries to connect to a Google talk
+// host (if the network is indicated to be up) and sends a message
+// with the result to the parent thread.
+
+class NetworkStatusDetectorTaskMac : public NetworkStatusDetectorTask,
+ public PlatformThread::Delegate,
+ public talk_base::MessageHandler {
+ public:
+ explicit NetworkStatusDetectorTaskMac(talk_base::Task* parent);
+
+ virtual ~NetworkStatusDetectorTaskMac();
+
+ // talk_base::Task functions (via NetworkStatusDetectorTask).
+ virtual int ProcessStart();
+ virtual void Stop();
+
+ // talk_base::MessageHandler functions.
+ // Currently OnMessage() simply calls SetNetworkAlive() with alive
+ // set to true iff message->message_id is non-zero.
+ virtual void OnMessage(talk_base::Message* message);
+
+ // Only the following public functions are called from the worker
+ // thread.
+
+ // PlatformThread::Delegate functions.
+ virtual void ThreadMain();
+
+ // Called when network reachability changes. network_active should
+ // be set only when the network is currently active and connecting
+ // to a host won't require any user intervention or create a network
+ // connection (e.g., prompting for a password, causing a modem to
+ // dial).
+ void NetworkReachabilityChanged(bool network_active);
+
+ private:
+ enum WorkerThreadState {
+ WORKER_THREAD_STOPPED = 1,
+ WORKER_THREAD_RUNNING,
+ WORKER_THREAD_ERROR,
+ };
+
+ // If thread_state => WORKER_THREAD_STOPPED:
+ //
+ // thread_id => parent_thread_id_
+ // thread_run_loop => NULL
+ // possible successor states =>
+ // { WORKER_THREAD_RUNNING, WORKER_THREAD_ERROR }
+ //
+ // If thread_state => WORKER_THREAD_RUNNING, the worker thread is
+ // successfully running and will continue to run until Stop() is
+ // called.
+ //
+ // thread_id => id of worker thread (!= parent_thread_id_)
+ // thread_run_loop => reference to the worker thread's run loop
+ // possible successor states => { WORKER_THREAD_STOPPED }
+ //
+ // If thread_state => WORKER_THREAD_ERROR, the worker thread has
+ // failed to start running and has stopped. Join() must be still
+ // called on the worker thread.
+ //
+ // thread_id => id of worker thread (!= parent_thread_id_)
+ // thread_run_loop => NULL
+ // possible successor states => { WORKER_THREAD_STOPPED }
+ //
+ // Only the worker thread can change the state from
+ // WORKER_THREAD_STOPPED to any other state and only the main thread
+ // can change the state to WORKER_THREAD_STOPPED.
+ struct WorkerInfo {
+ WorkerThreadState thread_state;
+ PlatformThreadId thread_id;
+ CFRunLoopRef thread_run_loop;
+
+ // This constructor sets thread_state to WORKER_THREAD_STOPPED
+ // and thread_run_loop to NULL.
+ explicit WorkerInfo(PlatformThreadId thread_id);
+
+ WorkerInfo(WorkerThreadState thread_state,
+ PlatformThreadId thread_id,
+ CFRunLoopRef thread_run_loop);
+ };
+
+ // After this function is called, worker_shared_info_.thread_state
+ // is guaranteed to be WORKER_THREAD_STOPPED and
+ // network_reachability_ is guaranteed to be NULL. Must be called
+ // only from the parent thread without worker_lock_ being held.
+ void ClearWorker();
+
+ bool IsOnParentThread() const;
+ // Acquires and releases worker_lock_.
+ bool IsOnWorkerThread();
+
+ // The thread ID of the thread that constructed this object.
+ PlatformThreadId parent_thread_id_;
+ // The libjingle thread object of the thread that constructed this
+ // object.
+ talk_base::Thread* parent_thread_;
+
+ // The handle to the worker thread, or kNullThreadHandle if a worker
+ // thread doesn't exist.
+ PlatformThreadHandle worker_thread_;
+
+ // This lock protects worker_shared_info_ when the worker
+ // thread is running.
+ Lock worker_lock_;
+
+ // Signalled by the worker thread when
+ // worker_shared_info_.thread_state moves from WORKER_THREAD_STOPPED
+ // to another state.
+ ConditionVariable worker_thread_not_stopped_;
+
+ // Struct for everything that is shared between the parent and the
+ // worker thread.
+ WorkerInfo worker_shared_info_;
+
+ FRIEND_TEST(NetworkStatusDetectorTaskMacTest, StartNoStopTest);
+ FRIEND_TEST(NetworkStatusDetectorTaskMacTest, StartStopTest);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkStatusDetectorTaskMac);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_MAC_NETWORK_STATUS_DETECTOR_TASK_MAC_H_
+
diff --git a/chrome/common/net/notifier/base/mac/network_status_detector_task_mac_unittest.cc b/chrome/common/net/notifier/base/mac/network_status_detector_task_mac_unittest.cc
new file mode 100644
index 0000000..9e75b42
--- /dev/null
+++ b/chrome/common/net/notifier/base/mac/network_status_detector_task_mac_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/mac/network_status_detector_task_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace notifier {
+
+// No anonymous namespace because we use FRIEND_TESTs.
+
+class NetworkStatusDetectorTaskMacTest : public testing::Test {
+};
+
+// TODO(akalin): We can't test much with the current interface.
+// Extend it so we're able to inject mock network events and then add
+// more tests.
+
+// Some basic sanity checks to make sure the object is destroyed
+// cleanly with various configurations.
+
+TEST_F(NetworkStatusDetectorTaskMacTest, InitTest) {
+ NetworkStatusDetectorTaskMac network_status_detector_mac(NULL);
+}
+
+TEST_F(NetworkStatusDetectorTaskMacTest, StartNoStopTest) {
+ NetworkStatusDetectorTaskMac network_status_detector_mac(NULL);
+ EXPECT_EQ(NetworkStatusDetectorTaskMac::STATE_RESPONSE,
+ network_status_detector_mac.ProcessStart());
+}
+
+class DummyTaskRunner : public talk_base::TaskRunner {
+ public:
+ virtual void WakeTasks() {}
+ virtual int64 CurrentTime() { return 0; }
+};
+
+TEST_F(NetworkStatusDetectorTaskMacTest, StartStopTest) {
+ DummyTaskRunner task_runner;
+ NetworkStatusDetectorTaskMac network_status_detector_mac(&task_runner);
+ EXPECT_EQ(NetworkStatusDetectorTaskMac::STATE_RESPONSE,
+ network_status_detector_mac.ProcessStart());
+ network_status_detector_mac.Stop();
+}
+
+// Some miscellaneous tests.
+
+class AliveListener : public sigslot::has_slots<> {
+ public:
+ AliveListener()
+ : was_alive_(false),
+ is_alive_(false),
+ set_alive_called_(false) {}
+
+ void SetAlive(bool was_alive, bool is_alive) {
+ was_alive_ = was_alive;
+ is_alive_ = is_alive;
+ set_alive_called_ = true;
+ }
+
+ void ResetSetAliveCalled() {
+ set_alive_called_ = false;
+ }
+
+ bool was_alive() const { return was_alive_; }
+ bool is_alive() const { return is_alive_; }
+ bool set_alive_called() const { return set_alive_called_; }
+
+ private:
+ bool was_alive_, is_alive_, set_alive_called_;
+};
+
+TEST_F(NetworkStatusDetectorTaskMacTest, OnMessageTest) {
+ NetworkStatusDetectorTaskMac network_status_detector_mac(NULL);
+ AliveListener alive_listener;
+ network_status_detector_mac.SignalNetworkStateDetected.connect(
+ &alive_listener, &AliveListener::SetAlive);
+
+ talk_base::Message message;
+
+ alive_listener.ResetSetAliveCalled();
+ message.message_id = 0;
+ network_status_detector_mac.OnMessage(&message);
+ EXPECT_TRUE(alive_listener.set_alive_called());
+ EXPECT_FALSE(alive_listener.was_alive());
+ EXPECT_FALSE(alive_listener.is_alive());
+
+ alive_listener.ResetSetAliveCalled();
+ message.message_id = 5;
+ network_status_detector_mac.OnMessage(&message);
+ EXPECT_TRUE(alive_listener.set_alive_called());
+ EXPECT_FALSE(alive_listener.was_alive());
+ EXPECT_TRUE(alive_listener.is_alive());
+
+ alive_listener.ResetSetAliveCalled();
+ message.message_id = 0;
+ network_status_detector_mac.OnMessage(&message);
+ EXPECT_TRUE(alive_listener.set_alive_called());
+ EXPECT_TRUE(alive_listener.was_alive());
+ EXPECT_FALSE(alive_listener.is_alive());
+}
+
+} // namespace notifier
+
diff --git a/chrome/common/net/notifier/base/nethelpers.cc b/chrome/common/net/notifier/base/nethelpers.cc
new file mode 100644
index 0000000..df93768
--- /dev/null
+++ b/chrome/common/net/notifier/base/nethelpers.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2009 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 "build/build_config.h"
+#include "chrome/common/net/notifier/base/nethelpers.h"
+
+namespace notifier {
+
+hostent* SafeGetHostByName(const char* hostname, hostent* host,
+ char* buffer, size_t buffer_len,
+ int* herrno) {
+ hostent* result = NULL;
+#if WIN32
+ result = gethostbyname(hostname);
+ if (!result) {
+ *herrno = WSAGetLastError();
+ }
+#elif OS_LINUX
+ gethostbyname_r(hostname, host, buffer, buffer_len, &result, herrno);
+#elif OS_MACOSX
+ result = getipnodebyname(hostname, AF_INET, AI_DEFAULT, herrno);
+#else
+#error "I don't know how to do gethostbyname safely on your system."
+#endif
+ return result;
+}
+
+// This function should mirror the above function, and free any resources
+// allocated by the above.
+void FreeHostEnt(hostent* host) {
+#if WIN32
+ // No need to free anything, struct returned is static memory.
+#elif OS_LINUX
+ // No need to free anything, we pass in a pointer to a struct.
+#elif OS_MACOSX
+ freehostent(host);
+#else
+#error "I don't know how to free a hostent on your system."
+#endif
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/nethelpers.h b/chrome/common/net/notifier/base/nethelpers.h
new file mode 100644
index 0000000..d53d01a
--- /dev/null
+++ b/chrome/common/net/notifier/base/nethelpers.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_NETHELPERS_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_NETHELPERS_H_
+
+#include "base/basictypes.h"
+
+#if defined(OS_POSIX)
+#include <cstddef>
+#include <netdb.h>
+#elif defined(OS_WIN)
+#include <winsock2.h>
+#endif
+
+namespace notifier {
+
+hostent* SafeGetHostByName(const char* hostname, hostent* host,
+ char* buffer, size_t buffer_len,
+ int* herrno);
+
+void FreeHostEnt(hostent* host);
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_NETHELPERS_H_
diff --git a/chrome/common/net/notifier/base/network_status_detector_task.cc b/chrome/common/net/notifier/base/network_status_detector_task.cc
new file mode 100644
index 0000000..12be088
--- /dev/null
+++ b/chrome/common/net/notifier/base/network_status_detector_task.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/network_status_detector_task.h"
+
+namespace notifier {
+
+void NetworkStatusDetectorTask::DetectNetworkState() {
+ // If the detection has been finished, then just broadcast the current state.
+ // Otherwise, allow the signal to be sent when the initial detection is
+ // finished.
+ if (initial_detection_done_) {
+ SignalNetworkStateDetected(is_alive_, is_alive_);
+ }
+}
+
+void NetworkStatusDetectorTask::SetNetworkAlive(bool is_alive) {
+ bool was_alive = is_alive_;
+ is_alive_ = is_alive;
+
+ if (!initial_detection_done_ || was_alive != is_alive_) {
+ initial_detection_done_ = true;
+
+ // Tell everyone about the network state change.
+ SignalNetworkStateDetected(was_alive, is_alive_);
+ }
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/network_status_detector_task.h b/chrome/common/net/notifier/base/network_status_detector_task.h
new file mode 100644
index 0000000..4b24a24
--- /dev/null
+++ b/chrome/common/net/notifier/base/network_status_detector_task.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_H_
+
+#include "chrome/common/net/notifier/base/time.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+
+namespace notifier {
+
+class AsyncNetworkAlive;
+
+// Detects the current network state and any changes to that.
+class NetworkStatusDetectorTask : public talk_base::Task,
+ public sigslot::has_slots<> {
+ public:
+ // Create an instance of (a subclass of) this class.
+ static NetworkStatusDetectorTask* Create(talk_base::Task* parent);
+
+ // Determines the current network state and then calls
+ // SignalNetworkStateDetected.
+ void DetectNetworkState();
+
+ // Fires whenever the network state is detected.
+ // SignalNetworkStateDetected(was_alive, is_alive);
+ sigslot::signal2<bool, bool> SignalNetworkStateDetected;
+
+ protected:
+ explicit NetworkStatusDetectorTask(talk_base::Task* parent)
+ : talk_base::Task(parent),
+ initial_detection_done_(false),
+ is_alive_(false) {
+ }
+
+ virtual ~NetworkStatusDetectorTask() { }
+
+ virtual int ProcessStart() = 0;
+
+ // Stay around until aborted.
+ virtual int ProcessResponse() {
+ return STATE_BLOCKED;
+ }
+
+ void SetNetworkAlive(bool is_alive);
+
+ private:
+ bool initial_detection_done_;
+ bool is_alive_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkStatusDetectorTask);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_H_
diff --git a/chrome/common/net/notifier/base/network_status_detector_task_mt.cc b/chrome/common/net/notifier/base/network_status_detector_task_mt.cc
new file mode 100644
index 0000000..7f35a37
--- /dev/null
+++ b/chrome/common/net/notifier/base/network_status_detector_task_mt.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/network_status_detector_task_mt.h"
+
+#include "chrome/common/net/notifier/base/async_network_alive.h"
+#include "chrome/common/net/notifier/base/signal_thread_task.h"
+
+#include "talk/base/common.h"
+
+namespace notifier {
+
+void NetworkStatusDetectorTaskMT::OnNetworkAliveDone(
+ AsyncNetworkAlive* network_alive) {
+ ASSERT(network_alive);
+ SetNetworkAlive(network_alive->alive());
+ // If we got an error from detecting the network alive state, then stop
+ // retrying the detection.
+ if (network_alive->error()) {
+ return;
+ }
+ StartAsyncDetection(network_alive->ReleaseInfo());
+}
+
+void NetworkStatusDetectorTaskMT::StartAsyncDetection(
+ PlatformNetworkInfo* previous_info) {
+ // Use the AsyncNetworkAlive to determine the network state (and changes in
+ // the network state).
+ AsyncNetworkAlive* network_alive = AsyncNetworkAlive::Create();
+
+ if (previous_info) {
+ network_alive->SetWaitForNetworkChange(previous_info);
+ }
+ SignalThreadTask<AsyncNetworkAlive>* task =
+ new SignalThreadTask<AsyncNetworkAlive>(this, &network_alive);
+ task->SignalWorkDone.connect(
+ this, &NetworkStatusDetectorTaskMT::OnNetworkAliveDone);
+ task->Start();
+}
+
+NetworkStatusDetectorTask* NetworkStatusDetectorTask::Create(
+ talk_base::Task* parent) {
+ ASSERT(parent);
+ return new NetworkStatusDetectorTaskMT(parent);
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/network_status_detector_task_mt.h b/chrome/common/net/notifier/base/network_status_detector_task_mt.h
new file mode 100644
index 0000000..ba55cd8
--- /dev/null
+++ b/chrome/common/net/notifier/base/network_status_detector_task_mt.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_MT_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_MT_H_
+
+#include "chrome/common/net/notifier/base/network_status_detector_task.h"
+
+namespace notifier {
+
+class AsyncNetworkAlive;
+class PlatformNetworkInfo;
+
+class NetworkStatusDetectorTaskMT : public NetworkStatusDetectorTask {
+ public:
+ explicit NetworkStatusDetectorTaskMT(talk_base::Task* parent)
+ : NetworkStatusDetectorTask(parent) {
+ }
+
+ protected:
+ virtual int ProcessStart() {
+ StartAsyncDetection(NULL);
+ return STATE_RESPONSE;
+ }
+
+ private:
+ void OnNetworkAliveDone(AsyncNetworkAlive* network_alive);
+ void StartAsyncDetection(PlatformNetworkInfo* network_info);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_MT_H_
diff --git a/chrome/common/net/notifier/base/posix/time_posix.cc b/chrome/common/net/notifier/base/posix/time_posix.cc
new file mode 100644
index 0000000..45c35c1
--- /dev/null
+++ b/chrome/common/net/notifier/base/posix/time_posix.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2009 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "chrome/common/net/notifier/base/time.h"
+
+namespace notifier {
+
+time64 GetCurrent100NSTime() {
+ struct timeval tv;
+ struct timezone tz;
+
+ gettimeofday(&tv, &tz);
+
+ time64 retval = tv.tv_sec * kSecsTo100ns;
+ retval += tv.tv_usec * kMicrosecsTo100ns;
+ retval += kStart100NsTimeToEpoch;
+ return retval;
+}
+
+time64 TmToTime64(const struct tm& tm) {
+ struct tm tm_temp;
+ memcpy(&tm_temp, &tm, sizeof(struct tm));
+ time_t t = timegm(&tm_temp);
+ return t * kSecsTo100ns;
+}
+
+bool Time64ToTm(time64 t, struct tm* tm) {
+ assert(tm != NULL);
+ time_t secs = t / kSecsTo100ns;
+ gmtime_r(&secs, tm);
+ return true;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/signal_thread_task.h b/chrome/common/net/notifier/base/signal_thread_task.h
new file mode 100644
index 0000000..46f203a
--- /dev/null
+++ b/chrome/common/net/notifier/base/signal_thread_task.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
+
+#include "talk/base/common.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+
+namespace notifier {
+
+template<class T>
+class SignalThreadTask : public talk_base::Task,
+ public sigslot::has_slots<> {
+ public:
+ // Takes ownership of signal_thread.
+ SignalThreadTask(talk_base::Task* task_parent, T** signal_thread)
+ : talk_base::Task(task_parent),
+ signal_thread_(NULL),
+ finished_(false) {
+ SetSignalThread(signal_thread);
+ }
+
+ virtual ~SignalThreadTask() {
+ ClearSignalThread();
+ }
+
+ virtual void Stop() {
+ Task::Stop();
+ ClearSignalThread();
+ }
+
+ virtual int ProcessStart() {
+ ASSERT(GetState() == talk_base::Task::STATE_START);
+ signal_thread_->SignalWorkDone.connect(
+ this,
+ &SignalThreadTask<T>::OnWorkDone);
+ signal_thread_->Start();
+ return talk_base::Task::STATE_RESPONSE;
+ }
+
+ int ProcessResponse() {
+ if (!finished_) {
+ return talk_base::Task::STATE_BLOCKED;
+ }
+ SignalWorkDone(signal_thread_);
+ ClearSignalThread();
+ return talk_base::Task::STATE_DONE;
+ }
+
+ sigslot::signal1<T*> SignalWorkDone;
+
+ private:
+ // Takes ownership of signal_thread.
+ void SetSignalThread(T** signal_thread) {
+ ASSERT(!signal_thread_ && signal_thread && *signal_thread);
+ // Verify that no one is listening to the signal thread for work done.
+ // They should be using this class instead.
+ ASSERT((*signal_thread)->SignalWorkDone.is_empty());
+
+ signal_thread_ = *signal_thread;
+
+ // Helps callers not to use signal thread after this point since this class
+ // has taken ownership (and avoid the error of doing
+ // signal_thread->Start()).
+ *signal_thread = NULL;
+ }
+
+ void OnWorkDone(talk_base::SignalThread* signal_thread) {
+ ASSERT(signal_thread == signal_thread_);
+ finished_ = true;
+ Wake();
+ }
+
+ void ClearSignalThread() {
+ if (signal_thread_) {
+ // Don't wait on the thread destruction, or we may deadlock.
+ signal_thread_->Destroy(false);
+ signal_thread_ = NULL;
+ }
+ }
+
+ T* signal_thread_;
+ bool finished_;
+ DISALLOW_COPY_AND_ASSIGN(SignalThreadTask);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
diff --git a/chrome/common/net/notifier/base/sigslotrepeater.h b/chrome/common/net/notifier/base/sigslotrepeater.h
new file mode 100644
index 0000000..45dfba1
--- /dev/null
+++ b/chrome/common/net/notifier/base/sigslotrepeater.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_SIGSLOTREPEATER_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_SIGSLOTREPEATER_H_
+
+// Repeaters are both signals and slots, which are designed as intermediate
+// pass-throughs for signals and slots which don't know about each other (for
+// modularity or encapsulation). This eliminates the need to declare a signal
+// handler whose sole purpose is to fire another signal. The repeater connects
+// to the originating signal using the 'repeat' method. When the repeated
+// signal fires, the repeater will also fire.
+
+#include "talk/base/sigslot.h"
+
+namespace sigslot {
+
+template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+class repeater0 : public signal0<mt_policy>,
+ public has_slots<mt_policy> {
+ public:
+ typedef signal0<mt_policy> base_type;
+ typedef repeater0<mt_policy> this_type;
+
+ repeater0() { }
+ explicit repeater0(const this_type& s) : base_type(s) { }
+
+ void reemit() { signal0<mt_policy>::emit(); }
+ void repeat(base_type &s) { s.connect(this, &this_type::reemit); }
+};
+
+template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+class repeater1 : public signal1<arg1_type, mt_policy>,
+ public has_slots<mt_policy> {
+ public:
+ typedef signal1<arg1_type, mt_policy> base_type;
+ typedef repeater1<arg1_type, mt_policy> this_type;
+
+ repeater1() { }
+ repeater1(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1) { signal1<arg1_type, mt_policy>::emit(a1); }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+};
+
+template<class arg1_type, class arg2_type,
+ class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+class repeater2 : public signal2<arg1_type, arg2_type, mt_policy>,
+ public has_slots<mt_policy> {
+ public:
+ typedef signal2<arg1_type, arg2_type, mt_policy> base_type;
+ typedef repeater2<arg1_type, arg2_type, mt_policy> this_type;
+
+ repeater2() { }
+ repeater2(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1, arg2_type a2) {
+ signal2<arg1_type, arg2_type, mt_policy>::emit(a1, a2);
+ }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+};
+
+template<class arg1_type, class arg2_type, class arg3_type,
+ class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+class repeater3 : public signal3<arg1_type, arg2_type, arg3_type, mt_policy>,
+ public has_slots<mt_policy> {
+ public:
+ typedef signal3<arg1_type, arg2_type, arg3_type, mt_policy> base_type;
+ typedef repeater3<arg1_type, arg2_type, arg3_type, mt_policy> this_type;
+
+ repeater3() { }
+ repeater3(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1, arg2_type a2, arg3_type a3) {
+ signal3<arg1_type, arg2_type, arg3_type, mt_policy>::emit(a1, a2, a3);
+ }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+};
+
+} // namespace sigslot
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_SIGSLOTREPEATER_H_
diff --git a/chrome/common/net/notifier/base/ssl_adapter.cc b/chrome/common/net/notifier/base/ssl_adapter.cc
new file mode 100644
index 0000000..9f12019
--- /dev/null
+++ b/chrome/common/net/notifier/base/ssl_adapter.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/ssl_adapter.h"
+
+#if defined(OS_WIN)
+#include "talk/base/ssladapter.h"
+#else
+#include "chrome/common/net/notifier/communicator/ssl_socket_adapter.h"
+#endif
+
+namespace notifier {
+
+talk_base::SSLAdapter* CreateSSLAdapter(talk_base::AsyncSocket* socket) {
+ talk_base::SSLAdapter* ssl_adapter =
+#if defined(OS_WIN)
+ talk_base::SSLAdapter::Create(socket);
+#else
+ notifier::SSLSocketAdapter::Create(socket);
+#endif
+ DCHECK(ssl_adapter);
+ return ssl_adapter;
+}
+
+} // namespace notifier
+
diff --git a/chrome/common/net/notifier/base/ssl_adapter.h b/chrome/common/net/notifier/base/ssl_adapter.h
new file mode 100644
index 0000000..bd39d3b
--- /dev/null
+++ b/chrome/common/net/notifier/base/ssl_adapter.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_SSL_ADAPTER_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_SSL_ADAPTER_H_
+
+namespace talk_base {
+class AsyncSocket;
+class SSLAdapter;
+} // namespace talk_base
+
+namespace notifier {
+
+// Wraps the given socket in a platform-dependent SSLAdapter
+// implementation.
+talk_base::SSLAdapter* CreateSSLAdapter(talk_base::AsyncSocket* socket);
+
+// Utility template class that overrides CreateSSLAdapter() to use the
+// above function.
+template <class SocketFactory>
+class SSLAdapterSocketFactory : public SocketFactory {
+ public:
+ virtual talk_base::SSLAdapter* CreateSSLAdapter(
+ talk_base::AsyncSocket* socket) {
+ return ::notifier::CreateSSLAdapter(socket);
+ }
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_SSL_ADAPTER_H_
+
diff --git a/chrome/common/net/notifier/base/static_assert.h b/chrome/common/net/notifier/base/static_assert.h
new file mode 100644
index 0000000..5ebf335f
--- /dev/null
+++ b/chrome/common/net/notifier/base/static_assert.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_STATIC_ASSERT_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_STATIC_ASSERT_H_
+
+template<bool> struct STATIC_ASSERTION_FAILURE;
+
+template<> struct STATIC_ASSERTION_FAILURE<true> {
+ enum { value = 1 };
+};
+
+template<int> struct static_assert_test{};
+
+#define STATIC_ASSERT(B) \
+typedef static_assert_test<\
+ sizeof(STATIC_ASSERTION_FAILURE< (bool)( B ) >)>\
+ static_assert_typedef_ ## __LINE__
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_STATIC_ASSERT_H_
diff --git a/chrome/common/net/notifier/base/task_pump.cc b/chrome/common/net/notifier/base/task_pump.cc
new file mode 100644
index 0000000..99254fb
--- /dev/null
+++ b/chrome/common/net/notifier/base/task_pump.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/task_pump.h"
+#include "chrome/common/net/notifier/base/time.h"
+#include "talk/base/common.h"
+#include "talk/base/thread.h"
+
+namespace notifier {
+
+// Don't add any messages because there are cleared and thrown away.
+enum { MSG_WAKE_UP = 1, MSG_TIMED_WAKE_UP };
+
+TaskPump::TaskPump() : timeout_change_count_(0), posted_(false) {
+}
+
+void TaskPump::OnMessage(talk_base::Message* msg) {
+ posted_ = false;
+
+ // If a task timed out, ensure that it is not blocked, so it will be deleted.
+ // This may result in a WakeTasks if a task is timed out.
+ PollTasks();
+
+ // Run tasks and handle timeouts.
+ RunTasks();
+}
+
+void TaskPump::WakeTasks() {
+ if (!posted_) {
+ // Do the requested wake up.
+ talk_base::Thread::Current()->Post(this, MSG_WAKE_UP);
+ posted_ = true;
+ }
+}
+
+int64 TaskPump::CurrentTime() {
+ return GetCurrent100NSTime();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/task_pump.h b/chrome/common/net/notifier/base/task_pump.h
new file mode 100644
index 0000000..b3dd4fa
--- /dev/null
+++ b/chrome/common/net/notifier/base/task_pump.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_TASK_PUMP_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_TASK_PUMP_H_
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/taskrunner.h"
+
+namespace notifier {
+
+class TaskPump : public talk_base::MessageHandler,
+ public talk_base::TaskRunner {
+ public:
+ TaskPump();
+
+ // MessageHandler interface.
+ virtual void OnMessage(talk_base::Message* msg);
+
+ // TaskRunner interface.
+ virtual void WakeTasks();
+ virtual int64 CurrentTime();
+
+ private:
+ int timeout_change_count_;
+ bool posted_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskPump);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_TASK_PUMP_H_
diff --git a/chrome/common/net/notifier/base/time.cc b/chrome/common/net/notifier/base/time.cc
new file mode 100644
index 0000000..1f07ebe
--- /dev/null
+++ b/chrome/common/net/notifier/base/time.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/time.h"
+
+#include <string>
+#include <time.h>
+
+#include "chrome/common/net/notifier/base/utils.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace notifier {
+
+char* GetLocalTimeAsString() {
+ time64 long_time = GetCurrent100NSTime();
+ struct tm now;
+ Time64ToTm(long_time, &now);
+ char* time_string = asctime(&now);
+ if (time_string) {
+ int time_len = strlen(time_string);
+ if (time_len > 0) {
+ time_string[time_len - 1] = 0; // trim off terminating \n.
+ }
+ }
+ return time_string;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/time.h b/chrome/common/net/notifier/base/time.h
new file mode 100644
index 0000000..77a68d4
--- /dev/null
+++ b/chrome/common/net/notifier/base/time.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_TIME_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_TIME_H_
+
+#include <time.h>
+
+#include "talk/base/basictypes.h"
+
+typedef uint64 time64;
+
+#define kMicrosecsTo100ns (static_cast<time64>(10))
+#define kMillisecsTo100ns (static_cast<time64>(10000))
+#define kSecsTo100ns (1000 * kMillisecsTo100ns)
+#define kMinsTo100ns (60 * kSecsTo100ns)
+#define kHoursTo100ns (60 * kMinsTo100ns)
+#define kDaysTo100ns (24 * kHoursTo100ns)
+const time64 kMaxTime100ns = UINT64_C(9223372036854775807);
+
+// Time difference in 100NS granularity between platform-dependent starting
+// time and Jan 1, 1970.
+#if defined(OS_WIN)
+// On Windows time64 is seconds since Jan 1, 1601.
+#define kStart100NsTimeToEpoch (116444736000000000uI64)
+#else
+// On Unix time64 is seconds since Jan 1, 1970.
+#define kStart100NsTimeToEpoch (0)
+#endif
+
+// Time difference in 100NS granularity between platform-dependent starting
+// time and Jan 1, 1980.
+#define kStart100NsTimeTo1980 \
+ kStart100NsTimeToEpoch + UINT64_C(3155328000000000)
+
+#define kTimeGranularity (kDaysTo100ns)
+
+namespace notifier {
+
+// Get the current time represented in 100NS granularity. Different platform
+// might return the value since different starting time. Win32 platform returns
+// the value since Jan 1, 1601.
+time64 GetCurrent100NSTime();
+
+// Convert from struct tm to time64.
+time64 TmToTime64(const struct tm& tm);
+
+// Convert from time64 to struct tm.
+bool Time64ToTm(time64 t, struct tm* tm);
+
+// Returns the local time as a string suitable for logging.
+// Note: This is *not* threadsafe, so only call it from the main thread.
+char* GetLocalTimeAsString();
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_TIME_H_
diff --git a/chrome/common/net/notifier/base/time_unittest.cc b/chrome/common/net/notifier/base/time_unittest.cc
new file mode 100644
index 0000000..bf0962a
--- /dev/null
+++ b/chrome/common/net/notifier/base/time_unittest.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/time.h"
+
+namespace notifier {
+
+TEST_NOTIFIER_F(TimeTest);
+
+TEST_F(TimeTest, UseLocalTimeAsString) {
+ // Just call it to ensure that it doesn't assert.
+ GetLocalTimeAsString();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/timer.cc b/chrome/common/net/notifier/base/timer.cc
new file mode 100644
index 0000000..96ec246
--- /dev/null
+++ b/chrome/common/net/notifier/base/timer.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/timer.h"
+
+namespace notifier {
+
+Timer::Timer(talk_base::Task* parent, int timeout_seconds, bool repeat)
+ : Task(parent),
+ repeat_(repeat) {
+
+ set_timeout_seconds(timeout_seconds);
+ Start();
+ ResumeTimeout();
+}
+
+Timer::~Timer() {
+}
+
+int Timer::OnTimeout() {
+ if (!repeat_) {
+ return STATE_DONE;
+ }
+ ResetTimeout();
+ return STATE_BLOCKED;
+}
+
+int Timer::ProcessStart() {
+ return STATE_BLOCKED;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/timer.h b/chrome/common/net/notifier/base/timer.h
new file mode 100644
index 0000000..b977d8e
--- /dev/null
+++ b/chrome/common/net/notifier/base/timer.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_BASE_TIMER_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_TIMER_H_
+
+#include "talk/base/task.h"
+
+namespace notifier {
+
+class Timer : private talk_base::Task {
+ public:
+ Timer(talk_base::Task* parent, int timeout_seconds, bool repeat);
+ ~Timer();
+
+ // Call Abort() to stop the timer.
+ using talk_base::Task::Abort;
+
+ // Call to find out when the timer is set to go off. Returns int64.
+ using talk_base::Task::get_timeout_time;
+
+ // Call to set the timeout interval.
+ using talk_base::Task::set_timeout_seconds;
+
+ using talk_base::Task::SignalTimeout;
+
+ private:
+ virtual int OnTimeout();
+ virtual int ProcessStart();
+
+ bool repeat_;
+
+ DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_TIMER_H_
diff --git a/chrome/common/net/notifier/base/utils.h b/chrome/common/net/notifier/base/utils.h
new file mode 100644
index 0000000..fc12943
--- /dev/null
+++ b/chrome/common/net/notifier/base/utils.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_BASE_UTILS_H_
+#define CHROME_COMMON_NET_NOTIFIER_BASE_UTILS_H_
+
+#include <map>
+#include <string>
+
+#include "chrome/common/net/notifier/base/static_assert.h"
+
+// Return error if the first argument evaluates to false.
+#define RET_IF_FALSE(x) do { if (!(x)) return false; } while (false)
+
+// Protocol constants.
+const char kHttpProto[] = "http://";
+const char kHttpsProto[] = "https://";
+
+// Initialize a POD to zero. Using this function requires discipline. Don't
+// use for types that have a v-table or virtual bases.
+template <typename T>
+inline void SetZero(T& p) {
+ // Guard against the easy mistake of
+ // foo(int *p) { SetZero(p); } instead of
+ // SetZero(*p);
+ // which it should be.
+ STATIC_ASSERT(sizeof(T) != sizeof(void*));
+
+ // A POD (plain old data) object has one of these data types:
+ // a fundamental type, union, struct, array,
+ // or class--with no constructor. PODs don't have virtual functions or
+ // virtual bases.
+
+ // Test to see if the type has constructors.
+ union CtorTest {
+ T t;
+ int i;
+ };
+
+ // TODO(sync) There might be a way to test if the type has virtuals.
+ // For now, if we zero a type with virtuals by mistake, it is going to crash
+ // predictable at run-time when the virtuals are called.
+ memset(&p, 0, sizeof(T));
+}
+
+// Used to delete each element in a vector<T*>/deque<T*> (and then empty the
+// sequence).
+template <class T>
+void CleanupSequence(T* items) {
+ for (typename T::iterator it(items->begin()); it != items->end(); ++it) {
+ delete (*it);
+ }
+ items->clear();
+}
+
+// Typically used to clean up values used in a hash_map that had Type* as
+// values.
+//
+// WARNING: This function assumes that T::clear will not access the values
+// (or the keys if they are the same as the values). This is true for
+// hash_map.
+template <class T>
+void CleanupMap(T* items) {
+ // This is likely slower than a for loop, but we have to do it this way. In
+ // some of the maps we use, deleting it->second causes it->first to be
+ // deleted as well, and that seems to send the iterator in a tizzy.
+ typename T::iterator it = items->begin();
+ while (it != items->end()) {
+ items->erase(it->first);
+ delete it->second;
+ it = items->begin();
+ }
+}
+
+// Get the value of an element in the map with the specified name.
+template <class T>
+void GetMapElement(const std::map<const std::string, const T>& m,
+ const char* name,
+ T* value) {
+ typename std::map<const std::string, const T>::const_iterator iter(
+ m.find(name));
+ if (iter != m.end()) {
+ *value = iter->second;
+ }
+}
+
+#endif // CHROME_COMMON_NET_NOTIFIER_BASE_UTILS_H_
diff --git a/chrome/common/net/notifier/base/win/async_network_alive_win32.cc b/chrome/common/net/notifier/base/win/async_network_alive_win32.cc
new file mode 100644
index 0000000..f3978dd
--- /dev/null
+++ b/chrome/common/net/notifier/base/win/async_network_alive_win32.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/base/async_network_alive.h"
+
+#include <winsock2.h>
+
+#include "base/scoped_handle_win.h"
+#include "chrome/common/net/notifier/base/utils.h"
+#include "talk/base/common.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace notifier {
+
+class PlatformNetworkInfo {
+ public:
+ PlatformNetworkInfo() : ws_handle_(NULL), event_handle_(NULL) {
+ }
+
+ ~PlatformNetworkInfo() {
+ Close();
+ }
+
+ void Close() {
+ talk_base::CritScope crit_scope(&crit_sect_);
+ if (ws_handle_) {
+ if (event_handle_.IsValid()) // Unblock any waiting for network changes.
+ SetEvent(event_handle_.Get());
+ // finishes the iteration.
+ VERIFY(WSALookupServiceEnd(ws_handle_) == 0);
+ ws_handle_ = NULL;
+ LOG(INFO) << "WSACleanup 1";
+ ::WSACleanup();
+ }
+ }
+
+ bool IsAlive(bool* error) {
+ ASSERT(error);
+ *error = false;
+
+ // If IsAlive was previously called, we need a new handle.
+ // Why? If we use the same handle, we only get diffs on what changed
+ // which isn't what we want.
+ Close();
+ int result = Initialize();
+ if (result != 0) {
+ LOG(ERROR) << "failed:" << result;
+ // Default to alive on error.
+ *error = true;
+ return true;
+ }
+
+ bool alive = false;
+
+ // Retrieve network info and move to next one. In this function, we only
+ // need to know whether or not there is network connection.
+ // allocate 256 bytes for name, it should be enough for most cases.
+ // If the name is longer, it is OK as we will check the code returned and
+ // set correct network status.
+ char result_buffer[sizeof(WSAQUERYSET) + 256] = {0};
+ bool flush_previous_result = false;
+ do {
+ DWORD control_flags = LUP_RETURN_NAME;
+ if (flush_previous_result) {
+ control_flags |= LUP_FLUSHPREVIOUS;
+ }
+ DWORD length = sizeof(result_buffer);
+ reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize =
+ sizeof(WSAQUERYSET);
+ // ws_handle_ may be NULL (if exiting), but the call will simply fail
+ int result = ::WSALookupServiceNext(
+ ws_handle_,
+ control_flags,
+ &length,
+ reinterpret_cast<WSAQUERYSET*>(&result_buffer[0]));
+
+ if (result == 0) {
+ // get at least one connection, return "connected".
+ alive = true;
+ } else {
+ ASSERT(result == SOCKET_ERROR);
+ result = ::WSAGetLastError();
+ if (result == WSA_E_NO_MORE || result == WSAENOMORE) {
+ break;
+ }
+
+ // Error code WSAEFAULT means there is a network connection but the
+ // result_buffer size is too small to contain the results. The
+ // variable "length" returned from WSALookupServiceNext is the minimum
+ // number of bytes required. We do not need to retrieve detail info.
+ // Return "alive" in this case.
+ if (result == WSAEFAULT) {
+ alive = true;
+ flush_previous_result = true;
+ } else {
+ LOG(WARNING) << "failed:" << result;
+ *error = true;
+ break;
+ }
+ }
+ } while (true);
+
+ LOG(INFO) << "alive: " << alive;
+ return alive;
+ }
+
+ bool WaitForChange() {
+ // IsAlive must be called first.
+ int junk1 = 0, junk2 = 0;
+ DWORD bytes_returned = 0;
+ int result = SOCKET_ERROR;
+ {
+ talk_base::CritScope crit_scope(&crit_sect_);
+ if (!ws_handle_)
+ return false;
+ ASSERT(!event_handle_.IsValid());
+ event_handle_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
+ if (!event_handle_.IsValid()) {
+ LOG(WARNING) << "failed to CreateEvent";
+ return false;
+ }
+ WSAOVERLAPPED overlapped = {0};
+ overlapped.hEvent = event_handle_.Get();
+ WSACOMPLETION completion;
+ ::SetZero(completion);
+ completion.Type = NSP_NOTIFY_EVENT;
+ completion.Parameters.Event.lpOverlapped = &overlapped;
+
+ LOG(INFO) << "calling WSANSPIoctl";
+ // Do a non-blocking request for change notification. event_handle_
+ // will get signaled when there is a change, so we wait on it later.
+ // It can also be signaled by Close() in order allow clean termination.
+ result = ::WSANSPIoctl(ws_handle_,
+ SIO_NSP_NOTIFY_CHANGE,
+ &junk1,
+ 0,
+ &junk2,
+ 0,
+ &bytes_returned,
+ &completion);
+ }
+ if (NO_ERROR != result) {
+ result = ::WSAGetLastError();
+ if (WSA_IO_PENDING != result) {
+ LOG(WARNING) << "failed: " << result;
+ event_handle_.Close();
+ return false;
+ }
+ }
+ LOG(INFO) << "waiting";
+ WaitForSingleObject(event_handle_.Get(), INFINITE);
+ event_handle_.Close();
+ LOG(INFO) << "changed";
+ return true;
+ }
+
+ private:
+ int Initialize() {
+ WSADATA wsa_data;
+ LOG(INFO) << "calling WSAStartup";
+ int result = ::WSAStartup(MAKEWORD(2, 2), &wsa_data);
+ if (result != ERROR_SUCCESS) {
+ LOG(ERROR) << "failed:" << result;
+ return result;
+ }
+
+ WSAQUERYSET query_set = {0};
+ query_set.dwSize = sizeof(WSAQUERYSET);
+ query_set.dwNameSpace = NS_NLA;
+ // Initiate a client query to iterate through the
+ // currently connected networks.
+ if (0 != ::WSALookupServiceBegin(&query_set, LUP_RETURN_ALL,
+ &ws_handle_)) {
+ result = ::WSAGetLastError();
+ LOG(INFO) << "WSACleanup 2";
+ ::WSACleanup();
+ ASSERT(ws_handle_ == NULL);
+ ws_handle_ = NULL;
+ return result;
+ }
+ return 0;
+ }
+ talk_base::CriticalSection crit_sect_;
+ HANDLE ws_handle_;
+ ScopedHandle event_handle_;
+ DISALLOW_COPY_AND_ASSIGN(PlatformNetworkInfo);
+};
+
+class AsyncNetworkAliveWin32 : public AsyncNetworkAlive {
+ public:
+ AsyncNetworkAliveWin32() {
+ }
+
+ virtual ~AsyncNetworkAliveWin32() {
+ if (network_info_) {
+ delete network_info_;
+ network_info_ = NULL;
+ }
+ }
+
+ protected:
+ // SignalThread Interface
+ virtual void DoWork() {
+ if (!network_info_) {
+ network_info_ = new PlatformNetworkInfo();
+ } else {
+ // Since network_info is set, it means that
+ // we are suppose to wait for network state changes.
+ if (!network_info_->WaitForChange()) {
+ // The wait was aborted so we must be shutting down.
+ alive_ = false;
+ error_ = true;
+ return;
+ }
+ }
+
+ if (network_info_->IsAlive(&error_)) {
+ // If there is an active connection, check that www.google.com:80
+ // is reachable.
+ talk_base::PhysicalSocketServer physical;
+ scoped_ptr<talk_base::Socket> socket(physical.CreateSocket(SOCK_STREAM));
+ if (socket->Connect(talk_base::SocketAddress("talk.google.com", 5222))) {
+ alive_ = false;
+ } else {
+ alive_ = true;
+ }
+ } else {
+ // If there are no available connections, then we aren't alive.
+ alive_ = false;
+ }
+ }
+
+ virtual void OnWorkStop() {
+ if (network_info_) {
+ network_info_->Close();
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsyncNetworkAliveWin32);
+};
+
+AsyncNetworkAlive* AsyncNetworkAlive::Create() {
+ return new AsyncNetworkAliveWin32();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/base/win/time_win32.cc b/chrome/common/net/notifier/base/win/time_win32.cc
new file mode 100644
index 0000000..f839ff8
--- /dev/null
+++ b/chrome/common/net/notifier/base/win/time_win32.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 2009 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.
+//
+// Windows specific time functions.
+
+#include <time.h>
+#include <windows.h>
+
+#include "chrome/common/net/notifier/base/time.h"
+
+#include "chrome/common/net/notifier/base/utils.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace notifier {
+
+time64 FileTimeToTime64(const FILETIME& file_time) {
+ return static_cast<time64>(file_time.dwHighDateTime) << 32 |
+ file_time.dwLowDateTime;
+}
+
+void Time64ToFileTime(const time64& time, FILETIME* ft) {
+ ASSERT(ft);
+
+ ft->dwHighDateTime = static_cast<DWORD>(time >> 32);
+ ft->dwLowDateTime = static_cast<DWORD>(time & 0xffffffff);
+}
+
+void TmTimeToSystemTime(const struct tm& tm, SYSTEMTIME* sys_time) {
+ ASSERT(sys_time);
+
+ SetZero(*sys_time);
+ // tm's year is 1900 based, systemtime's year is absolute.
+ sys_time->wYear = tm.tm_year + 1900;
+ // tm's month is 0 based, but systemtime's month is 1 based.
+ sys_time->wMonth = tm.tm_mon + 1;
+ sys_time->wDay = tm.tm_mday;
+ sys_time->wDayOfWeek = tm.tm_wday;
+ sys_time->wHour = tm.tm_hour;
+ sys_time->wMinute = tm.tm_min;
+ sys_time->wSecond = tm.tm_sec;
+}
+
+void SystemTimeToTmTime(const SYSTEMTIME& sys_time, struct tm* tm) {
+ ASSERT(tm);
+
+ SetZero(*tm);
+ // tm's year is 1900 based, systemtime's year is absolute.
+ tm->tm_year = sys_time.wYear - 1900;
+ // tm's month is 0 based, but systemtime's month is 1 based.
+ tm->tm_mon = sys_time.wMonth - 1;
+ tm->tm_mday = sys_time.wDay;
+ tm->tm_wday = sys_time.wDayOfWeek;
+ tm->tm_hour = sys_time.wHour;
+ tm->tm_min = sys_time.wMinute;
+ tm->tm_sec = sys_time.wSecond;
+}
+
+time64 GetCurrent100NSTime() {
+ // In order to get the 100ns time we shouldn't use SystemTime as it's
+ // granularity is 1 ms. Below is the correct implementation. On the other
+ // hand the system clock granularity is 15 ms, so we are not gaining much by
+ // having the timestamp in nano-sec. If we decide to go with ms, divide
+ // "time64 time" by 10000.
+ FILETIME file_time;
+ ::GetSystemTimeAsFileTime(&file_time);
+
+ time64 time = FileTimeToTime64(file_time);
+ return time;
+}
+
+time64 TmToTime64(const struct tm& tm) {
+ SYSTEMTIME sys_time;
+ TmTimeToSystemTime(tm, &sys_time);
+
+ FILETIME file_time;
+ SetZero(file_time);
+ if (!::SystemTimeToFileTime(&sys_time, &file_time)) {
+ return 0;
+ }
+
+ return FileTimeToTime64(file_time);
+}
+
+bool Time64ToTm(time64 t, struct tm* tm) {
+ ASSERT(tm);
+
+ FILETIME file_time;
+ SetZero(file_time);
+ Time64ToFileTime(t, &file_time);
+
+ SYSTEMTIME sys_time;
+ SetZero(sys_time);
+ if (!::FileTimeToSystemTime(&file_time, &sys_time)) {
+ return false;
+ }
+
+ SystemTimeToTmTime(sys_time, tm);
+
+ return true;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/auto_reconnect.cc b/chrome/common/net/notifier/communicator/auto_reconnect.cc
new file mode 100644
index 0000000..35ff386
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/auto_reconnect.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/auto_reconnect.h"
+
+#include "chrome/common/net/notifier/base/network_status_detector_task.h"
+#include "chrome/common/net/notifier/base/time.h"
+#include "chrome/common/net/notifier/base/timer.h"
+#include "talk/base/common.h"
+
+namespace notifier {
+
+const int kResetReconnectInfoDelaySec = 2;
+
+AutoReconnect::AutoReconnect(talk_base::Task* parent,
+ NetworkStatusDetectorTask* network_status)
+ : reconnect_interval_ns_(0),
+ reconnect_timer_(NULL),
+ delayed_reset_timer_(NULL),
+ parent_(parent),
+ is_idle_(false) {
+ SetupReconnectInterval();
+ if (network_status) {
+ network_status->SignalNetworkStateDetected.connect(
+ this, &AutoReconnect::OnNetworkStateDetected);
+ }
+}
+
+void AutoReconnect::OnNetworkStateDetected(bool was_alive, bool is_alive) {
+ if (is_retrying() && !was_alive && is_alive) {
+ // Reconnect in 1 to 9 seconds (vary the time a little to try to avoid
+ // spikey behavior on network hiccups).
+ StartReconnectTimerWithInterval((rand() % 9 + 1) * kSecsTo100ns);
+ }
+}
+
+int AutoReconnect::seconds_until() const {
+ if (!is_retrying() || !reconnect_timer_->get_timeout_time()) {
+ return 0;
+ }
+ int64 time_until_100ns =
+ reconnect_timer_->get_timeout_time() - GetCurrent100NSTime();
+ if (time_until_100ns < 0) {
+ return 0;
+ }
+
+ // Do a ceiling on the value (to avoid returning before its time).
+ int64 result = (time_until_100ns + kSecsTo100ns - 1) / kSecsTo100ns;
+ return static_cast<int>(result);
+}
+
+void AutoReconnect::StartReconnectTimer() {
+ StartReconnectTimerWithInterval(reconnect_interval_ns_);
+}
+
+void AutoReconnect::StartReconnectTimerWithInterval(time64 interval_ns) {
+ // Don't call StopReconnectTimer because we don't want other classes to
+ // detect that the intermediate state of the timer being stopped.
+ // (We're avoiding the call to SignalTimerStartStop while reconnect_timer_ is
+ // NULL).
+ if (reconnect_timer_) {
+ reconnect_timer_->Abort();
+ reconnect_timer_ = NULL;
+ }
+ reconnect_timer_ = new Timer(parent_,
+ static_cast<int>(interval_ns / kSecsTo100ns),
+ false); // Repeat.
+ reconnect_timer_->SignalTimeout.connect(this,
+ &AutoReconnect::DoReconnect);
+ SignalTimerStartStop();
+}
+
+void AutoReconnect::DoReconnect() {
+ reconnect_timer_ = NULL;
+
+ // If timed out again, double autoreconnect time up to 30 minutes.
+ reconnect_interval_ns_ *= 2;
+ if (reconnect_interval_ns_ > 30 * kMinsTo100ns) {
+ reconnect_interval_ns_ = 30 * kMinsTo100ns;
+ }
+ SignalStartConnection();
+}
+
+void AutoReconnect::StopReconnectTimer() {
+ if (reconnect_timer_) {
+ reconnect_timer_->Abort();
+ reconnect_timer_ = NULL;
+ SignalTimerStartStop();
+ }
+}
+
+void AutoReconnect::StopDelayedResetTimer() {
+ if (delayed_reset_timer_) {
+ delayed_reset_timer_->Abort();
+ delayed_reset_timer_ = NULL;
+ }
+}
+
+void AutoReconnect::ResetState() {
+ StopDelayedResetTimer();
+ StopReconnectTimer();
+ SetupReconnectInterval();
+}
+
+void AutoReconnect::SetupReconnectInterval() {
+ if (is_idle_) {
+ // If we were idle, start the timer over again (120 - 360 seconds).
+ reconnect_interval_ns_ = (rand() % 240 + 120) * kSecsTo100ns;
+ } else {
+ // If we weren't idle, try the connection 5 - 25 seconds later.
+ reconnect_interval_ns_ = (rand() % 20 + 5) * kSecsTo100ns;
+ }
+}
+
+void AutoReconnect::OnPowerSuspend(bool suspended) {
+ if (suspended) {
+ // When the computer comes back on, ensure that the reconnect happens
+ // quickly (5 - 25 seconds).
+ reconnect_interval_ns_ = (rand() % 20 + 5) * kSecsTo100ns;
+ }
+}
+
+void AutoReconnect::OnClientStateChange(Login::ConnectionState state) {
+ // On any state change, stop the reset timer.
+ StopDelayedResetTimer();
+ switch (state) {
+ case Login::STATE_RETRYING:
+ // Do nothing.
+ break;
+
+ case Login::STATE_CLOSED:
+ // When the user has been logged out and no auto-reconnect is happening,
+ // then the autoreconnect intervals should be reset.
+ ResetState();
+ break;
+
+ case Login::STATE_OPENING:
+ StopReconnectTimer();
+ break;
+
+ case Login::STATE_OPENED:
+ // Reset autoreconnect timeout sequence after being connected for a bit
+ // of time. This helps in the case that we are connecting briefly and
+ // then getting disconnect like when an account hits an abuse limit.
+ StopReconnectTimer();
+ delayed_reset_timer_ = new Timer(parent_,
+ kResetReconnectInfoDelaySec,
+ false); // Repeat.
+ delayed_reset_timer_->SignalTimeout.connect(this,
+ &AutoReconnect::ResetState);
+ break;
+ }
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/auto_reconnect.h b/chrome/common/net/notifier/communicator/auto_reconnect.h
new file mode 100644
index 0000000..549f322
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/auto_reconnect.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_
+
+#include <string>
+
+#include "chrome/common/net/notifier/base/time.h"
+#include "chrome/common/net/notifier/communicator/login.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+class Task;
+}
+
+namespace notifier {
+
+class NetworkStatusDetectorTask;
+class Timer;
+
+class AutoReconnect : public sigslot::has_slots<> {
+ public:
+ AutoReconnect(talk_base::Task* parent,
+ NetworkStatusDetectorTask* network_status);
+ void StartReconnectTimer();
+ void StopReconnectTimer();
+ void OnClientStateChange(Login::ConnectionState state);
+
+ // Callback when power is suspended.
+ void OnPowerSuspend(bool suspended);
+
+ void set_idle(bool idle) {
+ is_idle_ = idle;
+ }
+
+ // Returns true if the auto-retry is to be done (pending a countdown).
+ bool is_retrying() const {
+ return reconnect_timer_ != NULL;
+ }
+
+ int seconds_until() const;
+ sigslot::signal0<> SignalTimerStartStop;
+ sigslot::signal0<> SignalStartConnection;
+
+ private:
+ void StartReconnectTimerWithInterval(time64 interval_ns);
+ void DoReconnect();
+ void ResetState();
+ void SetupReconnectInterval();
+ void StopDelayedResetTimer();
+
+ void OnNetworkStateDetected(bool was_alive, bool is_alive);
+
+ time64 reconnect_interval_ns_;
+ Timer* reconnect_timer_;
+ Timer* delayed_reset_timer_;
+ talk_base::Task* parent_;
+
+ bool is_idle_;
+ DISALLOW_COPY_AND_ASSIGN(AutoReconnect);
+};
+
+// Wait 2 seconds until after we actually connect to reset reconnect related
+// items.
+//
+// The reason for this delay is to avoid the situation in which buzz is trying
+// to block the client due to abuse and the client responses by going into
+// rapid reconnect mode, which makes the problem more severe.
+extern const int kResetReconnectInfoDelaySec;
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_
diff --git a/chrome/common/net/notifier/communicator/connection_options.cc b/chrome/common/net/notifier/communicator/connection_options.cc
new file mode 100644
index 0000000..1e5b6e7
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/connection_options.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/connection_options.h"
+
+namespace notifier {
+
+ConnectionOptions::ConnectionOptions()
+ : autodetect_proxy_(true),
+ auto_reconnect_(true),
+ proxy_port_(0),
+ use_proxy_auth_(0),
+ allow_unverified_certs_(false) {
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/connection_options.h b/chrome/common/net/notifier/communicator/connection_options.h
new file mode 100644
index 0000000..b42023b
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/connection_options.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_
+
+#include <string>
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/helpers.h"
+
+namespace notifier {
+
+class ConnectionOptions {
+ public:
+ ConnectionOptions();
+
+ bool autodetect_proxy() const { return autodetect_proxy_; }
+ bool auto_reconnect() const { return auto_reconnect_; }
+ const std::string& proxy_host() const { return proxy_host_; }
+ int proxy_port() const { return proxy_port_; }
+ bool use_proxy_auth() const { return use_proxy_auth_; }
+ const std::string& auth_user() const { return auth_user_; }
+ const talk_base::CryptString& auth_pass() const { return auth_pass_; }
+ bool allow_unverified_certs() const { return allow_unverified_certs_; }
+
+ void set_autodetect_proxy(bool f) { autodetect_proxy_ = f; }
+ void set_auto_reconnect(bool f) { auto_reconnect_ = f; }
+ void set_proxy_host(const std::string& val) { proxy_host_ = val; }
+ void set_proxy_port(int val) { proxy_port_ = val; }
+ void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; }
+ void set_auth_user(const std::string& val) { auth_user_ = val; }
+ void set_auth_pass(const talk_base::CryptString& val) { auth_pass_ = val; }
+
+ // Setting this to true opens a security hole, so it is *highly* recommended
+ // that you don't do this.
+ void set_allow_unverified_certs(bool allow_unverified_certs) {
+ allow_unverified_certs_ = allow_unverified_certs;
+ }
+
+ private:
+ bool autodetect_proxy_;
+ bool auto_reconnect_;
+ std::string proxy_host_;
+ int proxy_port_;
+ bool use_proxy_auth_;
+ std::string auth_user_;
+ talk_base::CryptString auth_pass_;
+ bool allow_unverified_certs_;
+ // Allow the copy constructor and operator=.
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_
diff --git a/chrome/common/net/notifier/communicator/connection_settings.cc b/chrome/common/net/notifier/communicator/connection_settings.cc
new file mode 100644
index 0000000..c4d29a4
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/connection_settings.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2009 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 <algorithm>
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "chrome/common/net/notifier/communicator/connection_settings.h"
+#include "talk/base/helpers.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+
+class RandomGenerator {
+ public:
+ int operator()(int ceiling) {
+ return static_cast<int>(cricket::CreateRandomId() % ceiling);
+ }
+};
+
+void ConnectionSettings::FillXmppClientSettings(
+ buzz::XmppClientSettings* xcs) const {
+ assert(xcs);
+ xcs->set_protocol(protocol_);
+ xcs->set_server(server_);
+ xcs->set_proxy(proxy_.type);
+ if (proxy_.type != talk_base::PROXY_NONE) {
+ xcs->set_proxy_host(proxy_.address.IPAsString());
+ xcs->set_proxy_port(proxy_.address.port());
+ }
+ if ((proxy_.type != talk_base::PROXY_NONE) && !proxy_.username.empty()) {
+ xcs->set_use_proxy_auth(true);
+ xcs->set_proxy_user(proxy_.username);
+ xcs->set_proxy_pass(proxy_.password);
+ } else {
+ xcs->set_use_proxy_auth(false);
+ }
+}
+
+void ConnectionSettingsList::AddPermutations(const std::string& hostname,
+ const std::vector<uint32>& iplist,
+ int16 port,
+ bool special_port_magic,
+ bool proxy_only) {
+ // randomize the list. This ensures the iplist isn't always
+ // evaluated in the order returned by DNS
+ std::vector<uint32> iplist_random = iplist;
+ RandomGenerator rg;
+ std::random_shuffle(iplist_random.begin(), iplist_random.end(), rg);
+
+ // Put generated addresses in a new deque, then append on the list_, since
+ // there are order dependencies and AddPermutations() may be called more
+ // than once.
+ std::deque<ConnectionSettings> list_temp;
+
+ // Permute addresses for this server. In some cases we haven't resolved the
+ // to ip addresses.
+ talk_base::SocketAddress server(hostname, port, false);
+ if (iplist_random.empty()) {
+ // We couldn't pre-resolve the hostname, so let's hope it will resolve
+ // further down the pipeline (by a proxy, for example).
+ PermuteForAddress(server, special_port_magic, proxy_only, &list_temp);
+ } else {
+ // Generate a set of possibilities for each server address.
+ // Don't do permute duplicates.
+ for (size_t index = 0; index < iplist_random.size(); ++index) {
+ if (std::find(iplist_seen_.begin(), iplist_seen_.end(),
+ iplist_random[index]) != iplist_seen_.end()) {
+ continue;
+ }
+ iplist_seen_.push_back(iplist_random[index]);
+ server.SetResolvedIP(iplist_random[index]);
+ PermuteForAddress(server, special_port_magic, proxy_only, &list_temp);
+ }
+ }
+
+ // Add this list to the instance list
+ while (list_temp.size() != 0) {
+ list_.push_back(list_temp[0]);
+ list_temp.pop_front();
+ }
+}
+
+
+void ConnectionSettingsList::PermuteForAddress(
+ const talk_base::SocketAddress& server,
+ bool special_port_magic,
+ bool proxy_only,
+ std::deque<ConnectionSettings>* list_temp) {
+ assert(list_temp);
+ *(template_.mutable_server()) = server;
+
+ // Use all of the original settings
+ list_temp->push_back(template_);
+
+ // Try alternate port
+ if (special_port_magic) {
+ ConnectionSettings settings(template_);
+ settings.set_protocol(cricket::PROTO_SSLTCP);
+ settings.mutable_server()->SetPort(443);
+ // HTTPS proxies usually require port 443, so try it first
+ if ((template_.proxy().type == talk_base::PROXY_HTTPS) ||
+ (template_.proxy().type == talk_base::PROXY_UNKNOWN)) {
+ list_temp->push_front(settings);
+ } else {
+ list_temp->push_back(settings);
+ }
+ }
+
+ if (!proxy_only) {
+ // Try without the proxy
+ if (template_.proxy().type != talk_base::PROXY_NONE) {
+ ConnectionSettings settings(template_);
+ settings.mutable_proxy()->type = talk_base::PROXY_NONE;
+ list_temp->push_back(settings);
+
+ if (special_port_magic) {
+ settings.set_protocol(cricket::PROTO_SSLTCP);
+ settings.mutable_server()->SetPort(443);
+ list_temp->push_back(settings);
+ }
+ }
+ }
+}
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/connection_settings.h b/chrome/common/net/notifier/communicator/connection_settings.h
new file mode 100644
index 0000000..fc5ef4c
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/connection_settings.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+
+class ConnectionSettings {
+ public:
+ ConnectionSettings() : protocol_(cricket::PROTO_TCP) {}
+
+ cricket::ProtocolType protocol() { return protocol_; }
+ const talk_base::SocketAddress& server() const { return server_; }
+ const talk_base::ProxyInfo& proxy() const { return proxy_; }
+
+ void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; }
+ talk_base::SocketAddress* mutable_server() { return &server_; }
+ talk_base::ProxyInfo* mutable_proxy() { return &proxy_; }
+
+ void FillXmppClientSettings(buzz::XmppClientSettings* xcs) const;
+
+ private:
+ cricket::ProtocolType protocol_; // PROTO_TCP, PROTO_SSLTCP, etc.
+ talk_base::SocketAddress server_; // Server.
+ talk_base::ProxyInfo proxy_; // Proxy info.
+ // Need copy constructor due to use in stl deque.
+};
+
+class ConnectionSettingsList {
+ public:
+ ConnectionSettingsList() {}
+
+ void SetProxy(const talk_base::ProxyInfo& proxy) {
+ *(template_.mutable_proxy()) = proxy;
+ }
+
+ const talk_base::ProxyInfo& proxy() const {
+ return template_.proxy();
+ }
+
+ int GetCount() { return list_.size(); }
+ ConnectionSettings* GetSettings(size_t index) { return &list_[index]; }
+
+ void ClearPermutations() {
+ list_.clear();
+ iplist_seen_.clear();
+ }
+
+ void AddPermutations(const std::string& hostname,
+ const std::vector<uint32>& iplist,
+ int16 port,
+ bool special_port_magic,
+ bool proxy_only);
+ private:
+ void PermuteForAddress(const talk_base::SocketAddress& server,
+ bool special_port_magic,
+ bool proxy_only,
+ std::deque<ConnectionSettings>* list_temp);
+
+ ConnectionSettings template_;
+ std::deque<ConnectionSettings> list_;
+ std::vector<uint32> iplist_seen_;
+ DISALLOW_COPY_AND_ASSIGN(ConnectionSettingsList);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_
diff --git a/chrome/common/net/notifier/communicator/const_communicator.h b/chrome/common/net/notifier/communicator/const_communicator.h
new file mode 100644
index 0000000..0b0a18a
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/const_communicator.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_
+
+namespace notifier {
+// The default port for jabber/xmpp communications.
+const int kDefaultXmppPort = 5222;
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_
diff --git a/chrome/common/net/notifier/communicator/login.cc b/chrome/common/net/notifier/communicator/login.cc
new file mode 100644
index 0000000..0a8f12c
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/login.cc
@@ -0,0 +1,358 @@
+// Copyright (c) 2009 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 "chrome/common/net/notifier/communicator/login.h"
+
+#include "chrome/common/net/notifier/base/network_status_detector_task.h"
+#include "chrome/common/net/notifier/base/time.h"
+#include "chrome/common/net/notifier/base/timer.h"
+#include "chrome/common/net/notifier/communicator/auto_reconnect.h"
+#include "chrome/common/net/notifier/communicator/connection_options.h"
+#include "chrome/common/net/notifier/communicator/login_settings.h"
+#include "chrome/common/net/notifier/communicator/product_info.h"
+#include "chrome/common/net/notifier/communicator/single_login_attempt.h"
+#include "talk/base/common.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/logging.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 time64 kRedirectTimeoutNs = 5 * kMinsTo100ns;
+
+// Disconnect if network stays down for more than 10 seconds.
+static const int kDisconnectionDelaySecs = 10;
+
+Login::Login(talk_base::Task* parent,
+ const buzz::XmppClientSettings& user_settings,
+ const ConnectionOptions& options,
+ std::string lang,
+ ServerInformation* server_list,
+ int server_count,
+ NetworkStatusDetectorTask* network_status,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only,
+ bool previous_login_successful)
+ : login_settings_(new LoginSettings(user_settings,
+ options,
+ lang,
+ server_list,
+ server_count,
+ firewall,
+ proxy_only)),
+ single_attempt_(NULL),
+ successful_connection_(previous_login_successful),
+ parent_(parent),
+ state_(STATE_OPENING),
+ redirect_time_ns_(0),
+ redirect_port_(0),
+ unexpected_disconnect_occurred_(false),
+ reset_unexpected_timer_(NULL),
+ google_host_(user_settings.host()),
+ google_user_(user_settings.user()),
+ disconnect_timer_(NULL) {
+ if (!network_status) {
+ network_status = NetworkStatusDetectorTask::Create(parent_);
+ if (network_status) {
+ // On linux we don't have an implementation of NetworkStatusDetectorTask.
+ network_status->Start();
+ }
+ }
+
+ if (network_status) {
+ network_status->SignalNetworkStateDetected.connect(
+ this, &Login::OnNetworkStateDetected);
+ auto_reconnect_.reset(new AutoReconnect(parent_, network_status));
+ auto_reconnect_->SignalStartConnection.connect(this,
+ &Login::StartConnection);
+ auto_reconnect_->SignalTimerStartStop.connect(
+ this,
+ &Login::OnAutoReconnectTimerChange);
+ SignalClientStateChange.connect(auto_reconnect_.get(),
+ &AutoReconnect::OnClientStateChange);
+ SignalIdleChange.connect(auto_reconnect_.get(),
+ &AutoReconnect::set_idle);
+ SignalPowerSuspended.connect(auto_reconnect_.get(),
+ &AutoReconnect::OnPowerSuspend);
+ }
+}
+
+// Defined so that the destructors are executed here (and the corresponding
+// classes don't need to be included in the header file).
+Login::~Login() {
+ if (single_attempt_) {
+ single_attempt_->Abort();
+ single_attempt_ = NULL;
+ }
+}
+
+void Login::StartConnection() {
+ // If there is a server redirect, use it.
+ if (GetCurrent100NSTime() < redirect_time_ns_ + kRedirectTimeoutNs) {
+ // Override server/port with redirect values.
+ talk_base::SocketAddress server_override;
+ server_override.SetIP(redirect_server_, false);
+ ASSERT(redirect_port_ != 0);
+ server_override.SetPort(redirect_port_);
+ login_settings_->set_server_override(server_override);
+ } else {
+ login_settings_->clear_server_override();
+ }
+
+ if (single_attempt_) {
+ single_attempt_->Abort();
+ single_attempt_ = NULL;
+ }
+ single_attempt_ = new SingleLoginAttempt(parent_,
+ login_settings_.get(),
+ successful_connection_);
+
+ // Do the signaling hook-ups.
+ single_attempt_->SignalLoginFailure.connect(this, &Login::OnLoginFailure);
+ single_attempt_->SignalRedirect.connect(this, &Login::OnRedirect);
+ single_attempt_->SignalClientStateChange.connect(
+ this,
+ &Login::OnClientStateChange);
+ single_attempt_->SignalUnexpectedDisconnect.connect(
+ this,
+ &Login::OnUnexpectedDisconnect);
+ single_attempt_->SignalLogoff.connect(
+ this,
+ &Login::OnLogoff);
+ single_attempt_->SignalNeedAutoReconnect.connect(
+ this,
+ &Login::DoAutoReconnect);
+ SignalLogInput.repeat(single_attempt_->SignalLogInput);
+ SignalLogOutput.repeat(single_attempt_->SignalLogOutput);
+
+ single_attempt_->Start();
+}
+
+const std::string& Login::google_host() const {
+ return google_host_;
+}
+
+const std::string& Login::google_user() const {
+ return google_user_;
+}
+
+const talk_base::ProxyInfo& Login::proxy() const {
+ return proxy_info_;
+}
+
+void Login::OnLoginFailure(const LoginFailure& failure) {
+ auto_reconnect_->StopReconnectTimer();
+ HandleClientStateChange(STATE_CLOSED);
+ SignalLoginFailure(failure);
+}
+
+void Login::OnLogoff() {
+ HandleClientStateChange(STATE_CLOSED);
+}
+
+void Login::OnClientStateChange(buzz::XmppEngine::State state) {
+ ConnectionState new_state = STATE_CLOSED;
+
+ switch (state) {
+ case buzz::XmppEngine::STATE_NONE:
+ case buzz::XmppEngine::STATE_CLOSED:
+ // Ignore the closed state (because we may be trying the next dns entry).
+ //
+ // But we go to this state for other signals when there is no retry
+ // happening.
+ new_state = state_;
+ break;
+
+ case buzz::XmppEngine::STATE_START:
+ case buzz::XmppEngine::STATE_OPENING:
+ new_state = STATE_OPENING;
+ break;
+
+ case buzz::XmppEngine::STATE_OPEN:
+ new_state = STATE_OPENED;
+ break;
+
+ default:
+ ASSERT(false);
+ break;
+ }
+ HandleClientStateChange(new_state);
+}
+
+void Login::HandleClientStateChange(ConnectionState new_state) {
+ // Do we need to transition between the retrying and closed states?
+ if (auto_reconnect_.get() && auto_reconnect_->is_retrying()) {
+ if (new_state == STATE_CLOSED) {
+ new_state = STATE_RETRYING;
+ }
+ } else {
+ if (new_state == STATE_RETRYING) {
+ new_state = STATE_CLOSED;
+ }
+ }
+
+ if (new_state != state_) {
+ state_ = new_state;
+ if (reset_unexpected_timer_) {
+ reset_unexpected_timer_->Abort();
+ reset_unexpected_timer_ = NULL;
+ }
+
+ if (state_ == STATE_OPENED) {
+ successful_connection_ = true;
+
+ google_host_ = single_attempt_->xmpp_client()->jid().domain();
+ google_user_ = single_attempt_->xmpp_client()->jid().node();
+ proxy_info_ = single_attempt_->proxy();
+
+ reset_unexpected_timer_ = new Timer(parent_,
+ kResetReconnectInfoDelaySec,
+ false); // Repeat.
+ reset_unexpected_timer_->SignalTimeout.connect(
+ this,
+ &Login::ResetUnexpectedDisconnect);
+ }
+ SignalClientStateChange(state_);
+ }
+}
+
+void Login::OnAutoReconnectTimerChange() {
+ if (!single_attempt_ || !single_attempt_->xmpp_client()) {
+ HandleClientStateChange(STATE_CLOSED);
+ return;
+ }
+ OnClientStateChange(single_attempt_->xmpp_client()->GetState());
+}
+
+buzz::XmppClient* Login::xmpp_client() {
+ if (!single_attempt_) {
+ return NULL;
+ }
+ return single_attempt_->xmpp_client();
+}
+
+int Login::seconds_until_reconnect() const {
+ return auto_reconnect_->seconds_until();
+}
+
+void Login::UseNextConnection() {
+ if (!single_attempt_) {
+ // Just in case, there is an obscure case that causes this to get called
+ // when there is no single_attempt_.
+ return;
+ }
+ single_attempt_->UseNextConnection();
+}
+
+void Login::UseCurrentConnection() {
+ if (!single_attempt_) {
+ // Just in case, there is an obscure case that causes this to get called
+ // when there is no single_attempt_.
+ return;
+ }
+ single_attempt_->UseCurrentConnection();
+}
+
+void Login::OnRedirect(const std::string& redirect_server, int redirect_port) {
+ ASSERT(redirect_port_ != 0);
+
+ redirect_time_ns_ = GetCurrent100NSTime();
+ redirect_server_ = redirect_server;
+ redirect_port_ = redirect_port;
+
+ // Drop the current connection, and start the login process again.
+ StartConnection();
+}
+
+void Login::OnUnexpectedDisconnect() {
+ if (reset_unexpected_timer_) {
+ reset_unexpected_timer_->Abort();
+ reset_unexpected_timer_ = NULL;
+ }
+
+ // Start the login process again.
+ if (unexpected_disconnect_occurred_) {
+ // If we already have received an unexpected disconnect recently, then our
+ // account may have be jailed due to abuse, so we shouldn't make the
+ // situation worse by trying really hard to reconnect. Instead, we'll do
+ // the autoreconnect route, which has exponential back-off.
+ DoAutoReconnect();
+ return;
+ }
+ StartConnection();
+ unexpected_disconnect_occurred_ = true;
+}
+
+void Login::ResetUnexpectedDisconnect() {
+ reset_unexpected_timer_ = NULL;
+ unexpected_disconnect_occurred_ = false;
+}
+
+void Login::DoAutoReconnect() {
+ bool allow_auto_reconnect =
+ login_settings_->connection_options().auto_reconnect();
+ // Start the reconnect time before aborting the connection to ensure that
+ // AutoReconnect::is_retrying() is true, so that the Login doesn't
+ // transition to the CLOSED state (which would cause the reconnection timer
+ // to reset and not double).
+ if (allow_auto_reconnect) {
+ auto_reconnect_->StartReconnectTimer();
+ }
+
+ if (single_attempt_) {
+ single_attempt_->Abort();
+ single_attempt_ = NULL;
+ }
+
+ if (!allow_auto_reconnect) {
+ HandleClientStateChange(STATE_CLOSED);
+ return;
+ }
+}
+
+void Login::OnNetworkStateDetected(bool was_alive, bool is_alive) {
+ if (was_alive && !is_alive) {
+ // Our network connection just went down. Setup a timer to disconnect.
+ // Don't disconnect immediately to avoid constant
+ // connection/disconnection due to flaky network interfaces.
+ ASSERT(disconnect_timer_ == NULL);
+ disconnect_timer_ = new Timer(parent_, kDisconnectionDelaySecs, false);
+ disconnect_timer_->SignalTimeout.connect(this,
+ &Login::OnDisconnectTimeout);
+ } else if (!was_alive && is_alive) {
+ // Our connection has come back up. If we have a disconnect timer going,
+ // abort it so we don't disconnect.
+ if (disconnect_timer_) {
+ disconnect_timer_->Abort();
+ // It will free itself.
+ disconnect_timer_ = NULL;
+ }
+ }
+}
+
+void Login::OnDisconnectTimeout() {
+ disconnect_timer_ = NULL;
+
+ if (state_ != STATE_OPENED) {
+ return;
+ }
+
+ if (single_attempt_) {
+ single_attempt_->Abort();
+ single_attempt_ = NULL;
+ }
+
+ StartConnection();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/login.h b/chrome/common/net/notifier/communicator/login.h
new file mode 100644
index 0000000..6ee8008
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/login.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_H_
+
+#include <string>
+
+#include "chrome/common/net/notifier/base/sigslotrepeater.h"
+#include "chrome/common/net/notifier/base/time.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+class XmppClient;
+class XmppEngine;
+class XmppClientSettings;
+} // namespace buzz
+
+namespace talk_base {
+class FirewallManager;
+struct ProxyInfo;
+class Task;
+} // namespace talk_base
+
+namespace notifier {
+
+class AutoReconnect;
+class ConnectionOptions;
+class LoginFailure;
+class LoginSettings;
+class NetworkStatusDetectorTask;
+struct ServerInformation;
+class SingleLoginAttempt;
+class Timer;
+
+// Does the login, keeps it alive (with refreshing cookies and reattempting
+// login when disconnected), figures out what actions to take on the various
+// errors that may occur.
+class Login : public sigslot::has_slots<> {
+ public:
+ // network_status and firewall may be NULL.
+ Login(talk_base::Task* parent,
+ const buzz::XmppClientSettings& user_settings,
+ const ConnectionOptions& options,
+ std::string lang,
+ ServerInformation* server_list,
+ int server_count,
+ NetworkStatusDetectorTask* network_status,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only,
+ bool previous_login_successful);
+ ~Login();
+
+ enum ConnectionState {
+ STATE_CLOSED,
+ // Same as the closed state but indicates that a countdown is happening for
+ // auto-retrying the connection.
+ STATE_RETRYING,
+ STATE_OPENING,
+ STATE_OPENED,
+ };
+
+ ConnectionState connection_state() const {
+ return state_;
+ }
+
+ void StartConnection();
+ void UseNextConnection();
+ void UseCurrentConnection();
+ buzz::XmppClient* xmpp_client();
+
+ // Start the auto-reconnect. It may not do the auto-reconnect if
+ // auto-reconnect is turned off.
+ void DoAutoReconnect();
+
+ const LoginSettings& login_settings() const {
+ return *(login_settings_.get());
+ }
+
+ // Returns the best guess at the host responsible for the account (which we
+ // use to determine if it is a dasher account or not).
+ //
+ // After login this may return a more accurate answer, which accounts for
+ // open sign-up accounts.
+ const std::string& google_host() const;
+
+ // Analogous to google_host but for the user account ("fred" in
+ // "fred@gmail.com").
+ const std::string& google_user() const;
+
+ // Returns the proxy that is being used to connect (or the default proxy
+ // information if all attempted connections failed).
+ //
+ // Do not call until StartConnection has been called.
+ const talk_base::ProxyInfo& proxy() const;
+
+ int seconds_until_reconnect() const;
+
+ // SignalClientStateChange(ConnectionState new_state);
+ sigslot::signal1<ConnectionState> SignalClientStateChange;
+
+ sigslot::signal1<const LoginFailure&> SignalLoginFailure;
+ sigslot::repeater2<const char*, int> SignalLogInput;
+ sigslot::repeater2<const char*, int> SignalLogOutput;
+ sigslot::repeater1<bool> SignalIdleChange;
+
+ // The creator should hook this up to a signal that indicates when the power
+ // is being suspended.
+ sigslot::repeater1<bool> SignalPowerSuspended;
+
+ private:
+ void OnRedirect(const std::string& redirect_server, int redirect_port);
+ void OnUnexpectedDisconnect();
+ void OnClientStateChange(buzz::XmppEngine::State state);
+ void OnLoginFailure(const LoginFailure& failure);
+ void OnLogoff();
+ void OnAutoReconnectTimerChange();
+
+ void HandleClientStateChange(ConnectionState new_state);
+ void ResetUnexpectedDisconnect();
+
+ void OnNetworkStateDetected(bool was_alive, bool is_alive);
+ void OnDisconnectTimeout();
+
+ scoped_ptr<LoginSettings> login_settings_;
+ scoped_ptr<AutoReconnect> auto_reconnect_;
+ SingleLoginAttempt* single_attempt_;
+ bool successful_connection_;
+ talk_base::Task* parent_;
+
+ ConnectionState state_;
+
+ // server redirect information
+ time64 redirect_time_ns_;
+ std::string redirect_server_;
+ int redirect_port_;
+ bool unexpected_disconnect_occurred_;
+ Timer* reset_unexpected_timer_;
+ std::string google_host_;
+ std::string google_user_;
+ talk_base::ProxyInfo proxy_info_;
+
+ Timer* disconnect_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(Login);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_H_
diff --git a/chrome/common/net/notifier/communicator/login_failure.cc b/chrome/common/net/notifier/communicator/login_failure.cc
new file mode 100644
index 0000000..22c0652
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/login_failure.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/login_failure.h"
+
+namespace notifier {
+
+LoginFailure::LoginFailure(LoginError error)
+ : error_(error),
+ xmpp_error_(buzz::XmppEngine::ERROR_NONE),
+ subcode_(0) {
+}
+
+LoginFailure::LoginFailure(LoginError error,
+ buzz::XmppEngine::Error xmpp_error,
+ int subcode)
+ : error_(error),
+ xmpp_error_(xmpp_error),
+ subcode_(subcode) {
+}
+
+buzz::XmppEngine::Error LoginFailure::xmpp_error() const {
+ ASSERT(error_ == XMPP_ERROR);
+ return xmpp_error_;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/login_failure.h b/chrome/common/net/notifier/communicator/login_failure.h
new file mode 100644
index 0000000..89c629b
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/login_failure.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_
+
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+class LoginFailure {
+ public:
+ enum LoginError {
+ // Check the xmpp_error for more information.
+ XMPP_ERROR,
+
+ // If the certificate has expired, it usually means that the computer's
+ // clock isn't set correctly.
+ CERTIFICATE_EXPIRED_ERROR,
+
+ // Apparently, there is a proxy that needs authentication information.
+ PROXY_AUTHENTICATION_ERROR,
+ };
+
+ explicit LoginFailure(LoginError error);
+ LoginFailure(LoginError error,
+ buzz::XmppEngine::Error xmpp_error,
+ int subcode);
+
+ // Used as the first level of error information.
+ LoginError error() const {
+ return error_;
+ }
+
+ // Returns the XmppEngine only. Valid if and only if error() == XMPP_ERROR.
+ //
+ // Handler should mimic logic from PhoneWindow::ShowConnectionError
+ // (except that the DiagnoseConnectionError has already been done).
+ buzz::XmppEngine::Error xmpp_error() const;
+
+ private:
+ LoginError error_;
+ buzz::XmppEngine::Error xmpp_error_;
+ int subcode_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoginFailure);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_
diff --git a/chrome/common/net/notifier/communicator/login_settings.cc b/chrome/common/net/notifier/communicator/login_settings.cc
new file mode 100644
index 0000000..06ea8e7
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/login_settings.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2009 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 "chrome/common/net/notifier/communicator/login_settings.h"
+
+#include "chrome/common/net/notifier/communicator/connection_options.h"
+#include "chrome/common/net/notifier/communicator/xmpp_connection_generator.h"
+#include "talk/base/common.h"
+#include "talk/base/socketaddress.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+
+LoginSettings::LoginSettings(const buzz::XmppClientSettings& user_settings,
+ const ConnectionOptions& options,
+ std::string lang,
+ ServerInformation* server_list,
+ int server_count,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only)
+ : proxy_only_(proxy_only),
+ firewall_(firewall),
+ lang_(lang),
+ server_list_(new ServerInformation[server_count]),
+ server_count_(server_count),
+ user_settings_(new buzz::XmppClientSettings(user_settings)),
+ connection_options_(new ConnectionOptions(options)) {
+ // Note: firewall may be NULL.
+ ASSERT(server_list != 0);
+ ASSERT(server_count > 0);
+ for (int i = 0; i < server_count_; ++i) {
+ server_list_[i] = server_list[i];
+ }
+}
+
+// Defined so that the destructors are executed here (and the corresponding
+// classes don't need to be included in the header file).
+LoginSettings::~LoginSettings() {
+}
+
+void LoginSettings::set_server_override(
+ const talk_base::SocketAddress& server) {
+ server_override_.reset(new ServerInformation());
+ server_override_->server = server;
+ server_override_->special_port_magic = server_list_[0].special_port_magic;
+}
+
+void LoginSettings::clear_server_override() {
+ server_override_.reset();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/login_settings.h b/chrome/common/net/notifier/communicator/login_settings.h
new file mode 100644
index 0000000..2f05729
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/login_settings.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_
+#include <string>
+
+#include "chrome/common/net/notifier/communicator/xmpp_connection_generator.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace buzz {
+class XmppClientSettings;
+}
+
+namespace talk_base {
+class FirewallManager;
+class SocketAddress;
+}
+
+namespace notifier {
+class ConnectionOptions;
+struct ServerInformation;
+
+class LoginSettings {
+ public:
+ LoginSettings(const buzz::XmppClientSettings& user_settings,
+ const ConnectionOptions& options,
+ std::string lang,
+ ServerInformation* server_list,
+ int server_count,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only);
+
+ ~LoginSettings();
+
+ // Note: firewall() may return NULL.
+ //
+ // Could be a const method, but it allows
+ // modification of part (FirewallManager) of its state.
+ talk_base::FirewallManager* firewall() {
+ return firewall_;
+ }
+
+ bool proxy_only() const {
+ return proxy_only_;
+ }
+
+ const std::string& lang() const {
+ return lang_;
+ }
+
+ const ServerInformation* server_list() const {
+ return server_override_.get() ? server_override_.get() : server_list_.get();
+ }
+
+ int server_count() const {
+ return server_override_.get() ? 1 : server_count_;
+ }
+
+ const buzz::XmppClientSettings& user_settings() const {
+ return *user_settings_.get();
+ }
+
+ buzz::XmppClientSettings* modifiable_user_settings() {
+ return user_settings_.get();
+ }
+
+ const ConnectionOptions& connection_options() const {
+ return *connection_options_.get();
+ }
+
+ void set_server_override(const talk_base::SocketAddress& server);
+ void clear_server_override();
+
+ private:
+ bool proxy_only_;
+ talk_base::FirewallManager* firewall_;
+ std::string lang_;
+
+ talk_base::scoped_array<ServerInformation> server_list_;
+ int server_count_;
+ // Used to handle redirects
+ scoped_ptr<ServerInformation> server_override_;
+
+ scoped_ptr<buzz::XmppClientSettings> user_settings_;
+ scoped_ptr<ConnectionOptions> connection_options_;
+ DISALLOW_COPY_AND_ASSIGN(LoginSettings);
+};
+} // namespace notifier
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_
diff --git a/chrome/common/net/notifier/communicator/mailbox.cc b/chrome/common/net/notifier/communicator/mailbox.cc
new file mode 100644
index 0000000..aaf8db8
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/mailbox.cc
@@ -0,0 +1,675 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/mailbox.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <stack>
+#include <vector>
+
+#include "chrome/common/net/notifier/base/string.h"
+#include "chrome/common/net/notifier/base/utils.h"
+#include "chrome/common/net/notifier/communicator/xml_parse_helpers.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace notifier {
+
+// Labels are a list of strings seperated by a '|' character. The '|' character
+// is escaped with a backslash ('\\') and the backslash is also escaped with a
+// backslash.
+static void ParseLabelSet(const std::string& text,
+ MessageThread::StringSet* labels) {
+ const char* input_cur = text.c_str();
+ const char* input_end = input_cur + text.size();
+ char* result = new char[text.size() + 1];
+ char* next_write = result;
+
+ while (input_cur < input_end) {
+ if (*input_cur == '|') {
+ if (next_write != result) {
+ *next_write = '\0';
+ labels->insert(std::string(result));
+ next_write = result;
+ }
+ input_cur++;
+ continue;
+ }
+
+ if (*input_cur == '\\') {
+ // Skip a character in the input and break if we are at the end.
+ input_cur++;
+ if (input_cur >= input_end)
+ break;
+ }
+ *next_write = *input_cur;
+ next_write++;
+ input_cur++;
+ }
+
+ if (next_write != result) {
+ *next_write = '\0';
+ labels->insert(std::string(result));
+ }
+
+ delete [] result;
+}
+
+// -----------------------------------------------------------------------------
+
+std::string MailAddress::safe_name() const {
+ if (!name().empty()) {
+ return name();
+ }
+
+ if (!address().empty()) {
+ size_t at = address().find('@');
+ if (at == std::string::npos) {
+ return address();
+ }
+
+ if (at != 0) {
+ return address().substr(0, at);
+ }
+ }
+
+ return std::string("(unknown)");
+}
+
+// -----------------------------------------------------------------------------
+MessageThread::~MessageThread() {
+ Clear();
+}
+
+void MessageThread::Clear() {
+ delete labels_;
+ labels_ = NULL;
+
+ delete senders_;
+ senders_ = NULL;
+}
+
+MessageThread& MessageThread::operator=(const MessageThread& r) {
+ if (&r != this) {
+ Clear();
+ // Copy everything.
+ r.AssertValid();
+ thread_id_ = r.thread_id_;
+ date64_ = r.date64_;
+ message_count_ = r.message_count_;
+ personal_level_ = r.personal_level_;
+ subject_ = r.subject_;
+ snippet_ = r.snippet_;
+
+ if (r.labels_)
+ labels_ = new StringSet(*r.labels_);
+ else
+ labels_ = new StringSet;
+ if (r.senders_)
+ senders_ = new MailSenderList(*r.senders_);
+ else
+ senders_ = new MailSenderList;
+ }
+ AssertValid();
+ return *this;
+}
+
+MessageThread* MessageThread::CreateFromXML(
+ const buzz::XmlElement* src) {
+ MessageThread* info = new MessageThread();
+ if (!info || !info->InitFromXml(src)) {
+ delete info;
+ return NULL;
+ }
+ return info;
+}
+
+// Init from a chunk of XML.
+bool MessageThread::InitFromXml(const buzz::XmlElement* src) {
+ labels_ = new StringSet;
+ senders_ = new MailSenderList;
+
+ if (src->Name() != buzz::kQnMailThreadInfo)
+ return false;
+
+ if (!ParseInt64Attr(src, buzz::kQnMailTid, &thread_id_))
+ return false;
+ if (!ParseInt64Attr(src, buzz::kQnMailDate, &date64_))
+ return false;
+ if (!ParseIntAttr(src, buzz::kQnMailMessages, &message_count_))
+ return false;
+ if (!ParseIntAttr(src, buzz::kQnMailParticipation, &personal_level_))
+ return false;
+
+ const buzz::XmlElement* senders = src->FirstNamed(buzz::kQnMailSenders);
+ if (!senders)
+ return false;
+ for (const buzz::XmlElement* child = senders->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ if (child->Name() != buzz::kQnMailSender)
+ continue;
+ std::string address;
+ if (!ParseStringAttr(child, buzz::kQnMailAddress, &address))
+ continue;
+ std::string name;
+ ParseStringAttr(child, buzz::kQnMailName, &name);
+ bool originator = false;
+ ParseBoolAttr(child, buzz::kQnMailOriginator, &originator);
+ bool unread = false;
+ ParseBoolAttr(child, buzz::kQnMailUnread, &unread);
+
+ senders_->push_back(MailSender(name, address, unread, originator));
+ }
+
+ const buzz::XmlElement* labels = src->FirstNamed(buzz::kQnMailLabels);
+ if (!labels)
+ return false;
+ ParseLabelSet(labels->BodyText(), labels_);
+
+ const buzz::XmlElement* subject = src->FirstNamed(buzz::kQnMailSubject);
+ if (!subject)
+ return false;
+ subject_ = subject->BodyText();
+
+ const buzz::XmlElement* snippet = src->FirstNamed(buzz::kQnMailSnippet);
+ if (!snippet)
+ return false;
+ snippet_ = snippet->BodyText();
+
+ AssertValid();
+ return true;
+}
+
+bool MessageThread::starred() const {
+ return (labels_->find("^t") != labels_->end());
+}
+
+bool MessageThread::unread() const {
+ return (labels_->find("^u") != labels_->end());
+}
+
+#if defined(DEBUG)
+// Non-debug version is inline and empty.
+void MessageThread::AssertValid() const {
+ assert(thread_id_ != 0);
+ assert(senders_ != NULL);
+ // In some (odd) cases, gmail may send email with no sender.
+ // assert(!senders_->empty());
+ assert(message_count_ > 0);
+ assert(labels_ != NULL);
+}
+#endif
+
+MailBox* MailBox::CreateFromXML(const buzz::XmlElement* src) {
+ MailBox* mail_box = new MailBox();
+ if (!mail_box || !mail_box->InitFromXml(src)) {
+ delete mail_box;
+ return NULL;
+ }
+ return mail_box;
+}
+
+// Init from a chunk of XML.
+bool MailBox::InitFromXml(const buzz::XmlElement* src) {
+ if (src->Name() != buzz::kQnMailBox)
+ return false;
+
+ if (!ParseIntAttr(src, buzz::kQnMailTotalMatched, &mailbox_size_))
+ return false;
+
+ estimate_ = false;
+ ParseBoolAttr(src, buzz::kQnMailTotalEstimate, &estimate_);
+
+ first_index_ = 0;
+ ParseIntAttr(src, buzz::kQnMailFirstIndex, &first_index_);
+
+ result_time_ = 0;
+ ParseInt64Attr(src, buzz::kQnMailResultTime, &result_time_);
+
+ highest_thread_id_ = 0;
+
+ const buzz::XmlElement* thread_element = src->FirstNamed(buzz::kQnMailThreadInfo);
+ while (thread_element) {
+ MessageThread* thread = MessageThread::CreateFromXML(thread_element);
+ if (thread) {
+ if (thread->thread_id() > highest_thread_id_)
+ highest_thread_id_ = thread->thread_id();
+ threads_.push_back(MessageThreadPointer(thread));
+ }
+ thread_element = thread_element->NextNamed(buzz::kQnMailThreadInfo);
+ }
+ return true;
+}
+
+const size_t kMaxShortnameLength = 12;
+
+// Tip: If you extend this list of chars, do not include '-'.
+const char name_delim[] = " ,.:;\'\"()[]{}<>*@";
+
+class SenderFormatter {
+ public:
+ // sender should not be deleted while this class is in use.
+ SenderFormatter(const MailSender& sender,
+ const std::string& me_address)
+ : sender_(sender),
+ visible_(false),
+ short_format_(true),
+ space_(kMaxShortnameLength) {
+ me_ = talk_base::ascicmp(me_address.c_str(),
+ sender.address().c_str()) == 0;
+ }
+
+ bool visible() const {
+ return visible_;
+ }
+
+ bool is_unread() const {
+ return sender_.unread();
+ }
+
+ const std::string name() const {
+ return name_;
+ }
+
+ void set_short_format(bool short_format) {
+ short_format_ = short_format;
+ UpdateName();
+ }
+
+ void set_visible(bool visible) {
+ visible_ = visible;
+ UpdateName();
+ }
+
+ void set_space(size_t space) {
+ space_ = space;
+ UpdateName();
+ }
+
+ private:
+ // Attempt to shorten to the first word in a person's name We could revisit
+ // and do better at international punctuation, but this is what cricket did,
+ // and it should be removed soon when gmail does the notification instead of
+ // us forming it on the client.
+ static void ShortenName(std::string* name) {
+ size_t start = name->find_first_not_of(name_delim);
+ if (start != std::string::npos && start > 0) {
+ name->erase(0, start);
+ }
+ start = name->find_first_of(name_delim);
+ if (start != std::string::npos) {
+ name->erase(start);
+ }
+ }
+
+ void UpdateName() {
+ // Update the name if is going to be used.
+ if (!visible_) {
+ return;
+ }
+
+ if (me_) {
+ name_ = "me";
+ return;
+ }
+
+ if (sender_.name().empty() && sender_.address().empty()) {
+ name_ = "";
+ return;
+ }
+
+ name_ = sender_.name();
+ // Handle the case of no name or a name looks like an email address. When
+ // mail is sent to "Quality@example.com" <quality-team@example.com>, we
+ // shouldn't show "Quality@example.com" as the name. Instead, use the email
+ // address (without the @...)
+ if (name_.empty() || name_.find_first_of("@") != std::string::npos) {
+ name_ = sender_.address();
+ size_t at_index = name_.find_first_of("@");
+ if (at_index != std::string::npos) {
+ name_.erase(at_index);
+ }
+ } else if (short_format_) {
+ ShortenName(&name_);
+ }
+
+ if (name_.empty()) {
+ name_ = "(unknown)";
+ }
+
+ // Abbreviate if too long.
+ if (name_.size() > space_) {
+ name_.replace(space_ - 1, std::string::npos, ".");
+ }
+ }
+
+ const MailSender& sender_;
+ std::string name_;
+ bool visible_;
+ bool short_format_;
+ size_t space_;
+ bool me_;
+ DISALLOW_COPY_AND_ASSIGN(SenderFormatter);
+};
+
+const char kNormalSeparator[] = ",&nbsp;";
+const char kEllidedSeparator[] = "&nbsp;..";
+
+std::string FormatName(const std::string& name, bool bold) {
+ std::string formatted_name;
+ if (bold) {
+ formatted_name.append("<b>");
+ }
+ formatted_name.append(HtmlEncode(name));
+ if (bold) {
+ formatted_name.append("</b>");
+ }
+ return formatted_name;
+}
+
+class SenderFormatterList {
+ public:
+ // sender_list must not be deleted while this class is being used.
+ SenderFormatterList(const MailSenderList& sender_list,
+ const std::string& me_address)
+ : state_(INITIAL_STATE),
+ are_any_read_(false),
+ index_(-1),
+ first_unread_index_(-1) {
+ // Add all read messages.
+ const MailSender* originator = NULL;
+ bool any_unread = false;
+ for (size_t i = 0; i < sender_list.size(); ++i) {
+ if (sender_list[i].originator()) {
+ originator = &sender_list[i];
+ }
+ if (sender_list[i].unread()) {
+ any_unread = true;
+ continue;
+ }
+ are_any_read_ = true;
+ if (!sender_list[i].originator()) {
+ senders_.push_back(new SenderFormatter(sender_list[i],
+ me_address));
+ }
+ }
+
+ // There may not be an orignator (if there no senders).
+ if (originator) {
+ senders_.insert(senders_.begin(), new SenderFormatter(*originator,
+ me_address));
+ }
+
+ // Add all unread messages.
+ if (any_unread) {
+ // It should be rare, but there may be cases when all of the senders
+ // appear to have read the message.
+ first_unread_index_ = is_first_unread() ? 0 : senders_.size();
+ for (size_t i = 0; i < sender_list.size(); ++i) {
+ if (!sender_list[i].unread()) {
+ continue;
+ }
+ // Don't add the originator if it is already at the start of the
+ // "unread" list.
+ if (sender_list[i].originator() && is_first_unread()) {
+ continue;
+ }
+ senders_.push_back(new SenderFormatter(sender_list[i], me_address));
+ }
+ }
+ }
+
+ ~SenderFormatterList() {
+ CleanupSequence(&senders_);
+ }
+
+ std::string GetHtml(int space) {
+ index_ = -1;
+ state_ = INITIAL_STATE;
+ while (!added_.empty()) {
+ added_.pop();
+ }
+
+ // If there is only one sender, let it take up all of the space.
+ if (senders_.size() == 1) {
+ senders_[0]->set_space(space);
+ senders_[0]->set_short_format(false);
+ }
+
+ int length = 1;
+ // Add as many senders as we can in the given space. Computes the visible
+ // length at each iteration, but does not construct the actual html.
+ while (length < space && AddNextSender()) {
+ int new_length = ConstructHtml(is_first_unread(), NULL);
+ // Remove names to avoid truncating
+ // * if there will be at least 2 left or
+ // * if the spacing <= 2 characters per sender and there
+ // is at least one left.
+ if ((new_length > space &&
+ (visible_count() > 2 ||
+ (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) {
+ RemoveLastAddedSender();
+ break;
+ }
+ length = new_length;
+ }
+
+ if (length > space) {
+ int max = ComputeSpacePerSender(space);
+ for (size_t i = 0; i < senders_.size(); ++i) {
+ if (senders_[i]->visible()) {
+ senders_[i]->set_space(max);
+ }
+ }
+ }
+
+ // Now construct the actual html.
+ std::string html_list;
+ length = ConstructHtml(is_first_unread(), &html_list);
+ if (length > space) {
+ LOG(LS_WARNING) << "LENGTH: " << length << " exceeds "
+ << space << " " << html_list;
+ }
+ return html_list;
+ }
+
+ private:
+ int ComputeSpacePerSender(int space) const {
+ // Why the "- 2"? To allow for the " .. " which may occur after the names,
+ // and no matter what always allow at least 2 characters per sender.
+ return talk_base::_max<int>(space / visible_count() - 2, 2);
+ }
+
+ // Finds the next sender that should be added to the "from" list and sets it
+ // to visible.
+ //
+ // This method may be called until it returns false or until
+ // RemoveLastAddedSender is called.
+ bool AddNextSender() {
+ // The progression is:
+ // 1. Add the person who started the thread, which is the first message.
+ // 2. Add the first unread message (unless it has already been added).
+ // 3. Add the last message (unless it has already been added).
+ // 4. Add the message that is just before the last message processed
+ // (unless it has already been added).
+ // If there is no message (i.e. at index -1), return false.
+ //
+ // Typically, this method is called until it returns false or all of the
+ // space available is used.
+ switch (state_) {
+ case INITIAL_STATE:
+ state_ = FIRST_MESSAGE;
+ index_ = 0;
+ // If the server behaves odd and doesn't send us any senders, do
+ // something graceful.
+ if (senders_.size() == 0) {
+ return false;
+ }
+ break;
+
+ case FIRST_MESSAGE:
+ if (first_unread_index_ >= 0) {
+ state_ = FIRST_UNREAD_MESSAGE;
+ index_ = first_unread_index_;
+ break;
+ }
+ // Fall through.
+ case FIRST_UNREAD_MESSAGE:
+ state_ = LAST_MESSAGE;
+ index_ = senders_.size() - 1;
+ break;
+
+ case LAST_MESSAGE:
+ case PREVIOUS_MESSAGE:
+ state_ = PREVIOUS_MESSAGE;
+ index_--;
+ break;
+
+ case REMOVED_MESSAGE:
+ default:
+ ASSERT(false);
+ return false;
+ }
+
+ if (index_ < 0) {
+ return false;
+ }
+
+ if (!senders_[index_]->visible()) {
+ added_.push(index_);
+ senders_[index_]->set_visible(true);
+ }
+ return true;
+ }
+
+ // Makes the last added sender not visible.
+ //
+ // May be called while visible_count() > 0.
+ void RemoveLastAddedSender() {
+ state_ = REMOVED_MESSAGE;
+ int index = added_.top();
+ added_.pop();
+ senders_[index]->set_visible(false);
+ }
+
+ // Constructs the html of the SenderList and returns the length of the
+ // visible text.
+ //
+ // The algorithm simply walks down the list of Senders, appending the html
+ // for each visible sender, and adding ellipsis or commas in between,
+ // whichever is appropriate.
+ //
+ // html Filled with html. Maybe NULL if the html doesn't need to be
+ // constructed yet (useful for simply determining the length of the
+ // visible text).
+ //
+ // returns The approximate visible length of the html.
+ int ConstructHtml(bool first_is_unread,
+ std::string* html) const {
+ if (senders_.empty()) {
+ return 0;
+ }
+
+ int length = 0;
+
+ // The first is always visible.
+ const SenderFormatter* sender = senders_[0];
+ const std::string& originator_name = sender->name();
+ length += originator_name.length();
+ if (html) {
+ html->append(FormatName(originator_name, first_is_unread));
+ }
+
+ bool elided = false;
+ const char* between = "";
+ for (size_t i = 1; i < senders_.size(); i++) {
+ sender = senders_[i];
+
+ if (sender->visible()) {
+ // Handle the separator.
+ between = elided ? "&nbsp;" : kNormalSeparator;
+ // Ignore the , for length because it is so narrow, so in both cases
+ // above the space is the only things that counts for spaces.
+ length++;
+
+ // Handle the name.
+ const std::string name = sender->name();
+ length += name.size();
+
+ // Construct the html.
+ if (html) {
+ html->append(between);
+ html->append(FormatName(name, sender->is_unread()));
+ }
+ elided = false;
+ } else if (!elided) {
+ between = kEllidedSeparator;
+ length += 2; // ".." is narrow.
+ if (html) {
+ html->append(between);
+ }
+ elided = true;
+ }
+ }
+ return length;
+ }
+
+ bool is_first_unread() const {
+ return !are_any_read_;
+ }
+
+ size_t visible_count() const {
+ return added_.size();
+ }
+
+ enum MessageState {
+ INITIAL_STATE,
+ FIRST_MESSAGE,
+ FIRST_UNREAD_MESSAGE,
+ LAST_MESSAGE,
+ PREVIOUS_MESSAGE,
+ REMOVED_MESSAGE,
+ };
+
+ // What state we were in during the last "stateful" function call.
+ MessageState state_;
+ bool are_any_read_;
+ std::vector<SenderFormatter*> senders_;
+ std::stack<int> added_;
+ int index_;
+ int first_unread_index_;
+ DISALLOW_COPY_AND_ASSIGN(SenderFormatterList);
+};
+
+
+std::string GetSenderHtml(const MailSenderList& sender_list,
+ int message_count,
+ const std::string& me_address,
+ int space) {
+ // There has to be at least 9 spaces to show something reasonable.
+ ASSERT(space >= 10);
+ std::string count_html;
+ if (message_count > 1) {
+ std::string count(IntToString(message_count));
+ space -= (count.size());
+ count_html.append("&nbsp;(");
+ count_html.append(count);
+ count_html.append(")");
+ // Reduce space due to parenthesis and &nbsp;.
+ space -= 2;
+ }
+
+ SenderFormatterList senders(sender_list, me_address);
+ std::string html_list(senders.GetHtml(space));
+ html_list.append(count_html);
+ return html_list;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/mailbox.h b/chrome/common/net/notifier/communicator/mailbox.h
new file mode 100644
index 0000000..9aea0c7
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/mailbox.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_MAILBOX_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_MAILBOX_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/linked_ptr.h"
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace notifier {
+// -----------------------------------------------------------------------------
+class MailAddress {
+ public:
+ MailAddress(const std::string& name, const std::string& address)
+ : name_(name),
+ address_(address) {
+ }
+ const std::string& name() const { return name_; }
+ const std::string& address() const { return address_; }
+ std::string safe_name() const; // Will return *something*.
+ private:
+ std::string name_;
+ std::string address_;
+};
+
+// -----------------------------------------------------------------------------
+class MailSender : public MailAddress {
+ public:
+ MailSender(const std::string& name, const std::string& address, bool unread,
+ bool originator)
+ : MailAddress(name, address),
+ unread_(unread),
+ originator_(originator) {
+ }
+
+ MailSender(const MailSender& r)
+ : MailAddress(r.name(), r.address()) {
+ unread_ = r.unread_;
+ originator_ = r.originator_;
+ }
+
+ bool unread() const { return unread_; }
+ bool originator() const { return originator_; }
+
+ private:
+ bool unread_;
+ bool originator_;
+};
+
+typedef std::vector<MailSender> MailSenderList;
+
+// -----------------------------------------------------------------------------
+// MessageThread: everything there is to know about a mail thread.
+class MessageThread {
+ public:
+ MessageThread(const MessageThread& r) {
+ labels_ = NULL;
+ senders_ = NULL;
+ *this = r;
+ }
+
+ ~MessageThread();
+
+ // Try to parse the XML to create a MessageThreadInfo. If NULL is returned
+ // then we either ran out of memory or there was an error in parsing the XML.
+ static MessageThread* CreateFromXML(const buzz::XmlElement* src);
+
+ MessageThread& operator=(const MessageThread& r);
+
+ // SameThreadAs : name is self evident.
+ bool SameThreadAs(const MessageThread& r) {
+ AssertValid();
+ r.AssertValid();
+ return (thread_id_ == r.thread_id_);
+ }
+
+ // SameAs : true if thread has same id and messages.
+ // Assumes that messages don't disappear from threads.
+ bool SameAs(const MessageThread& r) {
+ AssertValid();
+ r.AssertValid();
+ return SameThreadAs(r) &&
+ message_count_ == r.message_count_;
+ }
+
+ typedef std::set<std::string> StringSet;
+
+ int64 thread_id() const { return thread_id_; }
+ const StringSet* labels() const { return labels_; }
+ int64 date64() const { return date64_; }
+ MailSenderList* senders() const { return senders_; }
+ int personal_level() const { return personal_level_; }
+ int message_count() const { return message_count_; }
+ const std::string& subject() const { return subject_; }
+ const std::string& snippet() const { return snippet_; }
+ bool starred() const;
+ bool unread() const;
+
+#if defined(DEBUG)
+ void AssertValid() const;
+#else
+ inline void AssertValid() const {}
+#endif
+
+ private:
+ void Clear();
+
+ private:
+ MessageThread() : senders_(NULL), labels_(NULL) {}
+ bool InitFromXml(const buzz::XmlElement* src);
+
+ int64 thread_id_;
+ int64 date64_;
+ int message_count_;
+ int personal_level_;
+ std::string subject_;
+ std::string snippet_;
+ MailSenderList* senders_;
+ StringSet* labels_;
+};
+
+typedef talk_base::linked_ptr<MessageThread> MessageThreadPointer;
+typedef std::vector<MessageThreadPointer> MessageThreadVector;
+
+// -----------------------------------------------------------------------------
+class MailBox {
+ public:
+ static MailBox* CreateFromXML(const buzz::XmlElement* src);
+
+ const MessageThreadVector& threads() const { return threads_; }
+ int mailbox_size() const { return mailbox_size_; }
+ int first_index() const { return first_index_; }
+ bool estimate() const { return estimate_; }
+ int64 result_time() const { return result_time_; }
+ int64 highest_thread_id() const { return highest_thread_id_; }
+
+ private:
+ MailBox() {}
+ bool InitFromXml(const buzz::XmlElement* src);
+
+ MessageThreadVector threads_;
+
+ int mailbox_size_;
+ int first_index_;
+ bool estimate_;
+ int64 result_time_;
+ int64 highest_thread_id_;
+};
+
+std::string GetSenderHtml(const MailSenderList& sender_list,
+ int message_count,
+ const std::string& me_address,
+ int space);
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_MAILBOX_H_
diff --git a/chrome/common/net/notifier/communicator/mailbox_unittest.cc b/chrome/common/net/notifier/communicator/mailbox_unittest.cc
new file mode 100644
index 0000000..7230766
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/mailbox_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/mailbox.h"
+
+namespace notifier {
+
+TEST_NOTIFIER_F(MailBoxTest);
+
+TEST_F(MailBoxTest, SingleSenderHtml) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(MailSender("Alex Smith", "a@a.com", true, true));
+ std::string sender_html = GetSenderHtml(sender_list, 1, me_address, 25);
+ ASSERT_STREQ("<b>Alex Smith</b>", sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, TruncatedSingleSenderHtml) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(MailSender(
+ "Alex Smith AReallyLongLastNameThatWillBeTruncated",
+ "a@a.com",
+ true,
+ true));
+ std::string sender_html = GetSenderHtml(sender_list, 1, me_address, 25);
+ ASSERT_STREQ("<b>Alex Smith AReallyLongLa.</b>", sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, SingleSenderManyTimesHtml) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(MailSender("Alex Smith", "a@a.com", true, true));
+ std::string sender_html = GetSenderHtml(sender_list, 10, me_address, 25);
+ ASSERT_STREQ("<b>Alex Smith</b>&nbsp;(10)", sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, SenderWithMeHtml) {
+ std::string me_address("randOm@comPany.Com");
+ MailSenderList sender_list;
+ sender_list.push_back(
+ MailSender("Alex Smith", "alex@jones.com", false, false));
+ sender_list.push_back(
+ MailSender("Your Name Goes Here", "raNdom@coMpany.cOm", true, true));
+ std::string sender_html = GetSenderHtml(sender_list, 5, me_address, 25);
+ ASSERT_STREQ("me,&nbsp;Alex,&nbsp;<b>me</b>&nbsp;(5)", sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, SenderHtmlWithAllUnread) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(
+ MailSender("Alex Smith", "alex@jones.com", true, false));
+ sender_list.push_back(MailSender(
+ "Your Name Goes Here",
+ "foo@company.com",
+ true,
+ true));
+ sender_list.push_back(MailSender("", "bob@davis.com", true, false));
+ std::string sender_html = GetSenderHtml(sender_list, 100, me_address, 25);
+ ASSERT_STREQ("<b>Your</b>,&nbsp;<b>Alex</b>,&nbsp;<b>bob</b>&nbsp;(100)",
+ sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, SenderHtmlWithTruncatedNames) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(MailSender(
+ "ReallyLongName Smith",
+ "alex@jones.com",
+ true,
+ false));
+ sender_list.push_back(MailSender(
+ "AnotherVeryLongFirstNameIsHere",
+ "foo@company.com",
+ true,
+ true));
+ std::string sender_html = GetSenderHtml(sender_list, 2, me_address, 25);
+ ASSERT_STREQ("<b>AnotherV.</b>,&nbsp;<b>ReallyLo.</b>&nbsp;(2)",
+ sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, SenderWithTwoSendersShowing) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(
+ MailSender("ALongishName Smith", "alex@jones.com", false, false));
+ sender_list.push_back(
+ MailSender("AnotherBigName", "no@company.com", true, false));
+ sender_list.push_back(
+ MailSender("Person1", "no1@company.com", true, false));
+ sender_list.push_back(
+ MailSender("Person2", "no2@company.com", false, true));
+ std::string sender_html = GetSenderHtml(sender_list, 6, me_address, 25);
+ ASSERT_STREQ("Person2&nbsp;..&nbsp;<b>AnotherB.</b>&nbsp;..&nbsp;(6)",
+ sender_html.c_str());
+}
+
+TEST_F(MailBoxTest, SenderWithThreeSendersShowing) {
+ std::string me_address("random@company.com");
+ MailSenderList sender_list;
+ sender_list.push_back(
+ MailSender("Joe Smith", "alex@jones.com", false, false));
+ sender_list.push_back(
+ MailSender("Bob Other", "no@company.com", true, false));
+ sender_list.push_back(
+ MailSender("Person0", "no0@company.com", true, false));
+ sender_list.push_back(
+ MailSender("Person1", "no1@company.com", true, false));
+ sender_list.push_back(
+ MailSender("ted", "ted@company.com", false, true));
+ std::string sender_html = GetSenderHtml(sender_list, 6, me_address, 25);
+ ASSERT_STREQ(
+ "ted&nbsp;..&nbsp;<b>Bob</b>&nbsp;..&nbsp;<b>Person1</b>&nbsp;(6)",
+ sender_html.c_str());
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/product_info.cc b/chrome/common/net/notifier/communicator/product_info.cc
new file mode 100644
index 0000000..c1deafb
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/product_info.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2009 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>
+
+namespace notifier {
+std::string GetUserAgentString() {
+ return kXmppProductName;
+}
+
+std::string GetProductSignature() {
+ return kXmppProductName;
+}
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/product_info.h b/chrome/common/net/notifier/communicator/product_info.h
new file mode 100644
index 0000000..6144122
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/product_info.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_
+
+#include <string>
+
+namespace notifier {
+std::string GetUserAgentString();
+std::string GetProductSignature();
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_
diff --git a/chrome/common/net/notifier/communicator/single_login_attempt.cc b/chrome/common/net/notifier/communicator/single_login_attempt.cc
new file mode 100644
index 0000000..939a07b
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/single_login_attempt.cc
@@ -0,0 +1,570 @@
+// Copyright (c) 2009 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 <algorithm>
+#include <string>
+#include <vector>
+
+#include "chrome/common/net/notifier/communicator/single_login_attempt.h"
+
+#include "base/logging.h"
+#include "chrome/common/net/notifier/communicator/connection_options.h"
+#include "chrome/common/net/notifier/communicator/connection_settings.h"
+#include "chrome/common/net/notifier/communicator/const_communicator.h"
+#include "chrome/common/net/notifier/communicator/login_failure.h"
+#include "chrome/common/net/notifier/communicator/login_settings.h"
+#include "chrome/common/net/notifier/communicator/product_info.h"
+#include "chrome/common/net/notifier/communicator/xmpp_connection_generator.h"
+#include "chrome/common/net/notifier/communicator/xmpp_socket_adapter.h"
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/winsock_initializer.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/saslcookiemechanism.h"
+#include "talk/xmpp/saslhandler.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppconstants.h"
+
+namespace notifier {
+
+static void GetClientErrorInformation(
+ buzz::XmppClient* client,
+ buzz::XmppEngine::Error* error,
+ int* subcode,
+ buzz::XmlElement** stream_error) {
+ ASSERT(client != NULL);
+ ASSERT(error && subcode && stream_error);
+
+ *error = client->GetError(subcode);
+
+ *stream_error = NULL;
+ if (*error == buzz::XmppEngine::ERROR_STREAM) {
+ const buzz::XmlElement* error_element = client->GetStreamError();
+ if (error_element) {
+ *stream_error = new buzz::XmlElement(*error_element);
+ }
+ }
+}
+
+namespace {
+
+const char kGaiaAuthMechanism[] = "X-GOOGLE-TOKEN";
+
+// This class looks for the X-GOOGLE-TOKEN auth mechanism and uses
+// that instead of the default auth mechanism (PLAIN).
+class GaiaOnlySaslHandler : public buzz::SaslHandler {
+ public:
+ GaiaOnlySaslHandler(
+ const std::string& username,
+ const std::string& token,
+ const std::string& token_service)
+ : username_(username),
+ token_(token),
+ token_service_(token_service) {}
+
+ virtual std::string ChooseBestSaslMechanism(
+ const std::vector<std::string> & mechanisms, bool encrypted) {
+ return (std::find(mechanisms.begin(),
+ mechanisms.end(), kGaiaAuthMechanism) !=
+ mechanisms.end()) ? kGaiaAuthMechanism : "";
+ }
+
+ virtual buzz::SaslMechanism* CreateSaslMechanism(
+ const std::string& mechanism) {
+ return
+ (mechanism == kGaiaAuthMechanism) ?
+ new buzz::SaslCookieMechanism(
+ kGaiaAuthMechanism, username_, token_, token_service_)
+ : NULL;
+ }
+
+ virtual bool GetTlsServerInfo(const talk_base::SocketAddress& server,
+ std::string* tls_server_hostname,
+ std::string* tls_server_domain) {
+ std::string server_ip = server.IPAsString();
+ if ((server_ip == buzz::STR_TALK_GOOGLE_COM) ||
+ (server_ip == buzz::STR_TALKX_L_GOOGLE_COM)) {
+ // For Gaia auth, the talk.google.com server expects you to use
+ // "gmail.com" in the stream, and expects the domain certificate
+ // to be "gmail.com" as well.
+ *tls_server_hostname = buzz::STR_GMAIL_COM;
+ *tls_server_domain = buzz::STR_GMAIL_COM;
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ std::string username_, token_, token_service_;
+};
+
+} // namespace
+
+SingleLoginAttempt::SingleLoginAttempt(talk_base::Task* parent,
+ LoginSettings* login_settings,
+ bool successful_connection)
+ : talk_base::Task(parent),
+ state_(buzz::XmppEngine::STATE_NONE),
+ code_(buzz::XmppEngine::ERROR_NONE),
+ subcode_(0),
+ need_authentication_(false),
+ certificate_expired_(false),
+ cookie_refreshed_(false),
+ successful_connection_(successful_connection),
+ login_settings_(login_settings),
+ client_(NULL) {
+#if defined(OS_WIN)
+ talk_base::EnsureWinsockInit();
+#endif
+ connection_generator_.reset(new XmppConnectionGenerator(
+ this,
+ &login_settings_->connection_options(),
+ login_settings_->proxy_only(),
+ login_settings_->server_list(),
+ login_settings_->server_count()));
+
+ connection_generator_->SignalExhaustedSettings.connect(
+ this,
+ &SingleLoginAttempt::OnAttemptedAllConnections);
+ connection_generator_->SignalNewSettings.connect(
+ this,
+ &SingleLoginAttempt::DoLogin);
+}
+
+SingleLoginAttempt::~SingleLoginAttempt() {
+ // If this assertion goes off, it means that "Stop()" didn't get called like
+ // it should have been.
+ ASSERT(client_ == NULL);
+}
+
+bool SingleLoginAttempt::auto_reconnect() const {
+ return login_settings_->connection_options().auto_reconnect();
+}
+
+const talk_base::ProxyInfo& SingleLoginAttempt::proxy() const {
+ ASSERT(connection_generator_.get());
+ return connection_generator_->proxy();
+}
+
+int SingleLoginAttempt::ProcessStart() {
+ ASSERT(GetState() == talk_base::Task::STATE_START);
+ connection_generator_->StartGenerating();
+
+ // After being started, this class is callback driven and does signaling from
+ // those callbacks (with checks to see if it is done if it may be called back
+ // from something that isn't a child task).
+ return talk_base::Task::STATE_BLOCKED;
+}
+
+void SingleLoginAttempt::Stop() {
+ ClearClient();
+ talk_base::Task::Stop();
+
+ // No more signals should happen after being stopped. This is needed because
+ // some of these signals happen due to other components doing signaling which
+ // may continue running even though this task is stopped.
+ SignalUnexpectedDisconnect.disconnect_all();
+ SignalRedirect.disconnect_all();
+ SignalLoginFailure.disconnect_all();
+ SignalNeedAutoReconnect.disconnect_all();
+ SignalClientStateChange.disconnect_all();
+}
+
+void SingleLoginAttempt::OnAttemptedAllConnections(
+ bool successfully_resolved_dns,
+ int first_dns_error) {
+
+ // Maybe we needed proxy authentication?
+ if (need_authentication_) {
+ LoginFailure failure(LoginFailure::PROXY_AUTHENTICATION_ERROR);
+ SignalLoginFailure(failure);
+ return;
+ }
+
+ if (certificate_expired_) {
+ LoginFailure failure(LoginFailure::CERTIFICATE_EXPIRED_ERROR);
+ SignalLoginFailure(failure);
+ return;
+ }
+
+ if (!successfully_resolved_dns) {
+ code_ = buzz::XmppEngine::ERROR_SOCKET;
+ subcode_ = first_dns_error;
+ }
+
+ LOG(INFO) << "Connection failed with error " << code_;
+
+ // We were connected and we had a problem.
+ if (successful_connection_ && auto_reconnect()) {
+ SignalNeedAutoReconnect();
+ // Expect to be deleted at this point.
+ return;
+ }
+
+ DiagnoseConnectionError();
+}
+
+void SingleLoginAttempt::UseNextConnection() {
+ ASSERT(connection_generator_.get() != NULL);
+ ClearClient();
+ connection_generator_->UseNextConnection();
+}
+
+void SingleLoginAttempt::UseCurrentConnection() {
+ ASSERT(connection_generator_.get() != NULL);
+ ClearClient();
+ connection_generator_->UseCurrentConnection();
+}
+
+void SingleLoginAttempt::DoLogin(
+ const ConnectionSettings& connection_settings) {
+ if (client_) {
+ return;
+ }
+
+ buzz::XmppClientSettings client_settings;
+ // Set the user settings portion.
+ *static_cast<buzz::XmppClientSettings*>(&client_settings) =
+ login_settings_->user_settings();
+ // Fill in the rest of the client settings.
+ connection_settings.FillXmppClientSettings(&client_settings);
+
+ client_ = new buzz::XmppClient(this);
+ SignalLogInput.repeat(client_->SignalLogInput);
+ SignalLogOutput.repeat(client_->SignalLogOutput);
+
+ // Listen for connection progress.
+ client_->SignalStateChange.connect(this,
+ &SingleLoginAttempt::OnClientStateChange);
+
+ // Transition to "start".
+ OnClientStateChange(buzz::XmppEngine::STATE_START);
+ // Start connecting.
+ client_->Connect(client_settings, login_settings_->lang(),
+ CreateSocket(client_settings),
+ NULL,
+ CreateSaslHandler(client_settings));
+ client_->Start();
+}
+
+void SingleLoginAttempt::OnAuthenticationError() {
+ // We can check this flag later if all connection options fail.
+ need_authentication_ = true;
+}
+
+void SingleLoginAttempt::OnCertificateExpired() {
+ // We can check this flag later if all connection options fail.
+ certificate_expired_ = true;
+}
+
+buzz::AsyncSocket* SingleLoginAttempt::CreateSocket(
+ const buzz::XmppClientSettings& xcs) {
+ bool allow_unverified_certs =
+ login_settings_->connection_options().allow_unverified_certs();
+ XmppSocketAdapter* adapter = new XmppSocketAdapter(xcs,
+ allow_unverified_certs);
+ adapter->SignalAuthenticationError.connect(
+ this,
+ &SingleLoginAttempt::OnAuthenticationError);
+ if (login_settings_->firewall()) {
+ adapter->set_firewall(true);
+ }
+ return adapter;
+}
+
+buzz::SaslHandler* SingleLoginAttempt::CreateSaslHandler(
+ const buzz::XmppClientSettings& xcs) {
+ buzz::Jid jid(xcs.user(), xcs.host(), buzz::STR_EMPTY);
+ return new GaiaOnlySaslHandler(
+ jid.Str(), xcs.auth_cookie(), xcs.token_service());
+}
+
+void SingleLoginAttempt::OnFreshAuthCookie(const std::string& auth_cookie) {
+ // Remember this is a fresh cookie.
+ cookie_refreshed_ = true;
+
+ // TODO(sync): do the cookie logic (part of which is in the #if 0 below).
+
+ // The following code is what PhoneWindow does for the equivalent method.
+#if 0
+ // Save cookie
+ AccountInfo current(account_history_.current());
+ current.set_auth_cookie(auth_cookie);
+ account_history_.set_current(current);
+
+ // Calc next time to refresh cookie, between 5 and 10 days. The cookie has
+ // 14 days of life; this gives at least 4 days of retries before the current
+ // cookie expires, maximizing the chance of having a valid cookie next time
+ // the connection servers go down.
+ FTULL now;
+
+ // NOTE: The following line is win32. Address this when implementing this
+ // code (doing "the cookie logic").
+ GetSystemTimeAsFileTime(&(now.ft));
+ ULONGLONG five_days = (ULONGLONG)10000 * 1000 * 60 * 60 * 24 * 5; // 5 days
+ ULONGLONG random = (ULONGLONG)10000 * // get to 100 ns units
+ ((rand() % (5 * 24 * 60)) * (60 * 1000) + // random min. in 5 day period
+ (rand() % 1000) * 60); // random 1/1000th of a minute
+ next_cookie_refresh_ = now.ull + five_days + random; // 5-10 days
+#endif
+}
+
+void SingleLoginAttempt::DiagnoseConnectionError() {
+ switch (code_) {
+ case buzz::XmppEngine::ERROR_MISSING_USERNAME:
+ case buzz::XmppEngine::ERROR_NETWORK_TIMEOUT:
+ case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
+ case buzz::XmppEngine::ERROR_BIND:
+ case buzz::XmppEngine::ERROR_AUTH:
+ case buzz::XmppEngine::ERROR_TLS:
+ case buzz::XmppEngine::ERROR_UNAUTHORIZED:
+ case buzz::XmppEngine::ERROR_VERSION:
+ case buzz::XmppEngine::ERROR_STREAM:
+ case buzz::XmppEngine::ERROR_XML:
+ case buzz::XmppEngine::ERROR_NONE:
+ default: {
+ LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_);
+ SignalLoginFailure(failure);
+ return;
+ }
+
+ // The following errors require diagnosistics:
+ // * spurious close of connection
+ // * socket errors after auth
+ case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
+ case buzz::XmppEngine::ERROR_SOCKET:
+ break;
+ }
+
+ talk_base::AsyncHttpRequest *http_request =
+ new talk_base::AsyncHttpRequest(GetUserAgentString());
+ http_request->set_host("www.google.com");
+ http_request->set_port(80);
+ http_request->set_secure(false);
+ http_request->request().path = "/";
+ http_request->request().verb = talk_base::HV_GET;
+
+ talk_base::ProxyInfo proxy;
+ ASSERT(connection_generator_.get() != NULL);
+ if (connection_generator_.get()) {
+ proxy = connection_generator_->proxy();
+ }
+ http_request->set_proxy(proxy);
+ http_request->set_firewall(login_settings_->firewall());
+
+ http_request->SignalWorkDone.connect(this,
+ &SingleLoginAttempt::OnHttpTestDone);
+ http_request->Start();
+ http_request->Release();
+}
+
+void SingleLoginAttempt::OnHttpTestDone(talk_base::SignalThread* thread) {
+ ASSERT(thread != NULL);
+
+ talk_base::AsyncHttpRequest* request =
+ static_cast<talk_base::AsyncHttpRequest*>(thread);
+
+ if (request->response().scode == 200) {
+ // We were able to do an HTTP GET of www.google.com:80
+
+ //
+ // The original error should be reported
+ //
+ LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_);
+ SignalLoginFailure(failure);
+ return;
+ }
+
+ // Otherwise lets transmute the error into ERROR_SOCKET, and put the subcode
+ // as an indicator of what we think the problem might be.
+
+#if 0
+ // TODO(sync): determine if notifier has an analogous situation.
+
+ //
+ // We weren't able to do an HTTP GET of www.google.com:80
+ //
+ GAutoupdater::Version version_logged_in(g_options.version_logged_in());
+ GAutoupdater::Version version_installed(GetProductVersion().c_str());
+ if (version_logged_in < version_installed) {
+ //
+ // Google Talk has been updated and can no longer connect to the Google
+ // Talk Service. Your firewall is probably not allowing the new version of
+ // Google Talk to connect to the internet. Please adjust your firewall
+ // settings to allow the new version of Google Talk to connect to the
+ // internet.
+ //
+ // We'll use the "error=1" to help figure this out for now.
+ //
+ LoginFailure failure(LoginFailure::XMPP_ERROR,
+ buzz::XmppEngine::ERROR_SOCKET,
+ 1);
+ SignalLoginFailure(failure);
+ return;
+ }
+#endif
+
+ //
+ // Any other checking we can add here?
+ //
+
+ //
+ // Google Talk is unable to use your internet connection. Either your network
+ // isn't configured or Google Talk is being blocked by a local firewall.
+ //
+ // We'll use the "error=0" to help figure this out for now
+ //
+ LoginFailure failure(LoginFailure::XMPP_ERROR,
+ buzz::XmppEngine::ERROR_SOCKET,
+ 0);
+ SignalLoginFailure(failure);
+}
+
+void SingleLoginAttempt::OnClientStateChange(buzz::XmppEngine::State state) {
+ if (state_ == state)
+ return;
+
+ buzz::XmppEngine::State previous_state = state_;
+ state_ = state;
+
+ switch (state) {
+ case buzz::XmppEngine::STATE_NONE:
+ case buzz::XmppEngine::STATE_START:
+ case buzz::XmppEngine::STATE_OPENING:
+ // Do nothing.
+ break;
+ case buzz::XmppEngine::STATE_OPEN:
+ successful_connection_ = true;
+ break;
+ case buzz::XmppEngine::STATE_CLOSED:
+ OnClientStateChangeClosed(previous_state);
+ break;
+ }
+ SignalClientStateChange(state);
+ if (state_ == buzz::XmppEngine::STATE_CLOSED) {
+ OnClientStateChange(buzz::XmppEngine::STATE_NONE);
+ }
+}
+
+void SingleLoginAttempt::ClearClient() {
+ if (client_ != NULL) {
+ client_->Disconnect();
+
+ // If this assertion goes off, it means that the disconnect didn't occur
+ // properly. See SingleLoginAttempt::OnClientStateChange,
+ // case XmppEngine::STATE_CLOSED
+ ASSERT(client_ == NULL);
+ }
+}
+
+void SingleLoginAttempt::OnClientStateChangeClosed(
+ buzz::XmppEngine::State previous_state) {
+ buzz::XmppEngine::Error error = buzz::XmppEngine::ERROR_NONE;
+ int error_subcode = 0;
+ buzz::XmlElement* stream_error_ptr;
+ GetClientErrorInformation(client_,
+ &error,
+ &error_subcode,
+ &stream_error_ptr);
+ scoped_ptr<buzz::XmlElement> stream_error(stream_error_ptr);
+
+ client_->SignalStateChange.disconnect(this);
+ client_ = NULL;
+
+ if (error == buzz::XmppEngine::ERROR_NONE) {
+ SignalLogoff();
+ return;
+ } else if (previous_state == buzz::XmppEngine::STATE_OPEN) {
+ // Handler should attempt reconnect.
+ SignalUnexpectedDisconnect();
+ return;
+ } else {
+ HandleConnectionError(error, error_subcode, stream_error.get());
+ }
+}
+
+void SingleLoginAttempt::HandleConnectionPasswordError() {
+ LOG(INFO) << "SingleLoginAttempt::HandleConnectionPasswordError";
+ LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_);
+ SignalLoginFailure(failure);
+}
+
+void SingleLoginAttempt::HandleConnectionError(
+ buzz::XmppEngine::Error code,
+ int subcode,
+ const buzz::XmlElement* stream_error) {
+ LOG(INFO) << "(" << code << ", " << subcode << ")";
+
+ // Save off the error code information, so we can use it to tell the user
+ // what went wrong if all else fails.
+ code_ = code;
+ subcode_ = subcode;
+ if ((code_ == buzz::XmppEngine::ERROR_UNAUTHORIZED) ||
+ (code_ == buzz::XmppEngine::ERROR_MISSING_USERNAME)) {
+ // There was a problem with credentials (username/password).
+ HandleConnectionPasswordError();
+ return;
+ }
+
+ // Unexpected disconnect,
+ // Unreachable host,
+ // Or internal server binding error -
+ // All these are temporary problems, so continue reconnecting.
+
+ // GaiaAuth signals this directly via SignalCertificateExpired, but
+ // SChannelAdapter propagates the error through SocketWindow as a socket
+ // error.
+ if (code_ == buzz::XmppEngine::ERROR_SOCKET &&
+ subcode_ == SEC_E_CERT_EXPIRED) {
+ certificate_expired_ = true;
+ }
+
+ login_settings_->modifiable_user_settings()->set_resource("");
+
+ // Look for stream::error server redirection stanza "see-other-host".
+ 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 std::string& redirect = text->BodyText();
+ size_t colon = redirect.find(":");
+ int redirect_port = kDefaultXmppPort;
+ std::string redirect_server;
+ if (colon == std::string::npos) {
+ redirect_server = redirect;
+ } else {
+ redirect_server = redirect.substr(0, colon);
+ const std::string& port_text = redirect.substr(colon + 1);
+ std::istringstream ist(port_text);
+ ist >> redirect_port;
+ }
+ // We never allow a redirect to port 0.
+ if (redirect_port == 0) {
+ redirect_port = kDefaultXmppPort;
+ }
+ SignalRedirect(redirect_server, redirect_port);
+ // May be deleted at this point.
+ return;
+ }
+ }
+ }
+
+ ASSERT(connection_generator_.get() != NULL);
+ if (!connection_generator_.get()) {
+ return;
+ }
+
+ // Iterate to the next possible connection (still trying to connect).
+ UseNextConnection();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/single_login_attempt.h b/chrome/common/net/notifier/communicator/single_login_attempt.h
new file mode 100644
index 0000000..0f4ac91
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/single_login_attempt.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_
+
+#include <string>
+
+#include "chrome/common/net/notifier/communicator/login.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+class AsyncSocket;
+class SaslHandler;
+class XmppClient;
+class XmppClientSettings;
+class XmppClientSettings;
+}
+
+namespace talk_base {
+class FirewallManager;
+struct ProxyInfo;
+class SignalThread;
+class Task;
+}
+
+namespace notifier {
+
+class ConnectionSettings;
+class LoginFailure;
+class LoginSettings;
+struct ServerInformation;
+class XmppConnectionGenerator;
+
+// Handles all of the aspects of a single login attempt (across multiple ip
+// addresses) and maintainence. By containing this within one class, when
+// another login attempt is made, this class will be disposed and all of the
+// signalling for the previous login attempt will be cleaned up immediately.
+//
+// This is a task to allow for cleaning this up when a signal is being fired.
+// Technically, delete this during the firing of a signal could work but it is
+// fragile.
+class SingleLoginAttempt : public talk_base::Task, public sigslot::has_slots<> {
+ public:
+ SingleLoginAttempt(talk_base::Task* parent,
+ LoginSettings* login_settings,
+ bool successful_connection);
+ ~SingleLoginAttempt();
+ virtual int ProcessStart();
+ void UseNextConnection();
+ void UseCurrentConnection();
+
+ buzz::XmppClient* xmpp_client() {
+ return client_;
+ }
+
+ // Returns the proxy that is being used to connect (or the default proxy
+ // information if all attempted connections failed).
+ const talk_base::ProxyInfo& proxy() const;
+
+ // Typically handled by creating a new SingleLoginAttempt and doing
+ // StartConnection.
+ sigslot::signal0<> SignalUnexpectedDisconnect;
+
+ // Typically handled by storing the redirect for 5 seconds, and setting it
+ // into LoginSettings, then creating a new SingleLoginAttempt, and doing
+ // StartConnection.
+ //
+ // SignalRedirect(const std::string& redirect_server, int redirect_port);
+ sigslot::signal2<const std::string&, int> SignalRedirect;
+
+ sigslot::signal0<> SignalNeedAutoReconnect;
+
+ // SignalClientStateChange(buzz::XmppEngine::State new_state);
+ sigslot::signal1<buzz::XmppEngine::State> SignalClientStateChange;
+
+ // See the LoginFailure for how to handle this.
+ sigslot::signal1<const LoginFailure&> SignalLoginFailure;
+
+ // Sent when there is a graceful log-off (state goes to closed with no
+ // error).
+ sigslot::signal0<> SignalLogoff;
+
+ sigslot::repeater2<const char*, int> SignalLogInput;
+ sigslot::repeater2<const char*, int> SignalLogOutput;
+
+ protected:
+ virtual void Stop();
+
+ private:
+ void DoLogin(const ConnectionSettings& connection_settings);
+ buzz::AsyncSocket* CreateSocket(const buzz::XmppClientSettings& xcs);
+ static buzz::SaslHandler* CreateSaslHandler(
+ const buzz::XmppClientSettings& xcs);
+
+ // Cleans up any xmpp client state to get ready for a new one.
+ void ClearClient();
+
+ void HandleConnectionError(
+ buzz::XmppEngine::Error code,
+ int subcode,
+ const buzz::XmlElement* stream_error);
+ void HandleConnectionPasswordError();
+
+ void DiagnoseConnectionError();
+ void OnHttpTestDone(talk_base::SignalThread* thread);
+
+ void OnAuthenticationError();
+ void OnCertificateExpired();
+ void OnFreshAuthCookie(const std::string& auth_cookie);
+ void OnClientStateChange(buzz::XmppEngine::State state);
+ void OnClientStateChangeClosed(buzz::XmppEngine::State previous_state);
+ void OnAttemptedAllConnections(bool successfully_resolved_dns,
+ int first_dns_error);
+
+ bool auto_reconnect() const;
+
+ buzz::XmppEngine::State state_;
+ buzz::XmppEngine::Error code_;
+ int subcode_;
+ bool need_authentication_;
+ bool certificate_expired_;
+ bool cookie_refreshed_;
+ bool successful_connection_;
+ LoginSettings* login_settings_;
+ buzz::XmppClient* client_;
+ scoped_ptr<XmppConnectionGenerator> connection_generator_;
+
+ DISALLOW_COPY_AND_ASSIGN(SingleLoginAttempt);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_
diff --git a/chrome/common/net/notifier/communicator/ssl_socket_adapter.cc b/chrome/common/net/notifier/communicator/ssl_socket_adapter.cc
new file mode 100644
index 0000000..1426864
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/ssl_socket_adapter.cc
@@ -0,0 +1,389 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/ssl_socket_adapter.h"
+
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/ssl_config_service.h"
+#include "net/base/sys_addrinfo.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/url_request/url_request_context.h"
+
+namespace notifier {
+
+namespace {
+
+// Convert values from <errno.h> to values from "net/base/net_errors.h"
+int MapPosixError(int err) {
+ // There are numerous posix error codes, but these are the ones we thus far
+ // find interesting.
+ switch (err) {
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return net::ERR_IO_PENDING;
+ case ENETDOWN:
+ return net::ERR_INTERNET_DISCONNECTED;
+ case ETIMEDOUT:
+ return net::ERR_TIMED_OUT;
+ case ECONNRESET:
+ case ENETRESET: // Related to keep-alive
+ return net::ERR_CONNECTION_RESET;
+ case ECONNABORTED:
+ return net::ERR_CONNECTION_ABORTED;
+ case ECONNREFUSED:
+ return net::ERR_CONNECTION_REFUSED;
+ case EHOSTUNREACH:
+ case ENETUNREACH:
+ return net::ERR_ADDRESS_UNREACHABLE;
+ case EADDRNOTAVAIL:
+ return net::ERR_ADDRESS_INVALID;
+ case 0:
+ return net::OK;
+ default:
+ LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
+ return net::ERR_FAILED;
+ }
+}
+
+} // namespace
+
+SSLSocketAdapter* SSLSocketAdapter::Create(AsyncSocket* socket) {
+ return new SSLSocketAdapter(socket);
+}
+
+SSLSocketAdapter::SSLSocketAdapter(AsyncSocket* socket)
+ : SSLAdapter(socket),
+ ignore_bad_cert_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ connected_callback_(this, &SSLSocketAdapter::OnConnected)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &SSLSocketAdapter::OnIO)),
+ ssl_connected_(false),
+ state_(STATE_NONE) {
+ transport_socket_ = new TransportSocket(socket, this);
+}
+
+int SSLSocketAdapter::StartSSL(const char* hostname, bool restartable) {
+ DCHECK(!restartable);
+ hostname_ = hostname;
+
+ if (socket_->GetState() != Socket::CS_CONNECTED) {
+ state_ = STATE_SSL_WAIT;
+ return 0;
+ } else {
+ return BeginSSL();
+ }
+}
+
+int SSLSocketAdapter::BeginSSL() {
+ if (!MessageLoop::current()) {
+ // Certificate verification is done via the Chrome message loop.
+ // Without this check, if we don't have a chrome message loop the
+ // SSL connection just hangs silently.
+ LOG(DFATAL) << "Chrome message loop (needed by SSL certificate "
+ << "verification) does not exist";
+ return net::ERR_UNEXPECTED;
+ }
+
+ // SSLConfigService is not thread-safe, and the default values for SSLConfig
+ // are correct for us, so we don't use the config service to initialize this
+ // object.
+ net::SSLConfig ssl_config;
+ transport_socket_->set_addr(talk_base::SocketAddress(hostname_.c_str()));
+ ssl_socket_.reset(
+ net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket(
+ transport_socket_, hostname_.c_str(), ssl_config));
+
+ int result = ssl_socket_->Connect(&connected_callback_);
+
+ if (result == net::ERR_IO_PENDING || result == net::OK) {
+ return 0;
+ } else {
+ LOG(ERROR) << "Could not start SSL: " << net::ErrorToString(result);
+ return result;
+ }
+}
+
+int SSLSocketAdapter::Send(const void* buf, size_t len) {
+ if (!ssl_connected_) {
+ return AsyncSocketAdapter::Send(buf, len);
+ } else {
+ scoped_refptr<net::IOBuffer> transport_buf = new net::IOBuffer(len);
+ memcpy(transport_buf->data(), buf, len);
+
+ int result = ssl_socket_->Write(transport_buf, len, NULL);
+ if (result == net::ERR_IO_PENDING) {
+ SetError(EWOULDBLOCK);
+ }
+ transport_buf = NULL;
+ return result;
+ }
+}
+
+int SSLSocketAdapter::Recv(void* buf, size_t len) {
+ if (!ssl_connected_) {
+ return AsyncSocketAdapter::Recv(buf, len);
+ }
+
+ switch (state_) {
+ case STATE_NONE: {
+ transport_buf_ = new net::IOBuffer(len);
+ int result = ssl_socket_->Read(transport_buf_, len, &io_callback_);
+ if (result >= 0) {
+ memcpy(buf, transport_buf_->data(), len);
+ }
+
+ if (result == net::ERR_IO_PENDING) {
+ state_ = STATE_READ;
+ SetError(EWOULDBLOCK);
+ } else {
+ if (result < 0) {
+ SetError(result);
+ LOG(INFO) << "Socket error " << result;
+ }
+ transport_buf_ = NULL;
+ }
+ return result;
+ }
+ case STATE_READ_COMPLETE:
+ memcpy(buf, transport_buf_->data(), len);
+ transport_buf_ = NULL;
+ state_ = STATE_NONE;
+ return data_transferred_;
+
+ case STATE_READ:
+ case STATE_WRITE:
+ case STATE_WRITE_COMPLETE:
+ case STATE_SSL_WAIT:
+ SetError(EWOULDBLOCK);
+ return -1;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ return -1;
+}
+
+void SSLSocketAdapter::OnConnected(int result) {
+ if (result == net::OK) {
+ ssl_connected_ = true;
+ OnConnectEvent(this);
+ } else {
+ LOG(WARNING) << "OnConnected failed with error " << result;
+ }
+}
+
+void SSLSocketAdapter::OnIO(int result) {
+ switch (state_) {
+ case STATE_READ:
+ state_ = STATE_READ_COMPLETE;
+ data_transferred_ = result;
+ AsyncSocketAdapter::OnReadEvent(this);
+ break;
+ case STATE_WRITE:
+ state_ = STATE_WRITE_COMPLETE;
+ data_transferred_ = result;
+ AsyncSocketAdapter::OnWriteEvent(this);
+ break;
+ case STATE_NONE:
+ case STATE_READ_COMPLETE:
+ case STATE_WRITE_COMPLETE:
+ case STATE_SSL_WAIT:
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void SSLSocketAdapter::OnReadEvent(talk_base::AsyncSocket* socket) {
+ if (!transport_socket_->OnReadEvent(socket))
+ AsyncSocketAdapter::OnReadEvent(socket);
+}
+
+void SSLSocketAdapter::OnWriteEvent(talk_base::AsyncSocket* socket) {
+ if (!transport_socket_->OnWriteEvent(socket))
+ AsyncSocketAdapter::OnWriteEvent(socket);
+}
+
+void SSLSocketAdapter::OnConnectEvent(talk_base::AsyncSocket* socket) {
+ if (state_ != STATE_SSL_WAIT) {
+ AsyncSocketAdapter::OnConnectEvent(socket);
+ } else {
+ state_ = STATE_NONE;
+ int result = BeginSSL();
+ if (0 != result) {
+ // TODO(zork): Handle this case gracefully.
+ LOG(WARNING) << "BeginSSL() failed with " << result;
+ }
+ }
+}
+
+TransportSocket::TransportSocket(talk_base::AsyncSocket* socket,
+ SSLSocketAdapter *ssl_adapter)
+ : connect_callback_(NULL),
+ read_callback_(NULL),
+ write_callback_(NULL),
+ read_buffer_len_(0),
+ write_buffer_len_(0),
+ socket_(socket) {
+ socket_->SignalConnectEvent.connect(this, &TransportSocket::OnConnectEvent);
+}
+
+int TransportSocket::Connect(net::CompletionCallback* callback) {
+ connect_callback_ = callback;
+ return socket_->Connect(addr_);
+}
+
+void TransportSocket::Disconnect() {
+ socket_->Close();
+}
+
+bool TransportSocket::IsConnected() const {
+ return (socket_->GetState() == talk_base::Socket::CS_CONNECTED);
+}
+
+bool TransportSocket::IsConnectedAndIdle() const {
+ // Not implemented.
+ NOTREACHED();
+ return false;
+}
+
+int TransportSocket::GetPeerAddress(net::AddressList* address) const {
+ talk_base::SocketAddress socket_address = socket_->GetRemoteAddress();
+
+ // libjingle supports only IPv4 addresses.
+ sockaddr_in ipv4addr;
+ socket_address.ToSockAddr(&ipv4addr);
+
+ struct addrinfo ai;
+ memset(&ai, 0, sizeof(ai));
+ ai.ai_family = ipv4addr.sin_family;
+ ai.ai_socktype = SOCK_STREAM;
+ ai.ai_protocol = IPPROTO_TCP;
+ ai.ai_addr = reinterpret_cast<struct sockaddr*>(&ipv4addr);
+ ai.ai_addrlen = sizeof(ipv4addr);
+
+ address->Copy(&ai, false);
+ return net::OK;
+}
+
+int TransportSocket::Read(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK(!read_callback_);
+ DCHECK(!read_buffer_.get());
+ int result = socket_->Recv(buf->data(), buf_len);
+ if (result < 0) {
+ result = MapPosixError(socket_->GetError());
+ if (result == net::ERR_IO_PENDING) {
+ read_callback_ = callback;
+ read_buffer_ = buf;
+ read_buffer_len_ = buf_len;
+ }
+ }
+ return result;
+}
+
+int TransportSocket::Write(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK(!write_callback_);
+ DCHECK(!write_buffer_.get());
+ int result = socket_->Send(buf->data(), buf_len);
+ if (result < 0) {
+ result = MapPosixError(socket_->GetError());
+ if (result == net::ERR_IO_PENDING) {
+ write_callback_ = callback;
+ write_buffer_ = buf;
+ write_buffer_len_ = buf_len;
+ }
+ }
+ return result;
+}
+
+bool TransportSocket::SetReceiveBufferSize(int32 size) {
+ // Not implemented.
+ return false;
+}
+
+bool TransportSocket::SetSendBufferSize(int32 size) {
+ // Not implemented.
+ return false;
+}
+
+void TransportSocket::OnConnectEvent(talk_base::AsyncSocket * socket) {
+ if (connect_callback_) {
+ net::CompletionCallback *callback = connect_callback_;
+ connect_callback_ = NULL;
+ callback->RunWithParams(Tuple1<int>(MapPosixError(socket_->GetError())));
+ } else {
+ LOG(WARNING) << "OnConnectEvent called with no callback.";
+ }
+}
+
+bool TransportSocket::OnReadEvent(talk_base::AsyncSocket* socket) {
+ if (read_callback_) {
+ DCHECK(read_buffer_.get());
+ net::CompletionCallback* callback = read_callback_;
+ scoped_refptr<net::IOBuffer> buffer = read_buffer_;
+ int buffer_len = read_buffer_len_;
+
+ read_callback_ = NULL;
+ read_buffer_ = NULL;
+ read_buffer_len_ = 0;
+
+ int result = socket_->Recv(buffer->data(), buffer_len);
+ if (result < 0) {
+ result = MapPosixError(socket_->GetError());
+ if (result == net::ERR_IO_PENDING) {
+ read_callback_ = callback;
+ read_buffer_ = buffer;
+ read_buffer_len_ = buffer_len;
+ return true;
+ }
+ }
+ callback->RunWithParams(Tuple1<int>(result));
+ return true;
+ } else {
+ LOG(WARNING) << "OnReadEvent called with no callback.";
+ return false;
+ }
+}
+
+bool TransportSocket::OnWriteEvent(talk_base::AsyncSocket* socket) {
+ if (write_callback_) {
+ DCHECK(write_buffer_.get());
+ net::CompletionCallback* callback = write_callback_;
+ scoped_refptr<net::IOBuffer> buffer = write_buffer_;
+ int buffer_len = write_buffer_len_;
+
+ write_callback_ = NULL;
+ write_buffer_ = NULL;
+ write_buffer_len_ = 0;
+
+ int result = socket_->Send(buffer->data(), buffer_len);
+ if (result < 0) {
+ result = MapPosixError(socket_->GetError());
+ if (result == net::ERR_IO_PENDING) {
+ write_callback_ = callback;
+ write_buffer_ = buffer;
+ write_buffer_len_ = buffer_len;
+ return true;
+ }
+ }
+ callback->RunWithParams(Tuple1<int>(result));
+ return true;
+ } else {
+ LOG(WARNING) << "OnWriteEvent called with no callback.";
+ return false;
+ }
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/ssl_socket_adapter.h b/chrome/common/net/notifier/communicator/ssl_socket_adapter.h
new file mode 100644
index 0000000..bbb0961
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/ssl_socket_adapter.h
@@ -0,0 +1,134 @@
+// 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_
+
+#include "base/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/socket/client_socket.h"
+#include "net/socket/ssl_client_socket.h"
+#include "talk/base/asyncsocket.h"
+#include "talk/base/ssladapter.h"
+
+namespace notifier {
+
+class SSLSocketAdapter;
+
+// This class provides a wrapper to libjingle's talk_base::AsyncSocket that
+// implements Chromium's net::ClientSocket interface. It's used by
+// SSLSocketAdapter to enable Chromium's SSL implementation to work over
+// libjingle's socket class.
+class TransportSocket : public net::ClientSocket, public sigslot::has_slots<> {
+ public:
+ TransportSocket(talk_base::AsyncSocket* socket,
+ SSLSocketAdapter *ssl_adapter);
+
+ void set_addr(const talk_base::SocketAddress& addr) {
+ addr_ = addr;
+ }
+
+ // net::ClientSocket implementation
+
+ virtual int Connect(net::CompletionCallback* callback);
+ virtual void Disconnect();
+ virtual bool IsConnected() const;
+ virtual bool IsConnectedAndIdle() const;
+ virtual int GetPeerAddress(net::AddressList* address) const;
+ virtual const net::BoundNetLog& NetLog() const { return net_log_; }
+
+ // net::Socket implementation
+
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
+ virtual bool SetReceiveBufferSize(int32 size);
+ virtual bool SetSendBufferSize(int32 size);
+
+ private:
+ friend class SSLSocketAdapter;
+
+ void OnConnectEvent(talk_base::AsyncSocket * socket);
+ bool OnReadEvent(talk_base::AsyncSocket * socket);
+ bool OnWriteEvent(talk_base::AsyncSocket * socket);
+
+ net::CompletionCallback* connect_callback_;
+ net::CompletionCallback* read_callback_;
+ net::CompletionCallback* write_callback_;
+
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ int read_buffer_len_;
+ scoped_refptr<net::IOBuffer> write_buffer_;
+ int write_buffer_len_;
+
+ net::BoundNetLog net_log_;
+
+ talk_base::AsyncSocket *socket_;
+ talk_base::SocketAddress addr_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportSocket);
+};
+
+// This provides a talk_base::AsyncSocketAdapter interface around Chromium's
+// net::SSLClientSocket class. This allows notifier to use Chromium's SSL
+// implementation instead of OpenSSL.
+class SSLSocketAdapter : public talk_base::SSLAdapter {
+ public:
+ explicit SSLSocketAdapter(talk_base::AsyncSocket* socket);
+
+ // StartSSL returns 0 if successful, or non-zero on failure.
+ // If StartSSL is called while the socket is closed or connecting, the SSL
+ // negotiation will begin as soon as the socket connects.
+ //
+ // restartable is not implemented, and must be set to false.
+ virtual int StartSSL(const char* hostname, bool restartable);
+
+ // Create the default SSL adapter for this platform.
+ static SSLSocketAdapter* Create(AsyncSocket* socket);
+
+ virtual int Send(const void* pv, size_t cb);
+ virtual int Recv(void* pv, size_t cb);
+
+ private:
+ friend class TransportSocket;
+
+ enum State {
+ STATE_NONE,
+ STATE_READ,
+ STATE_READ_COMPLETE,
+ STATE_WRITE,
+ STATE_WRITE_COMPLETE,
+ STATE_SSL_WAIT
+ };
+
+ void OnConnected(int result);
+ void OnIO(int result);
+
+ void OnReadEvent(talk_base::AsyncSocket * socket);
+ void OnWriteEvent(talk_base::AsyncSocket * socket);
+ void OnConnectEvent(talk_base::AsyncSocket * socket);
+
+ int BeginSSL();
+
+ bool ignore_bad_cert_;
+ std::string hostname_;
+ TransportSocket* transport_socket_;
+ scoped_ptr<net::SSLClientSocket> ssl_socket_;
+ net::CompletionCallbackImpl<SSLSocketAdapter> connected_callback_;
+ net::CompletionCallbackImpl<SSLSocketAdapter> io_callback_;
+ bool ssl_connected_;
+ State state_;
+ scoped_refptr<net::IOBuffer> transport_buf_;
+ int data_transferred_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLSocketAdapter);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_
diff --git a/chrome/common/net/notifier/communicator/xmpp_connection_generator.cc b/chrome/common/net/notifier/communicator/xmpp_connection_generator.cc
new file mode 100644
index 0000000..ff4355c
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/xmpp_connection_generator.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2009 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.
+//
+// XmppConnectionGenerator does the following algorithm:
+// proxy = ResolveProxyInformation(connection_options)
+// for server in server_list
+// get dns_addresses for server
+// connection_list = (dns_addresses X connection methods X proxy).shuffle()
+// for connection in connection_list
+// yield connection
+
+#include "chrome/common/net/notifier/communicator/xmpp_connection_generator.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "chrome/common/net/notifier/base/async_dns_lookup.h"
+#include "chrome/common/net/notifier/base/signal_thread_task.h"
+#include "chrome/common/net/notifier/communicator/connection_options.h"
+#include "chrome/common/net/notifier/communicator/connection_settings.h"
+#include "chrome/common/net/notifier/communicator/product_info.h"
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/task.h"
+#include "talk/base/thread.h"
+#include "talk/xmpp/prexmppauth.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+XmppConnectionGenerator::XmppConnectionGenerator(
+ talk_base::Task* parent,
+ const ConnectionOptions* options,
+ bool proxy_only,
+ const ServerInformation* server_list,
+ int server_count)
+ : settings_list_(new ConnectionSettingsList()),
+ settings_index_(0),
+ server_list_(new ServerInformation[server_count]),
+ server_count_(server_count),
+ server_index_(-1),
+ proxy_only_(proxy_only),
+ successfully_resolved_dns_(false),
+ first_dns_error_(0),
+ options_(options),
+ parent_(parent) {
+ assert(parent);
+ assert(options);
+ assert(server_count_ > 0);
+ for (int i = 0; i < server_count_; ++i) {
+ server_list_[i] = server_list[i];
+ }
+}
+
+XmppConnectionGenerator::~XmppConnectionGenerator() {
+ LOG(INFO) << "XmppConnectionGenerator::~XmppConnectionGenerator";
+}
+
+const talk_base::ProxyInfo& XmppConnectionGenerator::proxy() const {
+ assert(settings_list_.get());
+ if (settings_index_ >= settings_list_->GetCount()) {
+ return settings_list_->proxy();
+ }
+
+ ConnectionSettings* settings = settings_list_->GetSettings(settings_index_);
+ return settings->proxy();
+}
+
+// Starts resolving proxy information.
+void XmppConnectionGenerator::StartGenerating() {
+ LOG(INFO) << "XmppConnectionGenerator::StartGenerating";
+
+ talk_base::AutoDetectProxy* proxy_detect =
+ new talk_base::AutoDetectProxy(GetUserAgentString());
+
+ if (options_->autodetect_proxy()) {
+ // Pretend the xmpp server is https, when detecting whether a proxy is
+ // required to connect.
+ talk_base::Url<char> host_url("/",
+ server_list_[0].server.IPAsString().c_str(),
+ server_list_[0].server.port());
+ host_url.set_secure(true);
+ proxy_detect->set_server_url(host_url.url());
+ } else if (options_->proxy_host().length()) {
+ talk_base::SocketAddress proxy(options_->proxy_host(),
+ options_->proxy_port());
+ proxy_detect->set_proxy(proxy);
+ }
+ proxy_detect->set_auth_info(options_->use_proxy_auth(),
+ options_->auth_user(),
+ talk_base::CryptString(options_->auth_pass()));
+
+ SignalThreadTask<talk_base::AutoDetectProxy>* wrapper_task =
+ new SignalThreadTask<talk_base::AutoDetectProxy>(parent_, &proxy_detect);
+ wrapper_task->SignalWorkDone.connect(
+ this,
+ &XmppConnectionGenerator::OnProxyDetect);
+ wrapper_task->Start();
+}
+
+void XmppConnectionGenerator::OnProxyDetect(
+ talk_base::AutoDetectProxy* proxy_detect) {
+ LOG(INFO) << "XmppConnectionGenerator::OnProxyDetect";
+
+ ASSERT(settings_list_.get());
+ ASSERT(proxy_detect);
+ settings_list_->SetProxy(proxy_detect->proxy());
+
+ // Start iterating through the connections (which are generated on demand).
+ UseNextConnection();
+}
+
+void XmppConnectionGenerator::UseNextConnection() {
+ // Trying to connect.
+
+ // Iterate to the next possible connection.
+ settings_index_++;
+ if (settings_index_ < settings_list_->GetCount()) {
+ // We have more connection settings in the settings_list_ to try, kick off
+ // the next one.
+ UseCurrentConnection();
+ return;
+ }
+
+ // Iterate to the next possible server.
+ server_index_++;
+ if (server_index_ < server_count_) {
+ AsyncDNSLookup* dns_lookup = new AsyncDNSLookup(
+ server_list_[server_index_].server);
+ SignalThreadTask<AsyncDNSLookup>* wrapper_task =
+ new SignalThreadTask<AsyncDNSLookup>(parent_, &dns_lookup);
+ wrapper_task->SignalWorkDone.connect(
+ this,
+ &XmppConnectionGenerator::OnServerDNSResolved);
+ wrapper_task->Start();
+ return;
+ }
+
+ // All out of possibilities.
+ HandleExhaustedConnections();
+}
+
+void XmppConnectionGenerator::OnServerDNSResolved(
+ AsyncDNSLookup* dns_lookup) {
+ LOG(INFO) << "XmppConnectionGenerator::OnServerDNSResolved";
+
+ // Print logging info.
+ LOG(INFO) << " server: " <<
+ server_list_[server_index_].server.ToString() <<
+ " error: " << dns_lookup->error();
+ if (first_dns_error_ == 0 && dns_lookup->error() != 0) {
+ first_dns_error_ = dns_lookup->error();
+ }
+
+ if (!successfully_resolved_dns_ && dns_lookup->ip_list().size() > 0) {
+ successfully_resolved_dns_ = true;
+ }
+
+ for (int i = 0; i < static_cast<int>(dns_lookup->ip_list().size()); ++i) {
+ LOG(INFO)
+ << " ip " << i << " : "
+ << talk_base::SocketAddress::IPToString(dns_lookup->ip_list()[i]);
+ }
+
+ // Build the ip list.
+ assert(settings_list_.get());
+ settings_index_ = -1;
+ settings_list_->ClearPermutations();
+ settings_list_->AddPermutations(
+ server_list_[server_index_].server.IPAsString(),
+ dns_lookup->ip_list(),
+ server_list_[server_index_].server.port(),
+ server_list_[server_index_].special_port_magic,
+ proxy_only_);
+
+ UseNextConnection();
+}
+
+static const char* const PROTO_NAMES[cricket::PROTO_LAST + 1] = {
+ "udp", "tcp", "ssltcp"
+};
+
+static const char* ProtocolToString(cricket::ProtocolType proto) {
+ return PROTO_NAMES[proto];
+}
+
+void XmppConnectionGenerator::UseCurrentConnection() {
+ LOG(INFO) << "XmppConnectionGenerator::UseCurrentConnection";
+
+ ConnectionSettings* settings = settings_list_->GetSettings(settings_index_);
+ LOG(INFO) << "*** Attempting "
+ << ProtocolToString(settings->protocol()) << " connection to "
+ << settings->server().IPAsString() << ":"
+ << settings->server().port()
+ << " (via " << ProxyToString(settings->proxy().type)
+ << " proxy @ " << settings->proxy().address.IPAsString() << ":"
+ << settings->proxy().address.port() << ")";
+
+ SignalNewSettings(*settings);
+}
+
+void XmppConnectionGenerator::HandleExhaustedConnections() {
+ LOG(INFO) << "(" << buzz::XmppEngine::ERROR_SOCKET
+ << ", " << first_dns_error_ << ")";
+ SignalExhaustedSettings(successfully_resolved_dns_, first_dns_error_);
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/xmpp_connection_generator.h b/chrome/common/net/notifier/communicator/xmpp_connection_generator.h
new file mode 100644
index 0000000..47a2e40
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/xmpp_connection_generator.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_
+
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+class AutoDetectProxy;
+struct ProxyInfo;
+class SignalThread;
+class Task;
+}
+
+namespace notifier {
+
+class AsyncDNSLookup;
+class ConnectionOptions;
+class ConnectionSettings;
+class ConnectionSettingsList;
+
+struct ServerInformation {
+ talk_base::SocketAddress server;
+ bool special_port_magic;
+};
+
+// Resolves dns names and iterates through the various ip address and transport
+// combinations.
+class XmppConnectionGenerator : public sigslot::has_slots<> {
+ public:
+ // parent is the parent for any tasks needed during this operation.
+ // proxy_only indicates if true connections are only attempted using the
+ // proxy.
+ // server_list is the list of connections to attempt in priority order.
+ // server_count is the number of items in the server list.
+ XmppConnectionGenerator(talk_base::Task* parent,
+ const ConnectionOptions* options,
+ bool proxy_only,
+ const ServerInformation* server_list,
+ int server_count);
+ ~XmppConnectionGenerator();
+
+ // Only call this once. Create a new XmppConnectionGenerator and delete the
+ // current one if the process needs to start again.
+ void StartGenerating();
+
+ void UseNextConnection();
+ void UseCurrentConnection();
+
+ const talk_base::ProxyInfo& proxy() const;
+
+ sigslot::signal1<const ConnectionSettings&> SignalNewSettings;
+
+ // SignalExhaustedSettings(bool successfully_resolved_dns,
+ // int first_dns_error);
+ sigslot::signal2<bool, int> SignalExhaustedSettings;
+
+ private:
+ void OnProxyDetect(talk_base::AutoDetectProxy* proxy_detect);
+ void OnServerDNSResolved(AsyncDNSLookup* dns_lookup);
+ void HandleExhaustedConnections();
+
+ talk_base::scoped_ptr<ConnectionSettingsList> settings_list_;
+ int settings_index_; // The setting that is currently being used.
+ talk_base::scoped_array<ServerInformation> server_list_;
+ int server_count_;
+ int server_index_; // The server that is current being used.
+ bool proxy_only_;
+ bool successfully_resolved_dns_;
+ int first_dns_error_;
+ const ConnectionOptions* options_;
+
+ talk_base::Task* parent_;
+ DISALLOW_COPY_AND_ASSIGN(XmppConnectionGenerator);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_
diff --git a/chrome/common/net/notifier/communicator/xmpp_log.cc b/chrome/common/net/notifier/communicator/xmpp_log.cc
new file mode 100644
index 0000000..27b0cf96
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/xmpp_log.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 2009 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.
+
+#if LOGGING
+
+#include "chrome/common/net/notifier/communicator/xmpp_log.h"
+
+#include <iomanip>
+#include <string>
+#include <vector>
+
+#include "chrome/common/net/notifier/base/time.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace notifier {
+
+static bool IsAuthTag(const char* str, size_t len) {
+ // Beware that str is not NULL terminated.
+ if (str[0] == '<' &&
+ str[1] == 'a' &&
+ str[2] == 'u' &&
+ str[3] == 't' &&
+ str[4] == 'h' &&
+ str[5] <= ' ') {
+ std::string tag(str, len);
+ if (tag.find("mechanism") != std::string::npos)
+ return true;
+ }
+ return false;
+}
+
+static bool IsChatText(const char* str, size_t len) {
+ // Beware that str is not NULL terminated.
+ if (str[0] == '<' &&
+ str[1] == 'm' &&
+ str[2] == 'e' &&
+ str[3] == 's' &&
+ str[4] == 's' &&
+ str[5] == 'a' &&
+ str[6] == 'g' &&
+ str[7] == 'e' &&
+ str[8] <= ' ') {
+ std::string tag(str, len);
+ if (tag.find("chat") != std::string::npos)
+ return true;
+ }
+ return false;
+}
+
+void XmppLog::XmppPrint(bool output) {
+ std::vector<char>* buffer = output ?
+ &xmpp_output_buffer_ : &xmpp_input_buffer_;
+ const bool log_chat = LOG_CHECK_LEVEL(LS_SENSITIVE);
+ if (buffer->size()) {
+ char* time_string = GetLocalTimeAsString();
+ LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>>>>>>>>>>" :
+ "RECV <<<<<<<<<<<<<<<<<<<<<<<<<")
+ << " : " << time_string;
+
+ int start = 0;
+ int nest = 3;
+ for (int i = 0; i < static_cast<int>(buffer->size()); ++i) {
+ if ((*buffer)[i] == '>') {
+ bool indent;
+ if ((i > 0) && ((*buffer)[i - 1] == '/')) {
+ indent = false;
+ } else if ((start + 1 < static_cast<int>(buffer->size())) &&
+ ((*buffer)[start + 1] == '/')) {
+ indent = false;
+ nest -= 2;
+ } else {
+ indent = true;
+ }
+
+ // Output a tag
+ LOG(INFO) << std::setw(nest) << " "
+ << std::string(&((*buffer)[start]), i + 1 - start);
+
+ if (indent)
+ nest += 2;
+
+ // Note if it's a PLAIN auth tag
+ if (IsAuthTag(&((*buffer)[start]), i + 1 - start)) {
+ censor_password_ = true;
+ } else if (!log_chat && IsChatText(&((*buffer)[start]),
+ i + 1 - start)) {
+ censor_password_ = true;
+ }
+
+ start = i + 1;
+ }
+
+ if ((*buffer)[i] == '<' && start < i) {
+ if (censor_password_) {
+ LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
+ censor_password_ = false;
+ } else {
+ LOG(INFO) << std::setw(nest) << " "
+ << std::string(&((*buffer)[start]), i - start);
+ }
+ start = i;
+ }
+ }
+ buffer->erase(buffer->begin(), buffer->begin() + start);
+ }
+}
+
+} // namespace notifier
+
+#endif // if LOGGING
diff --git a/chrome/common/net/notifier/communicator/xmpp_log.h b/chrome/common/net/notifier/communicator/xmpp_log.h
new file mode 100644
index 0000000..1d6edac
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/xmpp_log.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_
+
+#if LOGGING
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+
+namespace notifier {
+
+// Log the xmpp input and output.
+class XmppLog : public sigslot::has_slots<> {
+ public:
+ XmppLog() : censor_password_(false) {
+ }
+
+ void Input(const char* data, int len) {
+ xmpp_input_buffer_.insert(xmpp_input_buffer_.end(), data, data + len);
+ XmppPrint(false);
+ }
+
+ void Output(const char* data, int len) {
+ xmpp_output_buffer_.insert(xmpp_output_buffer_.end(), data, data + len);
+ XmppPrint(true);
+ }
+
+ private:
+ void XmppPrint(bool output);
+
+ std::vector<char> xmpp_input_buffer_;
+ std::vector<char> xmpp_output_buffer_;
+ bool censor_password_;
+ DISALLOW_COPY_AND_ASSIGN(XmppLog);
+};
+
+} // namespace notifier
+
+#endif // if LOGGING
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_
diff --git a/chrome/common/net/notifier/communicator/xmpp_socket_adapter.cc b/chrome/common/net/notifier/communicator/xmpp_socket_adapter.cc
new file mode 100644
index 0000000..281a6179
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/xmpp_socket_adapter.cc
@@ -0,0 +1,427 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/communicator/xmpp_socket_adapter.h"
+
+#include <iomanip>
+#include <string>
+
+#include "base/logging.h"
+#include "chrome/common/net/notifier/base/ssl_adapter.h"
+#include "chrome/common/net/notifier/communicator/product_info.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+XmppSocketAdapter::XmppSocketAdapter(const buzz::XmppClientSettings& xcs,
+ bool allow_unverified_certs)
+ : state_(STATE_CLOSED),
+ error_(ERROR_NONE),
+ wsa_error_(0),
+ socket_(NULL),
+ protocol_(xcs.protocol()),
+ firewall_(false),
+ write_buffer_(NULL),
+ write_buffer_length_(0),
+ write_buffer_capacity_(0),
+ allow_unverified_certs_(allow_unverified_certs) {
+ proxy_.type = xcs.proxy();
+ proxy_.address.SetIP(xcs.proxy_host(), false);
+ proxy_.address.SetPort(xcs.proxy_port());
+ proxy_.username = xcs.proxy_user();
+ proxy_.password = xcs.proxy_pass();
+}
+
+XmppSocketAdapter::~XmppSocketAdapter() {
+ FreeState();
+
+ // Clean up any previous socket - cannot delete socket on close because close
+ // happens during the child socket's stack callback.
+ if (socket_) {
+ delete socket_;
+ socket_ = NULL;
+ }
+}
+
+bool XmppSocketAdapter::FreeState() {
+ int code = 0;
+
+ // Clean up the socket.
+ if (socket_ && !(state_ == STATE_CLOSED || state_ == STATE_CLOSING)) {
+ code = socket_->Close();
+ }
+
+ delete[] write_buffer_;
+ write_buffer_ = NULL;
+ write_buffer_length_ = 0;
+ write_buffer_capacity_ = 0;
+
+ if (code) {
+ SetWSAError(code);
+ return false;
+ }
+ return true;
+}
+
+bool XmppSocketAdapter::Connect(const talk_base::SocketAddress& addr) {
+ if (state_ != STATE_CLOSED) {
+ SetError(ERROR_WRONGSTATE);
+ return false;
+ }
+
+ LOG(INFO) << "XmppSocketAdapter::Connect(" << addr.ToString() << ")";
+
+ // Clean up any previous socket - cannot delete socket on close because close
+ // happens during the child socket's stack callback.
+ if (socket_) {
+ delete socket_;
+ socket_ = NULL;
+ }
+
+ talk_base::AsyncSocket* socket =
+ talk_base::Thread::Current()->socketserver()->CreateAsyncSocket(
+ SOCK_STREAM);
+ if (!socket) {
+ SetWSAError(WSA_NOT_ENOUGH_MEMORY);
+ return false;
+ }
+
+ if (firewall_) {
+ // TODO(sync): Change this to make WSAAsyncSockets support current thread
+ // socket server.
+ talk_base::FirewallSocketServer* fw =
+ static_cast<talk_base::FirewallSocketServer*>(
+ talk_base::Thread::Current()->socketserver());
+ socket = fw->WrapSocket(socket, SOCK_STREAM);
+ }
+
+ if (proxy_.type) {
+ talk_base::AsyncSocket* proxy_socket = 0;
+ if (proxy_.type == talk_base::PROXY_SOCKS5) {
+ proxy_socket = new talk_base::AsyncSocksProxySocket(
+ socket, proxy_.address, proxy_.username, proxy_.password);
+ } else {
+ // Note: we are trying unknown proxies as HTTPS currently.
+ proxy_socket = new talk_base::AsyncHttpsProxySocket(socket,
+ GetUserAgentString(), proxy_.address, proxy_.username,
+ proxy_.password);
+ }
+ if (!proxy_socket) {
+ SetWSAError(WSA_NOT_ENOUGH_MEMORY);
+ delete socket;
+ return false;
+ }
+ socket = proxy_socket; // For our purposes the proxy is now the socket.
+ }
+
+ if (protocol_ == cricket::PROTO_SSLTCP) {
+ talk_base::AsyncSocket *fake_ssl_socket =
+ new talk_base::AsyncSSLSocket(socket);
+ if (!fake_ssl_socket) {
+ SetWSAError(WSA_NOT_ENOUGH_MEMORY);
+ delete socket;
+ return false;
+ }
+ socket = fake_ssl_socket; // For our purposes the SSL socket is the socket.
+ }
+
+#if defined(FEATURE_ENABLE_SSL)
+ talk_base::SSLAdapter* ssl_adapter = notifier::CreateSSLAdapter(socket);
+ socket = ssl_adapter; // For our purposes the SSL adapter is the socket.
+#endif
+
+ socket->SignalReadEvent.connect(this, &XmppSocketAdapter::OnReadEvent);
+ socket->SignalWriteEvent.connect(this, &XmppSocketAdapter::OnWriteEvent);
+ socket->SignalConnectEvent.connect(this, &XmppSocketAdapter::OnConnectEvent);
+ socket->SignalCloseEvent.connect(this, &XmppSocketAdapter::OnCloseEvent);
+
+ // The linux implementation of socket::Connect returns an error when the
+ // connect didn't complete yet. This can be distinguished from a failure
+ // because socket::IsBlocking is true. Perhaps, the linux implementation
+ // should be made to behave like the windows version which doesn't do this,
+ // but it seems to be a pattern with these methods that they return an error
+ // if the operation didn't complete in a sync fashion and one has to check
+ // IsBlocking to tell if was a "real" error.
+ if (socket->Connect(addr) == SOCKET_ERROR && !socket->IsBlocking()) {
+ SetWSAError(socket->GetError());
+ delete socket;
+ return false;
+ }
+
+ socket_ = socket;
+ state_ = STATE_CONNECTING;
+ return true;
+}
+
+bool XmppSocketAdapter::Read(char* data, size_t len, size_t* len_read) {
+ if (len_read)
+ *len_read = 0;
+
+ if (state_ <= STATE_CLOSING) {
+ SetError(ERROR_WRONGSTATE);
+ return false;
+ }
+
+ ASSERT(socket_ != NULL);
+
+ if (IsOpen()) {
+ int result = socket_->Recv(data, len);
+ if (result < 0) {
+ if (!socket_->IsBlocking()) {
+ SetWSAError(socket_->GetError());
+ return false;
+ }
+
+ result = 0;
+ }
+
+ if (len_read)
+ *len_read = result;
+ }
+
+ return true;
+}
+
+bool XmppSocketAdapter::Write(const char* data, size_t len) {
+ if (state_ <= STATE_CLOSING) {
+ // There may be data in a buffer that gets lost. Too bad!
+ SetError(ERROR_WRONGSTATE);
+ return false;
+ }
+
+ ASSERT(socket_ != NULL);
+
+ size_t sent = 0;
+
+ // Try an immediate write when there is no buffer and we aren't in SSL mode
+ // or opening the connection.
+ if (write_buffer_length_ == 0 && IsOpen()) {
+ int result = socket_->Send(data, len);
+ if (result < 0) {
+ if (!socket_->IsBlocking()) {
+ SetWSAError(socket_->GetError());
+ return false;
+ }
+ result = 0;
+ }
+
+ sent = static_cast<size_t>(result);
+ }
+
+ // Buffer what we didn't send.
+ if (sent < len) {
+ QueueWriteData(data + sent, len - sent);
+ }
+
+ // Service the socket right away to push the written data out in SSL mode.
+ return HandleWritable();
+}
+
+bool XmppSocketAdapter::Close() {
+ if (state_ == STATE_CLOSING) {
+ return false; // Avoid recursion, but not unexpected.
+ }
+ if (state_ == STATE_CLOSED) {
+ // In theory should not be trying to re-InternalClose.
+ SetError(ERROR_WRONGSTATE);
+ return false;
+ }
+
+ // TODO(sync): deal with flushing close (flush, don't do reads, clean ssl).
+
+ // If we've gotten to the point where we really do have a socket underneath
+ // then close it. It should call us back to tell us it is closed, and
+ // NotifyClose will be called. We indicate "closing" state so that we
+ // do not recusively try to keep closing the socket.
+ if (socket_) {
+ state_ = STATE_CLOSING;
+ socket_->Close();
+ }
+
+ // If we didn't get the callback, then we better make sure we signal
+ // closed.
+ if (state_ != STATE_CLOSED) {
+ // The socket was closed manually, not directly due to error.
+ if (error_ != ERROR_NONE) {
+ LOG(INFO) << "XmppSocketAdapter::Close - previous Error: " << error_
+ << " WSAError: " << wsa_error_;
+ error_ = ERROR_NONE;
+ wsa_error_ = 0;
+ }
+ NotifyClose();
+ }
+ return true;
+}
+
+void XmppSocketAdapter::NotifyClose() {
+ if (state_ == STATE_CLOSED) {
+ SetError(ERROR_WRONGSTATE);
+ } else {
+ LOG(INFO) << "XmppSocketAdapter::NotifyClose - Error: " << error_
+ << " WSAError: " << wsa_error_;
+ state_ = STATE_CLOSED;
+ SignalClosed();
+ FreeState();
+ }
+}
+
+void XmppSocketAdapter::OnConnectEvent(talk_base::AsyncSocket *socket) {
+ if (state_ == STATE_CONNECTING) {
+ state_ = STATE_OPEN;
+ LOG(INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_OPEN";
+ SignalConnected();
+#if defined(FEATURE_ENABLE_SSL)
+ } else if (state_ == STATE_TLS_CONNECTING) {
+ state_ = STATE_TLS_OPEN;
+ LOG(INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_TLS_OPEN";
+ SignalSSLConnected();
+ if (write_buffer_length_ > 0) {
+ HandleWritable();
+ }
+#endif // defined(FEATURE_ENABLE_SSL)
+ } else {
+ LOG(INFO) << "XmppSocketAdapter::OnConnectEvent - state is " << state_;
+ ASSERT(false);
+ }
+}
+
+void XmppSocketAdapter::OnReadEvent(talk_base::AsyncSocket *socket) {
+ HandleReadable();
+}
+
+void XmppSocketAdapter::OnWriteEvent(talk_base::AsyncSocket *socket) {
+ HandleWritable();
+}
+
+void XmppSocketAdapter::OnCloseEvent(talk_base::AsyncSocket *socket,
+ int error) {
+ LOG(INFO) << "XmppSocketAdapter::OnCloseEvent(" << error << ")";
+ SetWSAError(error);
+ if (error == SOCKET_EACCES) {
+ SignalAuthenticationError(); // Proxy needs authentication.
+ }
+ NotifyClose();
+}
+
+#if defined(FEATURE_ENABLE_SSL)
+bool XmppSocketAdapter::StartTls(const std::string& verify_host_name) {
+ if (state_ != STATE_OPEN) {
+ SetError(ERROR_WRONGSTATE);
+ return false;
+ }
+
+ state_ = STATE_TLS_CONNECTING;
+
+ ASSERT(write_buffer_length_ == 0);
+
+ talk_base::SSLAdapter* ssl_adapter =
+ static_cast<talk_base::SSLAdapter*>(socket_);
+
+ if (allow_unverified_certs_) {
+ ssl_adapter->set_ignore_bad_cert(true);
+ }
+
+ if (ssl_adapter->StartSSL(verify_host_name.c_str(), false) != 0) {
+ state_ = STATE_OPEN;
+ SetError(ERROR_SSL);
+ return false;
+ }
+
+ return true;
+}
+#endif // defined(FEATURE_ENABLE_SSL)
+
+void XmppSocketAdapter::QueueWriteData(const char* data, size_t len) {
+ // Expand buffer if needed.
+ if (write_buffer_length_ + len > write_buffer_capacity_) {
+ size_t new_capacity = 1024;
+ while (new_capacity < write_buffer_length_ + len) {
+ new_capacity = new_capacity * 2;
+ }
+ char* new_buffer = new char[new_capacity];
+ ASSERT(write_buffer_length_ <= 64000);
+ memcpy(new_buffer, write_buffer_, write_buffer_length_);
+ delete[] write_buffer_;
+ write_buffer_ = new_buffer;
+ write_buffer_capacity_ = new_capacity;
+ }
+
+ // Copy data into the end of buffer.
+ memcpy(write_buffer_ + write_buffer_length_, data, len);
+ write_buffer_length_ += len;
+}
+
+void XmppSocketAdapter::FlushWriteQueue(Error* error, int* wsa_error) {
+ ASSERT(error && wsa_error);
+
+ size_t flushed = 0;
+ while (flushed < write_buffer_length_) {
+ int sent = socket_->Send(write_buffer_ + flushed,
+ static_cast<int>(write_buffer_length_ - flushed));
+ if (sent < 0) {
+ if (!socket_->IsBlocking()) {
+ *error = ERROR_WINSOCK;
+ *wsa_error = socket_->GetError();
+ }
+ break;
+ }
+ flushed += static_cast<size_t>(sent);
+ }
+
+ // Remove flushed memory.
+ write_buffer_length_ -= flushed;
+ memmove(write_buffer_, write_buffer_ + flushed, write_buffer_length_);
+
+ // When everything is flushed, deallocate the buffer if it's gotten big.
+ if (write_buffer_length_ == 0) {
+ if (write_buffer_capacity_ > 8192) {
+ delete[] write_buffer_;
+ write_buffer_ = NULL;
+ write_buffer_capacity_ = 0;
+ }
+ }
+}
+
+void XmppSocketAdapter::SetError(Error error) {
+ if (error_ == ERROR_NONE) {
+ error_ = error;
+ }
+}
+
+void XmppSocketAdapter::SetWSAError(int error) {
+ if (error_ == ERROR_NONE && error != 0) {
+ error_ = ERROR_WINSOCK;
+ wsa_error_ = error;
+ }
+}
+
+bool XmppSocketAdapter::HandleReadable() {
+ if (!IsOpen())
+ return false;
+
+ SignalRead();
+ return true;
+}
+
+bool XmppSocketAdapter::HandleWritable() {
+ if (!IsOpen())
+ return false;
+
+ Error error = ERROR_NONE;
+ int wsa_error = 0;
+ FlushWriteQueue(&error, &wsa_error);
+ if (error != ERROR_NONE) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/communicator/xmpp_socket_adapter.h b/chrome/common/net/notifier/communicator/xmpp_socket_adapter.h
new file mode 100644
index 0000000..e818173
--- /dev/null
+++ b/chrome/common/net/notifier/communicator/xmpp_socket_adapter.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_
+#define CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_
+
+#include <string>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
+#ifndef _WIN32
+// Additional errors used by us from Win32 headers.
+#define SEC_E_CERT_EXPIRED static_cast<int>(0x80090328L)
+#define WSA_NOT_ENOUGH_MEMORY ENOMEM
+#endif
+
+namespace notifier {
+
+class XmppSocketAdapter : public buzz::AsyncSocket,
+ public sigslot::has_slots<> {
+ public:
+ XmppSocketAdapter(const buzz::XmppClientSettings& xcs,
+ bool allow_unverified_certs);
+ virtual ~XmppSocketAdapter();
+
+ virtual State state() { return state_; }
+ virtual Error error() { return error_; }
+ virtual int GetError() { return wsa_error_; }
+
+ void set_firewall(bool firewall) { firewall_ = firewall; }
+
+ virtual bool Connect(const talk_base::SocketAddress& addr);
+ virtual bool Read(char* data, size_t len, size_t* len_read);
+ virtual bool Write(const char* data, size_t len);
+ virtual bool Close();
+
+#if defined(FEATURE_ENABLE_SSL)
+ bool StartTls(const std::string& domainname);
+ bool IsOpen() const { return state_ == STATE_OPEN
+ || state_ == STATE_TLS_OPEN; }
+#else
+ bool IsOpen() const { return state_ == STATE_OPEN; }
+#endif
+
+ sigslot::signal0<> SignalAuthenticationError;
+
+ private:
+ // Return false if the socket is closed.
+ bool HandleReadable();
+ bool HandleWritable();
+
+ State state_;
+ Error error_;
+ int wsa_error_;
+
+ talk_base::AsyncSocket* socket_;
+ cricket::ProtocolType protocol_;
+ talk_base::ProxyInfo proxy_;
+ bool firewall_;
+ char* write_buffer_;
+ size_t write_buffer_length_;
+ size_t write_buffer_capacity_;
+ bool allow_unverified_certs_;
+
+ bool FreeState();
+ void NotifyClose();
+
+ void OnReadEvent(talk_base::AsyncSocket* socket);
+ void OnWriteEvent(talk_base::AsyncSocket* socket);
+ void OnConnectEvent(talk_base::AsyncSocket* socket);
+ void OnCloseEvent(talk_base::AsyncSocket* socket, int error);
+
+ void QueueWriteData(const char* data, size_t len);
+ void FlushWriteQueue(Error* error, int* wsa_error);
+
+ void SetError(Error error);
+ void SetWSAError(int error);
+ DISALLOW_COPY_AND_ASSIGN(XmppSocketAdapter);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_
diff --git a/chrome/common/net/notifier/listener/listen_task.cc b/chrome/common/net/notifier/listener/listen_task.cc
new file mode 100644
index 0000000..3f47163
--- /dev/null
+++ b/chrome/common/net/notifier/listener/listen_task.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/listen_task.h"
+
+#include "base/logging.h"
+#include "chrome/common/net/notifier/listener/notification_constants.h"
+#include "chrome/common/net/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/xmppconstants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+ListenTask::ListenTask(Task* parent)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_TYPE) {
+}
+
+ListenTask::~ListenTask() {
+}
+
+int ListenTask::ProcessStart() {
+ LOG(INFO) << "P2P: Listener task started.";
+ return STATE_RESPONSE;
+}
+
+int ListenTask::ProcessResponse() {
+ LOG(INFO) << "P2P: Listener response received.";
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ // Acknowledge receipt of the notification to the buzz server.
+ scoped_ptr<buzz::XmlElement> response_stanza(MakeIqResult(stanza));
+ SendStanza(response_stanza.get());
+
+ // TODO(akalin): Write unittests to cover this.
+ // Extract the service URL and service-specific data from the stanza.
+ // The response stanza has the following format.
+ // <iq from="{bare_jid}" to="{full_jid}" id="#" type="set">
+ // <not:getAll xmlns:not="google:notifier">
+ // <Timestamp long="#" xmlns=""/>
+ // <Result xmlns="">
+ // <Id>
+ // <ServiceUrl data="{service_url}"/>
+ // <ServiceId data="{service_id}"/>
+ // </Id>
+ // <Timestamp long="#"/>
+ // <Content>
+ // <Priority int="#"/>
+ // <ServiceSpecificData data="{service_specific_data}"/>
+ // <RequireSubscription bool="true"/>
+ // </Content>
+ // <State>
+ // <Type int="#"/>
+ // <Read bool="{true/false}"/>
+ // </State>
+ // <ClientActive bool="{true/false}"/>
+ // </Result>
+ // </not:getAll>
+ // </iq> "
+ // Note that there can be multiple "Result" elements, so we need to loop
+ // through all of them.
+ bool update_signaled = false;
+ const buzz::XmlElement* get_all_element =
+ stanza->FirstNamed(buzz::QName(true, "google:notifier", "getAll"));
+ if (get_all_element) {
+ const buzz::XmlElement* result_element =
+ get_all_element->FirstNamed(
+ buzz::QName(true, buzz::STR_EMPTY, "Result"));
+ while (result_element) {
+ IncomingNotificationData notification_data;
+ const buzz::XmlElement* id_element =
+ result_element->FirstNamed(buzz::QName(true, buzz::STR_EMPTY, "Id"));
+ if (id_element) {
+ const buzz::XmlElement* service_url_element =
+ id_element->FirstNamed(
+ buzz::QName(true, buzz::STR_EMPTY, "ServiceUrl"));
+ if (service_url_element) {
+ notification_data.service_url = service_url_element->Attr(
+ buzz::QName(true, buzz::STR_EMPTY, "data"));
+ }
+ }
+ const buzz::XmlElement* content_element =
+ result_element->FirstNamed(
+ buzz::QName(true, buzz::STR_EMPTY, "Content"));
+ if (content_element) {
+ const buzz::XmlElement* service_data_element =
+ content_element->FirstNamed(
+ buzz::QName(true, buzz::STR_EMPTY, "ServiceSpecificData"));
+ if (service_data_element) {
+ notification_data.service_specific_data = service_data_element->Attr(
+ buzz::QName(true, buzz::STR_EMPTY, "data"));
+ }
+ }
+ // Inform listeners that a notification has been received.
+ SignalUpdateAvailable(notification_data);
+ update_signaled = true;
+ // Now go to the next Result element
+ result_element = result_element->NextNamed(
+ buzz::QName(true, buzz::STR_EMPTY, "Result"));
+ }
+ }
+ if (!update_signaled) {
+ LOG(WARNING) <<
+ "No getAll element or Result element found in response stanza";
+ // Signal an empty update to preserve old behavior
+ SignalUpdateAvailable(IncomingNotificationData());
+ }
+ return STATE_RESPONSE;
+}
+
+bool ListenTask::HandleStanza(const buzz::XmlElement* stanza) {
+ LOG(INFO) << "P2P: Stanza received: " << XmlElementToString(*stanza);
+ // TODO(akalin): Do more verification on stanza depending on
+ // the sync notification method
+ if (IsValidNotification(stanza)) {
+ QueueStanza(stanza);
+ return true;
+ }
+ return false;
+}
+
+bool ListenTask::IsValidNotification(const buzz::XmlElement* stanza) {
+ static const buzz::QName kQnNotifierGetAll(
+ true, kNotifierNamespace, "getAll");
+ // An update notificaiton has the following form.
+ // <cli:iq from="{bare_jid}" to="{full_jid}"
+ // id="#" type="set" xmlns:cli="jabber:client">
+ // <not:getAll xmlns:not="google:notifier">
+ // <Timestamp long="#" xmlns=""/>
+ // </not:getAll>
+ // </cli:iq>
+ return
+ (MatchRequestIq(stanza, buzz::STR_SET, kQnNotifierGetAll) &&
+ (stanza->Attr(buzz::QN_TO) == GetClient()->jid().Str()) &&
+ (stanza->Attr(buzz::QN_FROM) == GetClient()->jid().BareJid().Str()));
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/listen_task.h b/chrome/common/net/notifier/listener/listen_task.h
new file mode 100644
index 0000000..c4f2a6fa
--- /dev/null
+++ b/chrome/common/net/notifier/listener/listen_task.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2009 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 talk service, and signals when
+// they arrive. It checks all incoming stanza's 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 CHROME_COMMON_NET_NOTIFIER_LISTENER_LISTEN_TASK_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_LISTEN_TASK_H_
+
+#include "chrome/common/net/notifier/listener/notification_defines.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+class XmlElement;
+class Jid;
+}
+
+namespace notifier {
+
+class ListenTask : public buzz::XmppTask {
+ public:
+ explicit ListenTask(Task* parent);
+ virtual ~ListenTask();
+
+ // Overriden from buzz::XmppTask.
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+
+ // Signal callback upon receipt of a notification.
+ // SignalUpdateAvailable(const IncomingNotificationData& data);
+ sigslot::signal1<const IncomingNotificationData&> SignalUpdateAvailable;
+
+ private:
+ // Decide whether a notification should start a sync. We only validate that
+ // this notification came from our own Jid().
+ bool IsValidNotification(const buzz::XmlElement* stanza);
+
+ DISALLOW_COPY_AND_ASSIGN(ListenTask);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_LISTEN_TASK_H_
diff --git a/chrome/common/net/notifier/listener/mediator_thread.h b/chrome/common/net/notifier/listener/mediator_thread.h
new file mode 100644
index 0000000..5c460a0f
--- /dev/null
+++ b/chrome/common/net/notifier/listener/mediator_thread.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2009 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.
+//
+// These methods should post messages to a queue which a different thread will
+// later come back and read from.
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "chrome/common/net/notifier/listener/notification_defines.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+
+class MediatorThread {
+ public:
+ enum MediatorMessage {
+ MSG_LOGGED_IN,
+ MSG_LOGGED_OUT,
+ MSG_SUBSCRIPTION_SUCCESS,
+ MSG_SUBSCRIPTION_FAILURE,
+ MSG_NOTIFICATION_SENT
+ };
+
+ virtual ~MediatorThread() {}
+
+ virtual void Login(const buzz::XmppClientSettings& settings) = 0;
+ virtual void Logout() = 0;
+ virtual void Start() = 0;
+ virtual void SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list) = 0;
+ virtual void ListenForUpdates() = 0;
+ virtual void SendNotification(const OutgoingNotificationData& data) = 0;
+
+ // Connect to this for messages about talk events (except notifications).
+ sigslot::signal1<MediatorMessage> SignalStateChange;
+ // Connect to this for notifications
+ sigslot::signal1<const IncomingNotificationData&> SignalNotificationReceived;
+
+ protected:
+ MediatorThread() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediatorThread);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
diff --git a/chrome/common/net/notifier/listener/mediator_thread_impl.cc b/chrome/common/net/notifier/listener/mediator_thread_impl.cc
new file mode 100644
index 0000000..7ab5150
--- /dev/null
+++ b/chrome/common/net/notifier/listener/mediator_thread_impl.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/mediator_thread_impl.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/platform_thread.h"
+#include "chrome/common/net/notifier/base/async_dns_lookup.h"
+#include "chrome/common/net/notifier/base/task_pump.h"
+#include "chrome/common/net/notifier/communicator/connection_options.h"
+#include "chrome/common/net/notifier/communicator/const_communicator.h"
+#include "chrome/common/net/notifier/communicator/xmpp_connection_generator.h"
+#include "chrome/common/net/notifier/listener/listen_task.h"
+#include "chrome/common/net/notifier/listener/send_update_task.h"
+#include "chrome/common/net/notifier/listener/subscribe_task.h"
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+using std::string;
+
+namespace notifier {
+
+MediatorThreadImpl::MediatorThreadImpl() {}
+
+MediatorThreadImpl::~MediatorThreadImpl() {
+}
+
+void MediatorThreadImpl::Start() {
+ talk_base::Thread::Start();
+}
+
+void MediatorThreadImpl::Run() {
+ PlatformThread::SetName("Notifier_MediatorThread");
+ // For win32, this sets up the win32socketserver. Note that it needs to
+ // dispatch windows messages since that is what the win32 socket server uses.
+
+ MessageLoop message_loop;
+
+ Post(this, CMD_PUMP_AUXILIARY_LOOPS);
+ ProcessMessages(talk_base::kForever);
+}
+
+void MediatorThreadImpl::PumpAuxiliaryLoops() {
+ if (pump_.get() && pump_->HasPendingTimeoutTask()) {
+ pump_->WakeTasks();
+ }
+ MessageLoop::current()->RunAllPending();
+ // We want to pump auxiliary loops every 100ms until this thread is stopped,
+ // at which point this call will do nothing.
+ PostDelayed(100, this, CMD_PUMP_AUXILIARY_LOOPS);
+}
+
+void MediatorThreadImpl::Login(const buzz::XmppClientSettings& settings) {
+ Post(this, CMD_LOGIN, new LoginData(settings));
+}
+
+void MediatorThreadImpl::Stop() {
+ Thread::Stop();
+ CHECK(!login_.get() && !pump_.get()) << "Logout should be called prior to"
+ << "message queue exit.";
+}
+
+void MediatorThreadImpl::Logout() {
+ CHECK(!IsQuitting())
+ << "Logout should be called prior to message queue exit.";
+ Post(this, CMD_DISCONNECT);
+ Stop();
+}
+
+void MediatorThreadImpl::ListenForUpdates() {
+ Post(this, CMD_LISTEN_FOR_UPDATES);
+}
+
+void MediatorThreadImpl::SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list) {
+ Post(this, CMD_SUBSCRIBE_FOR_UPDATES,
+ new SubscriptionData(subscribed_services_list));
+}
+
+void MediatorThreadImpl::SendNotification(
+ const OutgoingNotificationData& data) {
+ Post(this, CMD_SEND_NOTIFICATION, new OutgoingNotificationMessageData(data));
+}
+
+void MediatorThreadImpl::ProcessMessages(int milliseconds) {
+ talk_base::Thread::ProcessMessages(milliseconds);
+}
+
+void MediatorThreadImpl::OnMessage(talk_base::Message* msg) {
+ scoped_ptr<LoginData> data;
+ switch (msg->message_id) {
+ case CMD_LOGIN:
+ DCHECK(msg->pdata);
+ data.reset(reinterpret_cast<LoginData*>(msg->pdata));
+ DoLogin(data.get());
+ break;
+ case CMD_DISCONNECT:
+ DoDisconnect();
+ break;
+ case CMD_LISTEN_FOR_UPDATES:
+ DoListenForUpdates();
+ break;
+ case CMD_SEND_NOTIFICATION: {
+ DCHECK(msg->pdata);
+ scoped_ptr<OutgoingNotificationMessageData> data(
+ reinterpret_cast<OutgoingNotificationMessageData*>(msg->pdata));
+ DoSendNotification(*data);
+ break;
+ }
+ case CMD_SUBSCRIBE_FOR_UPDATES: {
+ DCHECK(msg->pdata);
+ scoped_ptr<SubscriptionData> subscription_data(
+ reinterpret_cast<SubscriptionData*>(msg->pdata));
+ DoSubscribeForUpdates(*subscription_data);
+ break;
+ }
+ case CMD_PUMP_AUXILIARY_LOOPS:
+ PumpAuxiliaryLoops();
+ break;
+ default:
+ LOG(ERROR) << "P2P: Someone passed a bad message to the thread.";
+ break;
+ }
+}
+
+void MediatorThreadImpl::DoLogin(LoginData* login_data) {
+ LOG(INFO) << "P2P: Thread logging into talk network.";
+ buzz::XmppClientSettings& user_settings = login_data->user_settings;
+
+ // Start a new pump for the login.
+ login_.reset();
+ pump_.reset(new notifier::TaskPump());
+
+ notifier::ServerInformation server_list[2];
+ int server_list_count = 2;
+
+ // The default servers know how to serve over port 443 (that's the magic).
+ server_list[0].server = talk_base::SocketAddress("talk.google.com",
+ notifier::kDefaultXmppPort,
+ true); // Use DNS.
+ server_list[0].special_port_magic = true;
+ server_list[1].server = talk_base::SocketAddress("talkx.l.google.com",
+ notifier::kDefaultXmppPort,
+ true); // Use DNS.
+ server_list[1].special_port_magic = true;
+
+ // Autodetect proxy is on by default.
+ notifier::ConnectionOptions options;
+
+ // Language is not used in the stanza so we default to |en|.
+ std::string lang = "en";
+ login_.reset(new notifier::Login(pump_.get(),
+ user_settings,
+ options,
+ lang,
+ server_list,
+ server_list_count,
+ // NetworkStatusDetectionTask will be
+ // created for you if NULL is passed in.
+ // It helps shorten the autoreconnect
+ // time after going offline and coming
+ // back online.
+ NULL,
+ // talk_base::FirewallManager* is NULL.
+ NULL,
+ // Both the proxy and a non-proxy route
+ // will be attempted.
+ false,
+ // |previous_login_successful| is true
+ // because we have already done a
+ // successful gaia login at this point
+ // through another mechanism.
+ true));
+
+ login_->SignalClientStateChange.connect(
+ this, &MediatorThreadImpl::OnClientStateChangeMessage);
+ login_->SignalLoginFailure.connect(
+ this, &MediatorThreadImpl::OnLoginFailureMessage);
+ login_->StartConnection();
+}
+
+void MediatorThreadImpl::OnInputDebug(const char* msg, int length) {
+ string output(msg, length);
+ LOG(INFO) << "P2P: OnInputDebug:" << output << ".";
+}
+
+void MediatorThreadImpl::OnOutputDebug(const char* msg, int length) {
+ string output(msg, length);
+ LOG(INFO) << "P2P: OnOutputDebug:" << output << ".";
+}
+
+void MediatorThreadImpl::DoDisconnect() {
+ LOG(INFO) << "P2P: Thread logging out of talk network.";
+ login_.reset();
+ // Delete the old pump while on the thread to ensure that everything is
+ // cleaned-up in a predicatable manner.
+ pump_.reset();
+}
+
+void MediatorThreadImpl::DoSubscribeForUpdates(
+ const SubscriptionData& subscription_data) {
+ buzz::XmppClient* client = xmpp_client();
+ // If there isn't an active xmpp client, return.
+ if (!client) {
+ return;
+ }
+ SubscribeTask* subscription =
+ new SubscribeTask(client, subscription_data.subscribed_services_list);
+ subscription->SignalStatusUpdate.connect(
+ this,
+ &MediatorThreadImpl::OnSubscriptionStateChange);
+ subscription->Start();
+}
+
+void MediatorThreadImpl::DoListenForUpdates() {
+ buzz::XmppClient* client = xmpp_client();
+ // If there isn't an active xmpp client, return.
+ if (!client) {
+ return;
+ }
+ ListenTask* listener = new ListenTask(client);
+ listener->SignalUpdateAvailable.connect(
+ this,
+ &MediatorThreadImpl::OnUpdateListenerMessage);
+ listener->Start();
+}
+
+void MediatorThreadImpl::DoSendNotification(
+ const OutgoingNotificationMessageData& data) {
+ buzz::XmppClient* client = xmpp_client();
+ // If there isn't an active xmpp client, return.
+ if (!client) {
+ return;
+ }
+ SendUpdateTask* task = new SendUpdateTask(client, data.notification_data);
+ task->SignalStatusUpdate.connect(
+ this,
+ &MediatorThreadImpl::OnUpdateNotificationSent);
+ task->Start();
+}
+
+void MediatorThreadImpl::OnUpdateListenerMessage(
+ const IncomingNotificationData& notification_data) {
+ SignalNotificationReceived(notification_data);
+}
+
+void MediatorThreadImpl::OnUpdateNotificationSent(bool success) {
+ if (success) {
+ SignalStateChange(MSG_NOTIFICATION_SENT);
+ }
+}
+
+void MediatorThreadImpl::OnLoginFailureMessage(
+ const notifier::LoginFailure& failure) {
+ SignalStateChange(MSG_LOGGED_OUT);
+}
+
+void MediatorThreadImpl::OnClientStateChangeMessage(
+ notifier::Login::ConnectionState state) {
+ switch (state) {
+ case notifier::Login::STATE_CLOSED:
+ SignalStateChange(MSG_LOGGED_OUT);
+ break;
+ case notifier::Login::STATE_RETRYING:
+ case notifier::Login::STATE_OPENING:
+ LOG(INFO) << "P2P: Thread trying to connect.";
+ // Maybe first time logon, maybe intermediate network disruption. Assume
+ // the server went down, and lost our subscription for updates.
+ SignalStateChange(MSG_SUBSCRIPTION_FAILURE);
+ break;
+ case notifier::Login::STATE_OPENED:
+ SignalStateChange(MSG_LOGGED_IN);
+ break;
+ default:
+ LOG(WARNING) << "P2P: Unknown client state change.";
+ break;
+ }
+}
+
+void MediatorThreadImpl::OnSubscriptionStateChange(bool success) {
+ if (success) {
+ SignalStateChange(MSG_SUBSCRIPTION_SUCCESS);
+ } else {
+ SignalStateChange(MSG_SUBSCRIPTION_FAILURE);
+ }
+}
+
+buzz::XmppClient* MediatorThreadImpl::xmpp_client() {
+ if (!login_.get()) {
+ return NULL;
+ }
+ return login_->xmpp_client();
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/mediator_thread_impl.h b/chrome/common/net/notifier/listener/mediator_thread_impl.h
new file mode 100644
index 0000000..5517529
--- /dev/null
+++ b/chrome/common/net/notifier/listener/mediator_thread_impl.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2009 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 object runs on a thread and knows how to interpret messages sent by the
+// talk mediator. The mediator posts messages to a queue which the thread polls
+// (in a super class).
+//
+// Example usage:
+//
+// MediatorThread m = new MediatorThreadImpl(pass in stuff);
+// m.start(); // Start the thread
+// // Once the thread is started, you can do server stuff.
+// m.Login(loginInformation);
+// // Events happen, the mediator finds out through its pump more messages
+// // are dispatched to the thread eventually we want to log out.
+// m.Logout();
+// delete m; // Also stops the thread.
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/net/notifier/communicator/login.h"
+#include "chrome/common/net/notifier/communicator/login_failure.h"
+#include "chrome/common/net/notifier/listener/mediator_thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+class TaskPump;
+} // namespace notifier
+
+namespace buzz {
+class XmppClient;
+} // namespace buzz
+
+namespace talk_base {
+class SocketServer;
+} // namespace talk_base
+
+namespace notifier {
+
+enum MEDIATOR_CMD {
+ CMD_LOGIN,
+ CMD_DISCONNECT,
+ CMD_LISTEN_FOR_UPDATES,
+ CMD_SEND_NOTIFICATION,
+ CMD_SUBSCRIBE_FOR_UPDATES,
+ CMD_PUMP_AUXILIARY_LOOPS,
+};
+
+// Used to pass authentication information from the mediator to the thread.
+// Use new to allocate it on the heap, the thread will delete it for you.
+struct LoginData : public talk_base::MessageData {
+ explicit LoginData(const buzz::XmppClientSettings& settings)
+ : user_settings(settings) {
+ }
+ virtual ~LoginData() {}
+
+ buzz::XmppClientSettings user_settings;
+};
+
+// Used to pass subscription information from the mediator to the thread.
+// Use new to allocate it on the heap, the thread will delete it for you.
+struct SubscriptionData : public talk_base::MessageData {
+ explicit SubscriptionData(const std::vector<std::string>& services)
+ : subscribed_services_list(services) {
+ }
+ virtual ~SubscriptionData() {}
+
+ std::vector<std::string> subscribed_services_list;
+};
+
+// Used to pass outgoing notification information from the mediator to the
+// thread. Use new to allocate it on the heap, the thread will delete it
+// for you.
+struct OutgoingNotificationMessageData : public talk_base::MessageData {
+ explicit OutgoingNotificationMessageData(
+ const OutgoingNotificationData& data) : notification_data(data) {
+ }
+ virtual ~OutgoingNotificationMessageData() {}
+
+ OutgoingNotificationData notification_data;
+};
+
+class MediatorThreadImpl
+ : public MediatorThread,
+ public sigslot::has_slots<>,
+ public talk_base::MessageHandler,
+ public talk_base::Thread {
+ public:
+ explicit MediatorThreadImpl();
+ virtual ~MediatorThreadImpl();
+
+ // Start the thread.
+ virtual void Start();
+ virtual void Stop();
+ virtual void Run();
+
+ // These are called from outside threads, by the talk mediator object.
+ // They add messages to a queue which we poll in this thread.
+ void Login(const buzz::XmppClientSettings& settings);
+ void Logout();
+ void ListenForUpdates();
+ void SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list);
+ void SendNotification(const OutgoingNotificationData& data);
+ void LogStanzas();
+
+ private:
+ // Called from within the thread on internal events.
+ void ProcessMessages(int cms);
+ void OnMessage(talk_base::Message* msg);
+ void DoLogin(LoginData* login_data);
+ void DoDisconnect();
+ void DoSubscribeForUpdates(const SubscriptionData& subscription_data);
+ void DoListenForUpdates();
+ void DoSendNotification(
+ const OutgoingNotificationMessageData& data);
+ void DoStanzaLogging();
+ void PumpAuxiliaryLoops();
+
+ // These handle messages indicating an event happened in the outside world.
+ void OnUpdateListenerMessage(
+ const IncomingNotificationData& notification_data);
+ void OnUpdateNotificationSent(bool success);
+ void OnLoginFailureMessage(const notifier::LoginFailure& failure);
+ void OnClientStateChangeMessage(notifier::Login::ConnectionState state);
+ void OnSubscriptionStateChange(bool success);
+ void OnInputDebug(const char* msg, int length);
+ void OnOutputDebug(const char* msg, int length);
+
+ buzz::XmppClient* xmpp_client();
+
+ // All buzz::XmppClients are owned by their parent. The root parent is the
+ // SingleLoginTask created by the notifier::Login object. This in turn is
+ // owned by the TaskPump. They are destroyed either when processing is
+ // complete or the pump shuts down.
+ scoped_ptr<notifier::TaskPump> pump_;
+ scoped_ptr<notifier::Login> login_;
+ DISALLOW_COPY_AND_ASSIGN(MediatorThreadImpl);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
diff --git a/chrome/common/net/notifier/listener/mediator_thread_mock.h b/chrome/common/net/notifier/listener/mediator_thread_mock.h
new file mode 100644
index 0000000..4df36df
--- /dev/null
+++ b/chrome/common/net/notifier/listener/mediator_thread_mock.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2009 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 is mock for delicious testing.
+// It's very primitive, and it would have been better to use gmock, except
+// that gmock is only for linux.
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/net/notifier/listener/mediator_thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+
+class MockMediatorThread : public MediatorThread {
+ public:
+ MockMediatorThread() : MediatorThread() {
+ Reset();
+ }
+ ~MockMediatorThread() {}
+
+ void Reset() {
+ login_calls = 0;
+ logout_calls = 0;
+ start_calls = 0;
+ subscribe_calls = 0;
+ listen_calls = 0;
+ send_calls = 0;
+ }
+
+ // Overridden from MediatorThread
+ void Login(const buzz::XmppClientSettings& settings) {
+ login_calls++;
+ }
+
+ void Logout() {
+ logout_calls++;
+ }
+
+ void Start() {
+ start_calls++;
+ }
+
+ virtual void SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list) {
+ subscribe_calls++;
+ }
+
+ virtual void ListenForUpdates() {
+ listen_calls++;
+ }
+
+ virtual void SendNotification(const OutgoingNotificationData &) {
+ send_calls++;
+ }
+
+ // Callback control
+ void ChangeState(MediatorThread::MediatorMessage message) {
+ SignalStateChange(message);
+ }
+ void Notify(const IncomingNotificationData& data) {
+ SignalNotificationReceived(data);
+ }
+
+ // Intneral State
+ int login_calls;
+ int logout_calls;
+ int start_calls;
+ int subscribe_calls;
+ int listen_calls;
+ int send_calls;
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
diff --git a/chrome/common/net/notifier/listener/notification_constants.cc b/chrome/common/net/notifier/listener/notification_constants.cc
new file mode 100644
index 0000000..cbfbe3f
--- /dev/null
+++ b/chrome/common/net/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 "chrome/common/net/notifier/listener/notification_constants.h"
+
+namespace notifier {
+
+const char kNotifierNamespace[] = "google:notifier";
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/notification_constants.h b/chrome/common/net/notifier/listener/notification_constants.h
new file mode 100644
index 0000000..566dc90
--- /dev/null
+++ b/chrome/common/net/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 CHROME_COMMON_NET_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_
+
+namespace notifier {
+
+extern const char kNotifierNamespace[];
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_
diff --git a/chrome/common/net/notifier/listener/notification_defines.h b/chrome/common/net/notifier/listener/notification_defines.h
new file mode 100644
index 0000000..a2c0b42
--- /dev/null
+++ b/chrome/common/net/notifier/listener/notification_defines.h
@@ -0,0 +1,34 @@
+// 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 CHROME_COMMON_NET_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_
+
+#include <string>
+
+struct IncomingNotificationData {
+ std::string service_url;
+ std::string service_specific_data;
+};
+
+struct OutgoingNotificationData {
+ OutgoingNotificationData() : send_content(false), priority(0),
+ require_subscription(false),
+ write_to_cache_only(false) {
+ }
+ // Id values
+ std::string service_url;
+ std::string service_id;
+ // This bool signifies whether the content fields should be
+ // sent with the outgoing data.
+ bool send_content;
+ // Content values.
+ std::string service_specific_data;
+ int priority;
+ bool require_subscription;
+ bool write_to_cache_only;
+};
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_
+
diff --git a/chrome/common/net/notifier/listener/send_update_task.cc b/chrome/common/net/notifier/listener/send_update_task.cc
new file mode 100644
index 0000000..f1deb73
--- /dev/null
+++ b/chrome/common/net/notifier/listener/send_update_task.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/send_update_task.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/net/notifier/listener/notification_constants.h"
+#include "chrome/common/net/notifier/listener/xml_element_util.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppconstants.h"
+
+namespace notifier {
+
+SendUpdateTask::SendUpdateTask(Task* parent,
+ const OutgoingNotificationData& data)
+ : XmppTask(parent, buzz::XmppEngine::HL_SINGLE), // Watch for one reply.
+ notification_data_(data) {
+}
+
+SendUpdateTask::~SendUpdateTask() {
+}
+
+bool SendUpdateTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, GetClient()->jid().BareJid(), task_id()))
+ return false;
+ QueueStanza(stanza);
+ return true;
+}
+
+int SendUpdateTask::ProcessStart() {
+ LOG(INFO) << "P2P: Notification task started.";
+ scoped_ptr<buzz::XmlElement> stanza(
+ MakeUpdateMessage(notification_data_,
+ GetClient()->jid().BareJid(), task_id()));
+ LOG(INFO) << "P2P: Notification stanza: "
+ << XmlElementToString(*stanza.get());
+
+ if (SendStanza(stanza.get()) != buzz::XMPP_RETURN_OK) {
+ // TODO(brg) : Retry on error.
+ // TODO(akalin): Or maybe immediately return STATE_ERROR and let
+ // retries happen a higher level. In any case, STATE_ERROR should
+ // eventually be returned.
+ SignalStatusUpdate(false);
+ return STATE_DONE;
+ }
+ return STATE_RESPONSE;
+}
+
+int SendUpdateTask::ProcessResponse() {
+ LOG(INFO) << "P2P: Notification response received.";
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ LOG(INFO) << "P2P: Notification response: " << XmlElementToString(*stanza);
+ if (stanza->HasAttr(buzz::QN_TYPE) &&
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+ // Notify listeners of success.
+ SignalStatusUpdate(true);
+ return STATE_DONE;
+ }
+
+ // An error response was received.
+ // TODO(brg) : Error handling.
+ SignalStatusUpdate(false);
+ // TODO(akalin): This should be STATE_ERROR.
+ return STATE_DONE;
+}
+
+buzz::XmlElement* SendUpdateTask::MakeUpdateMessage(
+ const OutgoingNotificationData& notification_data,
+ const buzz::Jid& to_jid_bare, const std::string& task_id) {
+ DCHECK(to_jid_bare.IsBare());
+ static const buzz::QName kQnNotifierSet(true, kNotifierNamespace, "set");
+ static const buzz::QName kQnId(true, buzz::STR_EMPTY, "Id");
+ static const buzz::QName kQnContent(true, buzz::STR_EMPTY, "Content");
+
+ // Create our update stanza. The message is constructed as:
+ // <iq type='get' from='{fullJid}' to='{bareJid}' id='{#}'>
+ // <gn:set xmlns:gn="google:notifier" xmlns="">
+ // <Id>
+ // <ServiceUrl data="{Service_Url}" />
+ // <ServiceId data="{Service_Id}" />
+ // </Id>
+ // If content needs to be sent, then the below element is added
+ // <Content>
+ // <Priority int="{Priority}" />
+ // <RequireSubscription bool="{true/false}" />
+ // <!-- If is_transitional is set, this is omitted. -->
+ // <ServiceSpecificData data="{ServiceData}" />
+ // <WriteToCacheOnly bool="{true/false}" />
+ // </Content>
+ // </set>
+ // </iq>
+ buzz::XmlElement* iq = MakeIq(buzz::STR_GET, to_jid_bare, task_id);
+ buzz::XmlElement* set = new buzz::XmlElement(kQnNotifierSet, true);
+ buzz::XmlElement* id = new buzz::XmlElement(kQnId, true);
+ iq->AddElement(set);
+ set->AddElement(id);
+
+ id->AddElement(MakeStringXmlElement("ServiceUrl",
+ notification_data.service_url.c_str()));
+ id->AddElement(MakeStringXmlElement("ServiceId",
+ notification_data.service_id.c_str()));
+
+ if (notification_data.send_content) {
+ buzz::XmlElement* content = new buzz::XmlElement(kQnContent, true);
+ set->AddElement(content);
+ content->AddElement(MakeIntXmlElement("Priority",
+ notification_data.priority));
+ content->AddElement(
+ MakeBoolXmlElement("RequireSubscription",
+ notification_data.require_subscription));
+ if (!notification_data.service_specific_data.empty()) {
+ content->AddElement(
+ MakeStringXmlElement("ServiceSpecificData",
+ notification_data.service_specific_data.c_str()));
+ }
+ content->AddElement(
+ MakeBoolXmlElement("WriteToCacheOnly",
+ notification_data.write_to_cache_only));
+ }
+ return iq;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/send_update_task.h b/chrome/common/net/notifier/listener/send_update_task.h
new file mode 100644
index 0000000..4ee5df7
--- /dev/null
+++ b/chrome/common/net/notifier/listener/send_update_task.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2009 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 CHROME_COMMON_NET_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
+
+#include <string>
+
+#include "chrome/common/net/notifier/listener/notification_defines.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmpptask.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace notifier {
+
+class SendUpdateTask : public buzz::XmppTask {
+ public:
+ SendUpdateTask(Task* parent, const OutgoingNotificationData& data);
+ virtual ~SendUpdateTask();
+
+ // Overridden from buzz::XmppTask.
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+
+ // Signal callback upon subscription success.
+ sigslot::signal1<bool> SignalStatusUpdate;
+
+ private:
+ // Allocates and constructs an buzz::XmlElement containing the update stanza.
+ static buzz::XmlElement* MakeUpdateMessage(
+ const OutgoingNotificationData& notification_data,
+ const buzz::Jid& to_jid_bare, const std::string& task_id);
+
+ OutgoingNotificationData notification_data_;
+
+ FRIEND_TEST(SendUpdateTaskTest, MakeUpdateMessage);
+
+ DISALLOW_COPY_AND_ASSIGN(SendUpdateTask);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
diff --git a/chrome/common/net/notifier/listener/send_update_task_unittest.cc b/chrome/common/net/notifier/listener/send_update_task_unittest.cc
new file mode 100644
index 0000000..85c1ba8
--- /dev/null
+++ b/chrome/common/net/notifier/listener/send_update_task_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/send_update_task.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/common/net/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 SendUpdateTaskTest : public testing::Test {
+ public:
+ SendUpdateTaskTest() : to_jid_bare_("to@jid.com"), task_id_("taskid") {
+ EXPECT_EQ(to_jid_bare_.Str(), to_jid_bare_.BareJid().Str());
+ }
+
+ protected:
+ const buzz::Jid to_jid_bare_;
+ const std::string task_id_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SendUpdateTaskTest);
+};
+
+TEST_F(SendUpdateTaskTest, MakeUpdateMessage) {
+ OutgoingNotificationData data;
+ data.service_id = "test_service_id";
+ data.service_url = "test_service_url";
+ data.send_content = false;
+ data.priority = 200;
+ data.write_to_cache_only = true;
+ data.require_subscription = false;
+
+ scoped_ptr<buzz::XmlElement> message_without_content(
+ SendUpdateTask::MakeUpdateMessage(data, to_jid_bare_, task_id_));
+
+ std::string expected_xml_string =
+ StringPrintf(
+ "<cli:iq type=\"get\" to=\"%s\" id=\"%s\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<set xmlns=\"google:notifier\">"
+ "<Id xmlns=\"\">"
+ "<ServiceUrl xmlns=\"\" data=\"test_service_url\"/>"
+ "<ServiceId xmlns=\"\" data=\"test_service_id\"/>"
+ "</Id>"
+ "</set>"
+ "</cli:iq>",
+ to_jid_bare_.Str().c_str(), task_id_.c_str());
+ EXPECT_EQ(expected_xml_string, XmlElementToString(*message_without_content));
+
+ data.send_content = true;
+
+ expected_xml_string =
+ StringPrintf(
+ "<cli:iq type=\"get\" to=\"%s\" id=\"%s\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<set xmlns=\"google:notifier\">"
+ "<Id xmlns=\"\">"
+ "<ServiceUrl xmlns=\"\" "
+ "data=\"test_service_url\"/>"
+ "<ServiceId xmlns=\"\" data=\"test_service_id\"/>"
+ "</Id>"
+ "<Content xmlns=\"\">"
+ "<Priority xmlns=\"\" int=\"200\"/>"
+ "<RequireSubscription xmlns=\"\" bool=\"false\"/>"
+ "<WriteToCacheOnly xmlns=\"\" bool=\"true\"/>"
+ "</Content>"
+ "</set>"
+ "</cli:iq>",
+ to_jid_bare_.Str().c_str(), task_id_.c_str());
+
+ scoped_ptr<buzz::XmlElement> message_with_content(
+ SendUpdateTask::MakeUpdateMessage(data, to_jid_bare_, task_id_));
+
+ EXPECT_EQ(expected_xml_string, XmlElementToString(*message_with_content));
+
+ data.service_specific_data = "test_service_specific_data";
+ data.require_subscription = true;
+
+ expected_xml_string =
+ StringPrintf(
+ "<cli:iq type=\"get\" to=\"%s\" id=\"%s\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<set xmlns=\"google:notifier\">"
+ "<Id xmlns=\"\">"
+ "<ServiceUrl xmlns=\"\" "
+ "data=\"test_service_url\"/>"
+ "<ServiceId xmlns=\"\" data=\"test_service_id\"/>"
+ "</Id>"
+ "<Content xmlns=\"\">"
+ "<Priority xmlns=\"\" int=\"200\"/>"
+ "<RequireSubscription xmlns=\"\" bool=\"true\"/>"
+ "<ServiceSpecificData xmlns=\"\" "
+ "data=\"test_service_specific_data\"/>"
+ "<WriteToCacheOnly xmlns=\"\" bool=\"true\"/>"
+ "</Content>"
+ "</set>"
+ "</cli:iq>",
+ to_jid_bare_.Str().c_str(), task_id_.c_str());
+
+ scoped_ptr<buzz::XmlElement> message_with_data(
+ SendUpdateTask::MakeUpdateMessage(data, to_jid_bare_, task_id_));
+
+ EXPECT_EQ(expected_xml_string, XmlElementToString(*message_with_data));
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/subscribe_task.cc b/chrome/common/net/notifier/listener/subscribe_task.cc
new file mode 100644
index 0000000..8fc2c79
--- /dev/null
+++ b/chrome/common/net/notifier/listener/subscribe_task.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/subscribe_task.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "chrome/common/net/notifier/listener/notification_constants.h"
+#include "chrome/common/net/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/xmppconstants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+SubscribeTask::SubscribeTask(
+ Task* parent,
+ const std::vector<std::string>& subscribed_services_list)
+ : XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+ subscribed_services_list_(subscribed_services_list) {
+}
+
+SubscribeTask::~SubscribeTask() {
+}
+
+bool SubscribeTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, GetClient()->jid().BareJid(), task_id()))
+ return false;
+ QueueStanza(stanza);
+ return true;
+}
+
+int SubscribeTask::ProcessStart() {
+ LOG(INFO) << "P2P: Subscription task started.";
+ scoped_ptr<buzz::XmlElement> iq_stanza(
+ MakeSubscriptionMessage(subscribed_services_list_,
+ GetClient()->jid().BareJid(), task_id()));
+ LOG(INFO) << "P2P: Subscription stanza: "
+ << XmlElementToString(*iq_stanza.get());
+
+ if (SendStanza(iq_stanza.get()) != buzz::XMPP_RETURN_OK) {
+ SignalStatusUpdate(false);
+ // TODO(akalin): This should be STATE_ERROR.
+ return STATE_DONE;
+ }
+ return STATE_RESPONSE;
+}
+
+int SubscribeTask::ProcessResponse() {
+ LOG(INFO) << "P2P: Subscription response received.";
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ LOG(INFO) << "P2P: Subscription response: " << XmlElementToString(*stanza);
+ // We've receieved a response to our subscription request.
+ if (stanza->HasAttr(buzz::QN_TYPE) &&
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+ SignalStatusUpdate(true);
+ return STATE_DONE;
+ }
+ // An error response was received.
+ // TODO(brg) : Error handling.
+ SignalStatusUpdate(false);
+ // TODO(akalin): This should be STATE_ERROR.
+ return STATE_DONE;
+}
+
+buzz::XmlElement* SubscribeTask::MakeSubscriptionMessage(
+ const std::vector<std::string>& subscribed_services_list,
+ const buzz::Jid& to_jid_bare, const std::string& task_id) {
+ DCHECK(to_jid_bare.IsBare());
+ static const buzz::QName kQnNotifierGetAll(
+ true, kNotifierNamespace, "getAll");
+
+ // Create the subscription stanza using the notifications protocol.
+ // <iq type='get' from='{fullJid}' to='{bareJid}' id='{#}'>
+ // <gn:getAll xmlns:gn="google:notifier" xmlns="">
+ // <ClientActive bool="true" />
+ // <!-- present only if subscribed_services_list is not empty -->
+ // <SubscribedServiceUrl data="google:notifier">
+ // <SubscribedServiceUrl data="http://www.google.com/chrome/sync">
+ // <FilterNonSubscribed bool="true" />
+ // </gn:getAll>
+ // </iq>
+
+ buzz::XmlElement* iq = MakeIq(buzz::STR_GET, to_jid_bare, task_id);
+ buzz::XmlElement* get_all = new buzz::XmlElement(kQnNotifierGetAll, true);
+ iq->AddElement(get_all);
+
+ get_all->AddElement(MakeBoolXmlElement("ClientActive", true));
+ for (std::vector<std::string>::const_iterator iter =
+ subscribed_services_list.begin();
+ iter != subscribed_services_list.end(); ++iter) {
+ get_all->AddElement(
+ MakeStringXmlElement("SubscribedServiceUrl", iter->c_str()));
+ }
+ if (!subscribed_services_list.empty()) {
+ get_all->AddElement(MakeBoolXmlElement("FilterNonSubscribed", true));
+ }
+ return iq;
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/subscribe_task.h b/chrome/common/net/notifier/listener/subscribe_task.h
new file mode 100644
index 0000000..5aa0685
--- /dev/null
+++ b/chrome/common/net/notifier/listener/subscribe_task.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2009 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 talk notifications. It does the getAll iq
+// stanza which establishes the endpoint and directs future notifications to be
+// pushed.
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmpptask.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace notifier {
+// TODO(akalin): Remove NOTIFICATION_LEGACY and remove/refactor relevant code
+// in this class and any other class that uses notification_method.
+class SubscribeTask : public buzz::XmppTask {
+ public:
+ SubscribeTask(Task* parent,
+ const std::vector<std::string>& subscribed_services_list);
+ virtual ~SubscribeTask();
+
+ // Overridden from XmppTask.
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+
+ // Signal callback upon subscription success.
+ sigslot::signal1<bool> SignalStatusUpdate;
+
+ private:
+ // Assembles an Xmpp stanza which can be sent to subscribe to notifications.
+ static buzz::XmlElement* MakeSubscriptionMessage(
+ const std::vector<std::string>& subscribed_services_list,
+ const buzz::Jid& to_jid_bare, const std::string& task_id);
+
+ std::vector<std::string> subscribed_services_list_;
+
+ FRIEND_TEST(SubscribeTaskTest, MakeSubscriptionMessage);
+
+ DISALLOW_COPY_AND_ASSIGN(SubscribeTask);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
diff --git a/chrome/common/net/notifier/listener/subscribe_task_unittest.cc b/chrome/common/net/notifier/listener/subscribe_task_unittest.cc
new file mode 100644
index 0000000..cda6b75
--- /dev/null
+++ b/chrome/common/net/notifier/listener/subscribe_task_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/subscribe_task.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/common/net/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 SubscribeTaskTest : public testing::Test {
+ public:
+ SubscribeTaskTest() : to_jid_bare_("to@jid.com"), task_id_("taskid") {
+ EXPECT_EQ(to_jid_bare_.Str(), to_jid_bare_.BareJid().Str());
+ }
+
+ protected:
+ const buzz::Jid to_jid_bare_;
+ const std::string task_id_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SubscribeTaskTest);
+};
+
+TEST_F(SubscribeTaskTest, MakeSubscriptionMessage) {
+ std::vector<std::string> subscribed_services_list;
+ scoped_ptr<buzz::XmlElement> message_without_services(
+ SubscribeTask::MakeSubscriptionMessage(subscribed_services_list,
+ to_jid_bare_, task_id_));
+ std::string expected_xml_string =
+ StringPrintf(
+ "<cli:iq type=\"get\" to=\"%s\" id=\"%s\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<getAll xmlns=\"google:notifier\">"
+ "<ClientActive xmlns=\"\" bool=\"true\"/>"
+ "</getAll>"
+ "</cli:iq>",
+ to_jid_bare_.Str().c_str(), task_id_.c_str());
+ EXPECT_EQ(expected_xml_string, XmlElementToString(*message_without_services));
+
+ subscribed_services_list.push_back("test_service_url1");
+ subscribed_services_list.push_back("test_service_url2");
+ scoped_ptr<buzz::XmlElement> message_with_services(
+ SubscribeTask::MakeSubscriptionMessage(subscribed_services_list,
+ to_jid_bare_, task_id_));
+ expected_xml_string =
+ StringPrintf(
+ "<cli:iq type=\"get\" to=\"%s\" id=\"%s\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<getAll xmlns=\"google:notifier\">"
+ "<ClientActive xmlns=\"\" bool=\"true\"/>"
+ "<SubscribedServiceUrl "
+ "xmlns=\"\" data=\"test_service_url1\"/>"
+ "<SubscribedServiceUrl "
+ "xmlns=\"\" data=\"test_service_url2\"/>"
+ "<FilterNonSubscribed xmlns=\"\" bool=\"true\"/>"
+ "</getAll>"
+ "</cli:iq>",
+ to_jid_bare_.Str().c_str(), task_id_.c_str());
+
+ EXPECT_EQ(expected_xml_string, XmlElementToString(*message_with_services));
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/talk_mediator.h b/chrome/common/net/notifier/listener/talk_mediator.h
new file mode 100644
index 0000000..5580691
--- /dev/null
+++ b/chrome/common/net/notifier/listener/talk_mediator.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2009 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.
+//
+// Interface to the code which handles talk logic. Used to initialize SSL
+// before the underlying talk login occurs.
+//
+// Example usage:
+//
+// TalkMediator mediator();
+// mediator.SetAuthToken("email", "token", "service_id");
+// mediator.Login();
+// ...
+// mediator.Logout();
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
+
+#include <string>
+
+#include "chrome/common/net/notifier/listener/notification_defines.h"
+#include "chrome/common/deprecated/event_sys.h"
+
+namespace notifier {
+
+struct TalkMediatorEvent {
+ enum WhatHappened {
+ LOGIN_SUCCEEDED,
+ LOGOUT_SUCCEEDED,
+ SUBSCRIPTIONS_ON,
+ SUBSCRIPTIONS_OFF,
+ NOTIFICATION_RECEIVED,
+ NOTIFICATION_SENT,
+ TALKMEDIATOR_DESTROYED,
+ };
+
+ // Required by EventChannel.
+ typedef TalkMediatorEvent EventType;
+
+ static inline bool IsChannelShutdownEvent(const TalkMediatorEvent& event) {
+ return event.what_happened == TALKMEDIATOR_DESTROYED;
+ }
+
+ WhatHappened what_happened;
+ // Data in the case of a NOTIFICATION_RECEIVED event
+ IncomingNotificationData notification_data;
+};
+
+typedef EventChannel<TalkMediatorEvent, Lock> TalkMediatorChannel;
+
+class TalkMediator {
+ public:
+ TalkMediator() {}
+ virtual ~TalkMediator() {}
+
+ // The following methods are for authorizaiton of the xmpp client.
+ virtual bool SetAuthToken(const std::string& email,
+ const std::string& token,
+ const std::string& token_service) = 0;
+ virtual bool Login() = 0;
+ virtual bool Logout() = 0;
+
+ // Method for the owner of this object to notify peers that an update has
+ // occurred.
+ virtual bool SendNotification(const OutgoingNotificationData& data) = 0;
+
+ // Channel by which talk mediator events are signaled.
+ virtual TalkMediatorChannel* channel() const = 0;
+
+ // Add a URL to subscribe to for notifications.
+ virtual void AddSubscribedServiceUrl(const std::string& service_url) = 0;
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
diff --git a/chrome/common/net/notifier/listener/talk_mediator_impl.cc b/chrome/common/net/notifier/listener/talk_mediator_impl.cc
new file mode 100644
index 0000000..55eb815
--- /dev/null
+++ b/chrome/common/net/notifier/listener/talk_mediator_impl.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/talk_mediator_impl.h"
+
+#include "base/logging.h"
+#include "base/singleton.h"
+#include "chrome/common/net/notifier/listener/mediator_thread_impl.h"
+#include "chrome/common/deprecated/event_sys-inl.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/ssladapter.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+// Before any authorization event from TalkMediatorImpl, we need to initialize
+// the SSL library.
+class SslInitializationSingleton {
+ public:
+ virtual ~SslInitializationSingleton() {
+ talk_base::CleanupSSL();
+ };
+
+ void RegisterClient() {}
+
+ static SslInitializationSingleton* GetInstance() {
+ return Singleton<SslInitializationSingleton>::get();
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<SslInitializationSingleton>;
+
+ SslInitializationSingleton() {
+ talk_base::InitializeSSL();
+ };
+
+ DISALLOW_COPY_AND_ASSIGN(SslInitializationSingleton);
+};
+
+TalkMediatorImpl::TalkMediatorImpl(bool invalidate_xmpp_auth_token)
+ : mediator_thread_(new MediatorThreadImpl()),
+ invalidate_xmpp_auth_token_(invalidate_xmpp_auth_token) {
+ // Ensure the SSL library is initialized.
+ SslInitializationSingleton::GetInstance()->RegisterClient();
+
+ // Construct the callback channel with the shutdown event.
+ TalkMediatorInitialization(false);
+}
+
+TalkMediatorImpl::TalkMediatorImpl(MediatorThread *thread)
+ : mediator_thread_(thread),
+ invalidate_xmpp_auth_token_(false) {
+ // When testing we do not initialize the SSL library.
+ TalkMediatorInitialization(true);
+}
+
+void TalkMediatorImpl::TalkMediatorInitialization(bool should_connect) {
+ TalkMediatorEvent done = { TalkMediatorEvent::TALKMEDIATOR_DESTROYED };
+ channel_.reset(new TalkMediatorChannel(done));
+ if (should_connect) {
+ mediator_thread_->SignalStateChange.connect(
+ this,
+ &TalkMediatorImpl::MediatorThreadMessageHandler);
+ mediator_thread_->SignalNotificationReceived.connect(
+ this,
+ &TalkMediatorImpl::MediatorThreadNotificationHandler);
+ state_.connected = 1;
+ }
+ mediator_thread_->Start();
+ state_.started = 1;
+}
+
+TalkMediatorImpl::~TalkMediatorImpl() {
+ if (state_.started) {
+ Logout();
+ }
+}
+
+bool TalkMediatorImpl::Login() {
+ AutoLock lock(mutex_);
+ // Connect to the mediator thread and start processing messages.
+ if (!state_.connected) {
+ mediator_thread_->SignalStateChange.connect(
+ this,
+ &TalkMediatorImpl::MediatorThreadMessageHandler);
+ mediator_thread_->SignalNotificationReceived.connect(
+ this,
+ &TalkMediatorImpl::MediatorThreadNotificationHandler);
+ state_.connected = 1;
+ }
+ if (state_.initialized && !state_.logging_in) {
+ mediator_thread_->Login(xmpp_settings_);
+ state_.logging_in = 1;
+ return true;
+ }
+ return false;
+}
+
+bool TalkMediatorImpl::Logout() {
+ AutoLock lock(mutex_);
+ // We do not want to be called back during logout since we may be closing.
+ if (state_.connected) {
+ mediator_thread_->SignalStateChange.disconnect(this);
+ mediator_thread_->SignalNotificationReceived.disconnect(this);
+ state_.connected = 0;
+ }
+ if (state_.started) {
+ mediator_thread_->Logout();
+ state_.started = 0;
+ state_.logging_in = 0;
+ state_.logged_in = 0;
+ state_.subscribed = 0;
+ return true;
+ }
+ return false;
+}
+
+bool TalkMediatorImpl::SendNotification(const OutgoingNotificationData& data) {
+ AutoLock lock(mutex_);
+ if (state_.logged_in && state_.subscribed) {
+ mediator_thread_->SendNotification(data);
+ return true;
+ }
+ return false;
+}
+
+TalkMediatorChannel* TalkMediatorImpl::channel() const {
+ return channel_.get();
+}
+
+bool TalkMediatorImpl::SetAuthToken(const std::string& email,
+ const std::string& token,
+ const std::string& token_service) {
+ AutoLock lock(mutex_);
+
+ // Verify that we can create a JID from the email provided.
+ buzz::Jid jid = buzz::Jid(email);
+ if (jid.node().empty() || !jid.IsValid()) {
+ return false;
+ }
+
+ // Construct the XmppSettings object for login to buzz.
+ xmpp_settings_.set_user(jid.node());
+ xmpp_settings_.set_resource("chrome-sync");
+ xmpp_settings_.set_host(jid.domain());
+ xmpp_settings_.set_use_tls(true);
+ xmpp_settings_.set_auth_cookie(invalidate_xmpp_auth_token_ ?
+ token + "bogus" : token);
+ xmpp_settings_.set_token_service(token_service);
+
+ state_.initialized = 1;
+ return true;
+}
+
+void TalkMediatorImpl::AddSubscribedServiceUrl(
+ const std::string& service_url) {
+ subscribed_services_list_.push_back(service_url);
+ if (state_.logged_in) {
+ LOG(INFO) << "Resubscribing for updates, a new service got added";
+ mediator_thread_->SubscribeForUpdates(subscribed_services_list_);
+ }
+}
+
+
+void TalkMediatorImpl::MediatorThreadMessageHandler(
+ MediatorThread::MediatorMessage message) {
+ LOG(INFO) << "P2P: MediatorThread has passed a message";
+ switch (message) {
+ case MediatorThread::MSG_LOGGED_IN:
+ OnLogin();
+ break;
+ case MediatorThread::MSG_LOGGED_OUT:
+ OnLogout();
+ break;
+ case MediatorThread::MSG_SUBSCRIPTION_SUCCESS:
+ OnSubscriptionSuccess();
+ break;
+ case MediatorThread::MSG_SUBSCRIPTION_FAILURE:
+ OnSubscriptionFailure();
+ break;
+ case MediatorThread::MSG_NOTIFICATION_SENT:
+ OnNotificationSent();
+ break;
+ default:
+ LOG(WARNING) << "P2P: Unknown message returned from mediator thread.";
+ break;
+ }
+}
+
+void TalkMediatorImpl::MediatorThreadNotificationHandler(
+ const IncomingNotificationData& notification_data) {
+ LOG(INFO) << "P2P: Updates are available on the server.";
+ AutoLock lock(mutex_);
+ TalkMediatorEvent event = { TalkMediatorEvent::NOTIFICATION_RECEIVED };
+ event.notification_data = notification_data;
+ channel_->NotifyListeners(event);
+}
+
+
+void TalkMediatorImpl::OnLogin() {
+ LOG(INFO) << "P2P: Logged in.";
+ AutoLock lock(mutex_);
+ state_.logging_in = 0;
+ state_.logged_in = 1;
+ // ListenForUpdates enables the ListenTask. This is done before
+ // SubscribeForUpdates.
+ mediator_thread_->ListenForUpdates();
+ // Now subscribe for updates to all the services we are interested in
+ mediator_thread_->SubscribeForUpdates(subscribed_services_list_);
+ TalkMediatorEvent event = { TalkMediatorEvent::LOGIN_SUCCEEDED };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnLogout() {
+ LOG(INFO) << "P2P: Logged off.";
+ OnSubscriptionFailure();
+ AutoLock lock(mutex_);
+ state_.logging_in = 0;
+ state_.logged_in = 0;
+ TalkMediatorEvent event = { TalkMediatorEvent::LOGOUT_SUCCEEDED };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnSubscriptionSuccess() {
+ LOG(INFO) << "P2P: Update subscription active.";
+ {
+ AutoLock lock(mutex_);
+ state_.subscribed = 1;
+ }
+ // The above scope exists so that we can release the lock before
+ // notifying listeners. In theory we should do this for all methods.
+ // Notifying listeners with a lock held can cause the lock to be
+ // recursively taken if the listener decides to call back into us
+ // in the event handler.
+ TalkMediatorEvent event = { TalkMediatorEvent::SUBSCRIPTIONS_ON };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnSubscriptionFailure() {
+ LOG(INFO) << "P2P: Update subscription failure.";
+ AutoLock lock(mutex_);
+ state_.subscribed = 0;
+ TalkMediatorEvent event = { TalkMediatorEvent::SUBSCRIPTIONS_OFF };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnNotificationSent() {
+ LOG(INFO) <<
+ "P2P: Peers were notified that updates are available on the server.";
+ AutoLock lock(mutex_);
+ TalkMediatorEvent event = { TalkMediatorEvent::NOTIFICATION_SENT };
+ channel_->NotifyListeners(event);
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/talk_mediator_impl.h b/chrome/common/net/notifier/listener/talk_mediator_impl.h
new file mode 100644
index 0000000..7c701ed
--- /dev/null
+++ b/chrome/common/net/notifier/listener/talk_mediator_impl.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2009 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 is the interface between talk code and the client code proper
+// It will manage all aspects of the connection and call back into the client
+// when it needs attention (for instance if updates are available for syncing).
+
+#ifndef CHROME_COMMON_NET_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
+#define CHROME_COMMON_NET_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/lock.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/net/notifier/listener/mediator_thread.h"
+#include "chrome/common/net/notifier/listener/talk_mediator.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
+
+class EventListenerHookup;
+
+namespace notifier {
+
+class TalkMediatorImpl
+ : public TalkMediator,
+ public sigslot::has_slots<> {
+ public:
+ explicit TalkMediatorImpl(bool invalidate_xmpp_auth_token);
+ explicit TalkMediatorImpl(MediatorThread* thread);
+ virtual ~TalkMediatorImpl();
+
+ // Overriden from TalkMediator.
+ virtual bool SetAuthToken(const std::string& email,
+ const std::string& token,
+ const std::string& token_service);
+ virtual bool Login();
+ virtual bool Logout();
+
+ virtual bool SendNotification(const OutgoingNotificationData& data);
+
+ TalkMediatorChannel* channel() const;
+
+ virtual void AddSubscribedServiceUrl(const std::string& service_url);
+
+ private:
+ struct TalkMediatorState {
+ TalkMediatorState()
+ : started(0), connected(0), initialized(0), logging_in(0),
+ logged_in(0), subscribed(0) {
+ }
+
+ unsigned int started : 1; // Background thread has started.
+ unsigned int connected : 1; // Connected to the mediator thread signal.
+ unsigned int initialized : 1; // Initialized with login information.
+ unsigned int logging_in : 1; // Logging in to the mediator's
+ // authenticator.
+ unsigned int logged_in : 1; // Logged in the mediator's authenticator.
+ unsigned int subscribed : 1; // Subscribed to the xmpp receiving channel.
+ };
+
+ // Completes common initialization between the constructors. Set should
+ // connect to true if the talk mediator should connect to the controlled
+ // mediator thread's SignalStateChange object.
+ void TalkMediatorInitialization(bool should_connect);
+
+ // Callbacks for the mediator thread.
+ void MediatorThreadMessageHandler(MediatorThread::MediatorMessage message);
+ void MediatorThreadNotificationHandler(
+ const IncomingNotificationData& notification_data);
+
+ // Responses to messages from the MediatorThread.
+ void OnNotificationSent();
+ void OnLogin();
+ void OnLogout();
+ void OnSubscriptionFailure();
+ void OnSubscriptionSuccess();
+
+ // Mutex for synchronizing event access. This class listens to events
+ // from MediatorThread. It can also be called by through the
+ // TalkMediatorInteface. All these access points are serialized by
+ // this mutex.
+ Lock mutex_;
+
+ // Internal state.
+ TalkMediatorState state_;
+
+ // Cached and verfied from the SetAuthToken method.
+ buzz::XmppClientSettings xmpp_settings_;
+
+ // Interface to listen to authentication events.
+ scoped_ptr<EventListenerHookup> auth_hookup_;
+
+ // The worker thread through which talk events are posted and received.
+ scoped_ptr<MediatorThread> mediator_thread_;
+
+ // Channel through which to broadcast events.
+ scoped_ptr<TalkMediatorChannel> channel_;
+
+ bool invalidate_xmpp_auth_token_;
+
+ std::vector<std::string> subscribed_services_list_;
+
+ FRIEND_TEST(TalkMediatorImplTest, SetAuthTokenWithBadInput);
+ FRIEND_TEST(TalkMediatorImplTest, SetAuthTokenWithGoodInput);
+ FRIEND_TEST(TalkMediatorImplTest, SendNotification);
+ FRIEND_TEST(TalkMediatorImplTest, MediatorThreadCallbacks);
+ DISALLOW_COPY_AND_ASSIGN(TalkMediatorImpl);
+};
+
+} // namespace notifier
+
+#endif // CHROME_COMMON_NET_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
diff --git a/chrome/common/net/notifier/listener/talk_mediator_unittest.cc b/chrome/common/net/notifier/listener/talk_mediator_unittest.cc
new file mode 100644
index 0000000..dd50d64
--- /dev/null
+++ b/chrome/common/net/notifier/listener/talk_mediator_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2009 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/logging.h"
+#include "chrome/common/net/notifier/listener/mediator_thread_mock.h"
+#include "chrome/common/net/notifier/listener/talk_mediator_impl.h"
+#include "chrome/common/deprecated/event_sys-inl.h"
+#include "talk/xmpp/xmppengine.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace notifier {
+
+class TalkMediatorImplTest : public testing::Test {
+ public:
+ void HandleTalkMediatorEvent(
+ const notifier::TalkMediatorEvent& event) {
+ last_message_ = event.what_happened;
+ }
+
+ protected:
+ TalkMediatorImplTest() {}
+ ~TalkMediatorImplTest() {}
+
+ virtual void SetUp() {
+ last_message_ = -1;
+ }
+
+ virtual void TearDown() {
+ }
+
+ int last_message_;
+};
+
+TEST_F(TalkMediatorImplTest, ConstructionOfTheClass) {
+ // Constructing a single talk mediator enables SSL through the singleton.
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(false));
+ talk1.reset(NULL);
+}
+
+TEST_F(TalkMediatorImplTest, SetAuthTokenWithBadInput) {
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_FALSE(talk1->SetAuthToken("@missinguser.com", "", "fake_service"));
+ ASSERT_TRUE(talk1->state_.initialized == 0);
+
+ scoped_ptr<TalkMediatorImpl> talk2(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_FALSE(talk2->SetAuthToken("", "1234567890", "fake_service"));
+ ASSERT_TRUE(talk2->state_.initialized == 0);
+
+ scoped_ptr<TalkMediatorImpl> talk3(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_FALSE(talk3->SetAuthToken("missingdomain", "abcde", "fake_service"));
+ ASSERT_TRUE(talk3->state_.initialized == 0);
+}
+
+TEST_F(TalkMediatorImplTest, SetAuthTokenWithGoodInput) {
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service"));
+ ASSERT_TRUE(talk1->state_.initialized == 1);
+
+ scoped_ptr<TalkMediatorImpl> talk2(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_TRUE(talk2->SetAuthToken("chromium@mail.google.com", "token",
+ "fake_service"));
+ ASSERT_TRUE(talk2->state_.initialized == 1);
+
+ scoped_ptr<TalkMediatorImpl> talk3(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_TRUE(talk3->SetAuthToken("chromium@chromium.org", "token",
+ "fake_service"));
+ ASSERT_TRUE(talk3->state_.initialized == 1);
+}
+
+TEST_F(TalkMediatorImplTest, LoginWiring) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(mock));
+
+ // Login checks states for initialization.
+ ASSERT_TRUE(talk1->Login() == false);
+ ASSERT_TRUE(mock->login_calls == 0);
+
+ ASSERT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service") == true);
+ ASSERT_TRUE(talk1->Login() == true);
+ ASSERT_TRUE(mock->login_calls == 1);
+
+ // Successive calls to login will fail. One needs to create a new talk
+ // mediator object.
+ ASSERT_TRUE(talk1->Login() == false);
+ ASSERT_TRUE(mock->login_calls == 1);
+
+ ASSERT_TRUE(talk1->Logout() == true);
+ ASSERT_TRUE(mock->logout_calls == 1);
+
+ // Successive logout calls do nothing.
+ ASSERT_TRUE(talk1->Logout() == false);
+ ASSERT_TRUE(mock->logout_calls == 1);
+}
+
+TEST_F(TalkMediatorImplTest, SendNotification) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(mock));
+
+ // Failure due to not being logged in.
+ OutgoingNotificationData data;
+ ASSERT_TRUE(talk1->SendNotification(data) == false);
+ ASSERT_TRUE(mock->send_calls == 0);
+
+ ASSERT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service") == true);
+ ASSERT_TRUE(talk1->Login() == true);
+ talk1->OnLogin();
+ ASSERT_TRUE(mock->login_calls == 1);
+
+ // Failure due to not being subscribed.
+ ASSERT_TRUE(talk1->SendNotification(data) == false);
+ ASSERT_TRUE(mock->send_calls == 0);
+
+ // Fake subscription
+ talk1->OnSubscriptionSuccess();
+ ASSERT_TRUE(talk1->state_.subscribed == 1);
+ ASSERT_TRUE(talk1->SendNotification(data) == true);
+ ASSERT_TRUE(mock->send_calls == 1);
+ ASSERT_TRUE(talk1->SendNotification(data) == true);
+ ASSERT_TRUE(mock->send_calls == 2);
+
+ ASSERT_TRUE(talk1->Logout() == true);
+ ASSERT_TRUE(mock->logout_calls == 1);
+
+ // Failure due to being logged out.
+ ASSERT_TRUE(talk1->SendNotification(data) == false);
+ ASSERT_TRUE(mock->send_calls == 2);
+}
+
+TEST_F(TalkMediatorImplTest, MediatorThreadCallbacks) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(mock));
+
+ scoped_ptr<EventListenerHookup> callback(NewEventListenerHookup(
+ talk1->channel(), this, &TalkMediatorImplTest::HandleTalkMediatorEvent));
+
+ ASSERT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service") == true);
+ ASSERT_TRUE(talk1->Login() == true);
+ ASSERT_TRUE(mock->login_calls == 1);
+
+ mock->ChangeState(MediatorThread::MSG_LOGGED_IN);
+ ASSERT_TRUE(last_message_ == TalkMediatorEvent::LOGIN_SUCCEEDED);
+
+ // The message triggers calls to listen and subscribe.
+ ASSERT_TRUE(mock->listen_calls == 1);
+ ASSERT_TRUE(mock->subscribe_calls == 1);
+ ASSERT_TRUE(talk1->state_.subscribed == 0);
+
+ mock->ChangeState(MediatorThread::MSG_SUBSCRIPTION_SUCCESS);
+ ASSERT_TRUE(last_message_ == TalkMediatorEvent::SUBSCRIPTIONS_ON);
+ ASSERT_TRUE(talk1->state_.subscribed == 1);
+
+ // After subscription success is receieved, the talk mediator will allow
+ // sending of notifications.
+ OutgoingNotificationData outgoing_data;
+ ASSERT_TRUE(talk1->SendNotification(outgoing_data) == true);
+ ASSERT_TRUE(mock->send_calls == 1);
+
+ IncomingNotificationData incoming_data;
+ incoming_data.service_url = "service_url";
+ incoming_data.service_specific_data = "service_data";
+ mock->Notify(incoming_data);
+ ASSERT_TRUE(last_message_ == TalkMediatorEvent::NOTIFICATION_RECEIVED);
+
+ // A |TALKMEDIATOR_DESTROYED| message is received during tear down.
+ talk1.reset();
+ ASSERT_TRUE(last_message_ == TalkMediatorEvent::TALKMEDIATOR_DESTROYED);
+}
+
+} // namespace notifier
diff --git a/chrome/common/net/notifier/listener/xml_element_util.cc b/chrome/common/net/notifier/listener/xml_element_util.cc
new file mode 100644
index 0000000..b9403f6
--- /dev/null
+++ b/chrome/common/net/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 "chrome/common/net/notifier/listener/xml_element_util.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/string_util.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(true, buzz::STR_EMPTY, name);
+ const buzz::QName boolAttrQName(true, 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(true, buzz::STR_EMPTY, name);
+ const buzz::QName intAttrQName(true, buzz::STR_EMPTY, "int");
+ buzz::XmlElement* int_xml_element =
+ new buzz::XmlElement(elementQName, true);
+ int_xml_element->AddAttr(intAttrQName, IntToString(value));
+ return int_xml_element;
+}
+
+buzz::XmlElement* MakeStringXmlElement(const char* name, const char* value) {
+ const buzz::QName elementQName(true, buzz::STR_EMPTY, name);
+ const buzz::QName dataAttrQName(true, 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/chrome/common/net/notifier/listener/xml_element_util.h b/chrome/common/net/notifier/listener/xml_element_util.h
new file mode 100644
index 0000000..a63077a
--- /dev/null
+++ b/chrome/common/net/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 CHROME_COMMON_NET_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_
+#define CHROME_COMMON_NET_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 // CHROME_COMMON_NET_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_
diff --git a/chrome/common/net/notifier/listener/xml_element_util_unittest.cc b/chrome/common/net/notifier/listener/xml_element_util_unittest.cc
new file mode 100644
index 0000000..ab1d823
--- /dev/null
+++ b/chrome/common/net/notifier/listener/xml_element_util_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/net/notifier/listener/xml_element_util.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/logging.h"
+#include "base/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(true, "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