diff options
Diffstat (limited to 'chrome/common')
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[] = ", "; +const char kEllidedSeparator[] = " .."; + +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 ? " " : 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(" ("); + count_html.append(count); + count_html.append(")"); + // Reduce space due to parenthesis and . + 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> (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, Alex, <b>me</b> (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>, <b>Alex</b>, <b>bob</b> (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>, <b>ReallyLo.</b> (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 .. <b>AnotherB.</b> .. (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 .. <b>Bob</b> .. <b>Person1</b> (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 |