summaryrefslogtreecommitdiffstats
path: root/jingle/notifier
diff options
context:
space:
mode:
Diffstat (limited to 'jingle/notifier')
-rw-r--r--jingle/notifier/DEPS6
-rw-r--r--jingle/notifier/base/signal_thread_task.h96
-rw-r--r--jingle/notifier/base/sigslotrepeater.h83
-rw-r--r--jingle/notifier/base/ssl_adapter.cc27
-rw-r--r--jingle/notifier/base/ssl_adapter.h33
-rw-r--r--jingle/notifier/base/static_assert.h21
-rw-r--r--jingle/notifier/base/task_pump.cc45
-rw-r--r--jingle/notifier/base/task_pump.h34
-rw-r--r--jingle/notifier/communicator/auto_reconnect.cc125
-rw-r--r--jingle/notifier/communicator/auto_reconnect.h67
-rw-r--r--jingle/notifier/communicator/connection_options.cc17
-rw-r--r--jingle/notifier/communicator/connection_options.h56
-rw-r--r--jingle/notifier/communicator/connection_settings.cc128
-rw-r--r--jingle/notifier/communicator/connection_settings.h76
-rw-r--r--jingle/notifier/communicator/const_communicator.h13
-rw-r--r--jingle/notifier/communicator/gaia_token_pre_xmpp_auth.cc76
-rw-r--r--jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h61
-rw-r--r--jingle/notifier/communicator/login.cc344
-rw-r--r--jingle/notifier/communicator/login.h151
-rw-r--r--jingle/notifier/communicator/login_connection_state.h24
-rw-r--r--jingle/notifier/communicator/login_failure.cc30
-rw-r--r--jingle/notifier/communicator/login_failure.h50
-rw-r--r--jingle/notifier/communicator/login_settings.cc59
-rw-r--r--jingle/notifier/communicator/login_settings.h102
-rw-r--r--jingle/notifier/communicator/product_info.cc15
-rw-r--r--jingle/notifier/communicator/product_info.h15
-rw-r--r--jingle/notifier/communicator/single_login_attempt.cc517
-rw-r--r--jingle/notifier/communicator/single_login_attempt.h138
-rw-r--r--jingle/notifier/communicator/ssl_socket_adapter.cc389
-rw-r--r--jingle/notifier/communicator/ssl_socket_adapter.h134
-rw-r--r--jingle/notifier/communicator/xmpp_connection_generator.cc214
-rw-r--r--jingle/notifier/communicator/xmpp_connection_generator.h94
-rw-r--r--jingle/notifier/communicator/xmpp_socket_adapter.cc429
-rw-r--r--jingle/notifier/communicator/xmpp_socket_adapter.h87
-rw-r--r--jingle/notifier/listener/listen_task.cc144
-rw-r--r--jingle/notifier/listener/listen_task.h49
-rw-r--r--jingle/notifier/listener/mediator_thread.h55
-rw-r--r--jingle/notifier/listener/mediator_thread_impl.cc368
-rw-r--r--jingle/notifier/listener/mediator_thread_impl.h142
-rw-r--r--jingle/notifier/listener/mediator_thread_mock.h97
-rw-r--r--jingle/notifier/listener/notification_constants.cc11
-rw-r--r--jingle/notifier/listener/notification_constants.h14
-rw-r--r--jingle/notifier/listener/notification_defines.h34
-rw-r--r--jingle/notifier/listener/send_update_task.cc131
-rw-r--r--jingle/notifier/listener/send_update_task.h47
-rw-r--r--jingle/notifier/listener/send_update_task_unittest.cc116
-rw-r--r--jingle/notifier/listener/subscribe_task.cc109
-rw-r--r--jingle/notifier/listener/subscribe_task.h51
-rw-r--r--jingle/notifier/listener/subscribe_task_unittest.cc74
-rw-r--r--jingle/notifier/listener/talk_mediator.h63
-rw-r--r--jingle/notifier/listener/talk_mediator_impl.cc195
-rw-r--r--jingle/notifier/listener/talk_mediator_impl.h104
-rw-r--r--jingle/notifier/listener/talk_mediator_unittest.cc207
-rw-r--r--jingle/notifier/listener/xml_element_util.cc51
-rw-r--r--jingle/notifier/listener/xml_element_util.h29
-rw-r--r--jingle/notifier/listener/xml_element_util_unittest.cc59
56 files changed, 5906 insertions, 0 deletions
diff --git a/jingle/notifier/DEPS b/jingle/notifier/DEPS
new file mode 100644
index 0000000..2d7d13f
--- /dev/null
+++ b/jingle/notifier/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ # notifier depends on libjingle.
+ "+talk/base",
+ "+talk/xmpp",
+ "+talk/xmllite",
+]
diff --git a/jingle/notifier/base/signal_thread_task.h b/jingle/notifier/base/signal_thread_task.h
new file mode 100644
index 0000000..8a6ff27
--- /dev/null
+++ b/jingle/notifier/base/signal_thread_task.h
@@ -0,0 +1,96 @@
+// 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 JINGLE_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
+#define JINGLE_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
+
+#include "base/logging.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() {
+ DCHECK_EQ(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) {
+ DCHECK(!signal_thread_);
+ DCHECK(signal_thread);
+ DCHECK(*signal_thread);
+ // No one should be listening to the signal thread for work done.
+ // They should be using this class instead. Unfortunately, we
+ // can't verify this.
+
+ 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) {
+ DCHECK_EQ(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 // JINGLE_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
diff --git a/jingle/notifier/base/sigslotrepeater.h b/jingle/notifier/base/sigslotrepeater.h
new file mode 100644
index 0000000..cafb491
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_BASE_SIGSLOTREPEATER_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_BASE_SIGSLOTREPEATER_H_
diff --git a/jingle/notifier/base/ssl_adapter.cc b/jingle/notifier/base/ssl_adapter.cc
new file mode 100644
index 0000000..2111244
--- /dev/null
+++ b/jingle/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 "jingle/notifier/base/ssl_adapter.h"
+
+#if defined(OS_WIN)
+#include "talk/base/ssladapter.h"
+#else
+#include "jingle/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/jingle/notifier/base/ssl_adapter.h b/jingle/notifier/base/ssl_adapter.h
new file mode 100644
index 0000000..32517cd
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_BASE_SSL_ADAPTER_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_BASE_SSL_ADAPTER_H_
+
diff --git a/jingle/notifier/base/static_assert.h b/jingle/notifier/base/static_assert.h
new file mode 100644
index 0000000..58a8fe4
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_BASE_STATIC_ASSERT_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_BASE_STATIC_ASSERT_H_
diff --git a/jingle/notifier/base/task_pump.cc b/jingle/notifier/base/task_pump.cc
new file mode 100644
index 0000000..52ffd26
--- /dev/null
+++ b/jingle/notifier/base/task_pump.cc
@@ -0,0 +1,45 @@
+// 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 "base/message_loop.h"
+#include "jingle/notifier/base/task_pump.h"
+
+namespace notifier {
+
+TaskPump::TaskPump()
+ : scoped_runnable_method_factory_(
+ ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ posted_wake_(false) {}
+
+TaskPump::~TaskPump() {}
+
+void TaskPump::WakeTasks() {
+ if (!posted_wake_) {
+ // Do the requested wake up.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ scoped_runnable_method_factory_.NewRunnableMethod(
+ &TaskPump::CheckAndRunTasks));
+ posted_wake_ = true;
+ }
+}
+
+int64 TaskPump::CurrentTime() {
+ // Only timeout tasks rely on this function. Since we're not using
+ // libjingle tasks for timeout, it's safe to return 0 here.
+ return 0;
+}
+
+void TaskPump::CheckAndRunTasks() {
+ posted_wake_ = false;
+ // We shouldn't be using libjingle for timeout tasks, so we should
+ // have no timeout tasks at all.
+
+ // TODO(akalin): Add HasTimeoutTask() back in TaskRunner class and
+ // uncomment this check.
+ // DCHECK(!HasTimeoutTask())
+ RunTasks();
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/base/task_pump.h b/jingle/notifier/base/task_pump.h
new file mode 100644
index 0000000..806fed2
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_BASE_TASK_PUMP_H_
+#define JINGLE_NOTIFIER_BASE_TASK_PUMP_H_
+
+#include "base/task.h"
+#include "talk/base/taskrunner.h"
+
+namespace notifier {
+
+class TaskPump : public talk_base::TaskRunner {
+ public:
+ TaskPump();
+
+ virtual ~TaskPump();
+
+ // talk_base::TaskRunner implementation.
+ virtual void WakeTasks();
+ virtual int64 CurrentTime();
+
+ private:
+ void CheckAndRunTasks();
+
+ ScopedRunnableMethodFactory<TaskPump> scoped_runnable_method_factory_;
+ bool posted_wake_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskPump);
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_BASE_TASK_PUMP_H_
diff --git a/jingle/notifier/communicator/auto_reconnect.cc b/jingle/notifier/communicator/auto_reconnect.cc
new file mode 100644
index 0000000..4b16e93
--- /dev/null
+++ b/jingle/notifier/communicator/auto_reconnect.cc
@@ -0,0 +1,125 @@
+// 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 "jingle/notifier/communicator/auto_reconnect.h"
+
+#include "base/rand_util.h"
+#include "jingle/notifier/communicator/login.h"
+
+namespace notifier {
+
+const int kResetReconnectInfoDelaySec = 2;
+
+AutoReconnect::AutoReconnect()
+ : reconnect_timer_started_(false),
+ is_idle_(false) {
+ SetupReconnectInterval();
+}
+
+void AutoReconnect::NetworkStateChanged(bool is_alive) {
+ if (is_retrying() && is_alive) {
+ // Reconnect in 1 to 9 seconds (vary the time a little to try to avoid
+ // spikey behavior on network hiccups).
+ StartReconnectTimerWithInterval(
+ base::TimeDelta::FromSeconds(base::RandInt(1, 9)));
+ }
+}
+
+void AutoReconnect::StartReconnectTimer() {
+ StartReconnectTimerWithInterval(reconnect_interval_);
+}
+
+void AutoReconnect::StartReconnectTimerWithInterval(
+ base::TimeDelta interval) {
+ // 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).
+ reconnect_timer_.Stop();
+ reconnect_timer_.Start(interval, this, &AutoReconnect::DoReconnect);
+ reconnect_timer_started_ = true;
+ SignalTimerStartStop();
+}
+
+void AutoReconnect::DoReconnect() {
+ reconnect_timer_started_ = false;
+
+ // If timed out again, double autoreconnect time up to 30 minutes.
+ reconnect_interval_ *= 2;
+ if (reconnect_interval_ > base::TimeDelta::FromMinutes(30)) {
+ reconnect_interval_ = base::TimeDelta::FromMinutes(30);
+ }
+ SignalStartConnection();
+}
+
+void AutoReconnect::StopReconnectTimer() {
+ if (reconnect_timer_started_) {
+ reconnect_timer_started_ = false;
+ reconnect_timer_.Stop();
+ SignalTimerStartStop();
+ }
+}
+
+void AutoReconnect::StopDelayedResetTimer() {
+ delayed_reset_timer_.Stop();
+}
+
+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_ =
+ base::TimeDelta::FromSeconds(base::RandInt(120, 360));
+ } else {
+ // If we weren't idle, try the connection 5 - 25 seconds later.
+ reconnect_interval_ =
+ base::TimeDelta::FromSeconds(base::RandInt(5, 25));
+ }
+}
+
+void AutoReconnect::OnPowerSuspend(bool suspended) {
+ if (suspended) {
+ // When the computer comes back on, ensure that the reconnect happens
+ // quickly (5 - 25 seconds).
+ reconnect_interval_ =
+ base::TimeDelta::FromSeconds(base::RandInt(5, 25));
+ }
+}
+
+void AutoReconnect::OnClientStateChange(LoginConnectionState state) {
+ // On any state change, stop the reset timer.
+ StopDelayedResetTimer();
+ switch (state) {
+ case STATE_RETRYING:
+ // Do nothing.
+ break;
+
+ case 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 STATE_OPENING:
+ StopReconnectTimer();
+ break;
+
+ case 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_.Start(
+ base::TimeDelta::FromSeconds(kResetReconnectInfoDelaySec),
+ this, &AutoReconnect::ResetState);
+ break;
+ }
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/communicator/auto_reconnect.h b/jingle/notifier/communicator/auto_reconnect.h
new file mode 100644
index 0000000..6b2fa09
--- /dev/null
+++ b/jingle/notifier/communicator/auto_reconnect.h
@@ -0,0 +1,67 @@
+// 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 JINGLE_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_
+
+#include <string>
+
+#include "base/time.h"
+#include "base/timer.h"
+#include "jingle/notifier/communicator/login_connection_state.h"
+#include "talk/base/sigslot.h"
+
+namespace notifier {
+
+class AutoReconnect : public sigslot::has_slots<> {
+ public:
+ AutoReconnect();
+ void StartReconnectTimer();
+ void StopReconnectTimer();
+ void OnClientStateChange(LoginConnectionState state);
+
+ void NetworkStateChanged(bool is_alive);
+
+ // 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_started_;
+ }
+
+ sigslot::signal0<> SignalTimerStartStop;
+ sigslot::signal0<> SignalStartConnection;
+
+ private:
+ void StartReconnectTimerWithInterval(base::TimeDelta interval);
+ void DoReconnect();
+ void ResetState();
+ void SetupReconnectInterval();
+ void StopDelayedResetTimer();
+
+ base::TimeDelta reconnect_interval_;
+ bool reconnect_timer_started_;
+ base::OneShotTimer<AutoReconnect> reconnect_timer_;
+ base::OneShotTimer<AutoReconnect> delayed_reset_timer_;
+
+ 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 // JINGLE_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_
diff --git a/jingle/notifier/communicator/connection_options.cc b/jingle/notifier/communicator/connection_options.cc
new file mode 100644
index 0000000..fe52d7f
--- /dev/null
+++ b/jingle/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 "jingle/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/jingle/notifier/communicator/connection_options.h b/jingle/notifier/communicator/connection_options.h
new file mode 100644
index 0000000..de09910
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_
diff --git a/jingle/notifier/communicator/connection_settings.cc b/jingle/notifier/communicator/connection_settings.cc
new file mode 100644
index 0000000..1347e46
--- /dev/null
+++ b/jingle/notifier/communicator/connection_settings.cc
@@ -0,0 +1,128 @@
+// 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 "base/logging.h"
+#include "jingle/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>(talk_base::CreateRandomId() % ceiling);
+ }
+};
+
+void ConnectionSettings::FillXmppClientSettings(
+ buzz::XmppClientSettings* xcs) const {
+ DCHECK(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);
+ 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) {
+ DCHECK(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/jingle/notifier/communicator/connection_settings.h b/jingle/notifier/communicator/connection_settings.h
new file mode 100644
index 0000000..54051a6
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_
diff --git a/jingle/notifier/communicator/const_communicator.h b/jingle/notifier/communicator/const_communicator.h
new file mode 100644
index 0000000..5f49a62
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_
+
+namespace notifier {
+// The default port for jabber/xmpp communications.
+const int kDefaultXmppPort = 5222;
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_
diff --git a/jingle/notifier/communicator/gaia_token_pre_xmpp_auth.cc b/jingle/notifier/communicator/gaia_token_pre_xmpp_auth.cc
new file mode 100644
index 0000000..bcc6f04
--- /dev/null
+++ b/jingle/notifier/communicator/gaia_token_pre_xmpp_auth.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h"
+
+#include <algorithm>
+
+#include "talk/base/socketaddress.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslcookiemechanism.h"
+
+namespace notifier {
+
+namespace {
+const char kGaiaAuthMechanism[] = "X-GOOGLE-TOKEN";
+} // namespace
+
+GaiaTokenPreXmppAuth::GaiaTokenPreXmppAuth(
+ const std::string& username,
+ const std::string& token,
+ const std::string& token_service)
+ : username_(username),
+ token_(token),
+ token_service_(token_service) { }
+
+GaiaTokenPreXmppAuth::~GaiaTokenPreXmppAuth() { }
+
+void GaiaTokenPreXmppAuth::StartPreXmppAuth(
+ const buzz::Jid& jid,
+ const talk_base::SocketAddress& server,
+ const talk_base::CryptString& pass,
+ const std::string& auth_cookie) {
+ SignalAuthDone();
+}
+
+bool GaiaTokenPreXmppAuth::IsAuthDone() const {
+ return true;
+}
+
+bool GaiaTokenPreXmppAuth::IsAuthorized() const {
+ return true;
+}
+
+bool GaiaTokenPreXmppAuth::HadError() const {
+ return false;
+}
+
+int GaiaTokenPreXmppAuth::GetError() const {
+ return 0;
+}
+
+buzz::CaptchaChallenge GaiaTokenPreXmppAuth::GetCaptchaChallenge() const {
+ return buzz::CaptchaChallenge();
+}
+
+std::string GaiaTokenPreXmppAuth::GetAuthCookie() const {
+ return std::string();
+}
+
+std::string GaiaTokenPreXmppAuth::ChooseBestSaslMechanism(
+ const std::vector<std::string> & mechanisms, bool encrypted) {
+ return (std::find(mechanisms.begin(),
+ mechanisms.end(), kGaiaAuthMechanism) !=
+ mechanisms.end()) ? kGaiaAuthMechanism : "";
+}
+
+buzz::SaslMechanism* GaiaTokenPreXmppAuth::CreateSaslMechanism(
+ const std::string& mechanism) {
+ if (mechanism != kGaiaAuthMechanism)
+ return NULL;
+ return new buzz::SaslCookieMechanism(
+ kGaiaAuthMechanism, username_, token_, token_service_);
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h b/jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h
new file mode 100644
index 0000000..a18c77a
--- /dev/null
+++ b/jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef JINGLE_NOTIFIER_COMMUNICATOR_GAIA_TOKEN_PRE_XMPP_AUTH_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_GAIA_TOKEN_PRE_XMPP_AUTH_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/prexmppauth.h"
+
+namespace notifier {
+
+// This class implements buzz::PreXmppAuth interface for token-based
+// authentication in GTalk. It looks for the X-GOOGLE-TOKEN auth mechanism
+// and uses that instead of the default auth mechanism (PLAIN).
+class GaiaTokenPreXmppAuth : public buzz::PreXmppAuth {
+ public:
+ GaiaTokenPreXmppAuth(const std::string& username, const std::string& token,
+ const std::string& token_service);
+
+ virtual ~GaiaTokenPreXmppAuth();
+
+ // buzz::PreXmppAuth (-buzz::SaslHandler) implementation. We stub
+ // all the methods out as we don't actually do any authentication at
+ // this point.
+ virtual void StartPreXmppAuth(const buzz::Jid& jid,
+ const talk_base::SocketAddress& server,
+ const talk_base::CryptString& pass,
+ const std::string& auth_cookie);
+
+ virtual bool IsAuthDone() const;
+
+ virtual bool IsAuthorized() const;
+
+ virtual bool HadError() const;
+
+ virtual int GetError() const;
+
+ virtual buzz::CaptchaChallenge GetCaptchaChallenge() const;
+
+ virtual std::string GetAuthCookie() const;
+
+ // buzz::SaslHandler implementation.
+
+ virtual std::string ChooseBestSaslMechanism(
+ const std::vector<std::string>& mechanisms, bool encrypted);
+
+ virtual buzz::SaslMechanism* CreateSaslMechanism(
+ const std::string& mechanism);
+
+ private:
+ std::string username_;
+ std::string token_;
+ std::string token_service_;
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_COMMUNICATOR_GAIA_TOKEN_PRE_XMPP_AUTH_H_
diff --git a/jingle/notifier/communicator/login.cc b/jingle/notifier/communicator/login.cc
new file mode 100644
index 0000000..74af528
--- /dev/null
+++ b/jingle/notifier/communicator/login.cc
@@ -0,0 +1,344 @@
+// 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 <string>
+
+#include "jingle/notifier/communicator/login.h"
+
+#include "base/logging.h"
+#include "base/time.h"
+#include "jingle/notifier/communicator/auto_reconnect.h"
+#include "jingle/notifier/communicator/connection_options.h"
+#include "jingle/notifier/communicator/login_settings.h"
+#include "jingle/notifier/communicator/product_info.h"
+#include "jingle/notifier/communicator/single_login_attempt.h"
+#include "net/base/host_port_pair.h"
+#include "talk/base/common.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/taskrunner.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/prexmppauth.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+// Redirect valid for 5 minutes.
+static const int kRedirectTimeoutMinutes = 5;
+
+// Disconnect if network stays down for more than 10 seconds.
+static const int kDisconnectionDelaySecs = 10;
+
+Login::Login(talk_base::TaskParent* parent,
+ const buzz::XmppClientSettings& user_settings,
+ const ConnectionOptions& options,
+ std::string lang,
+ net::HostResolver* host_resolver,
+ ServerInformation* server_list,
+ int server_count,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only,
+ bool previous_login_successful)
+ : parent_(parent),
+ login_settings_(new LoginSettings(user_settings,
+ options,
+ lang,
+ host_resolver,
+ server_list,
+ server_count,
+ firewall,
+ proxy_only)),
+ single_attempt_(NULL),
+ successful_connection_(previous_login_successful),
+ state_(STATE_OPENING),
+ redirect_port_(0),
+ unexpected_disconnect_occurred_(false),
+ google_host_(user_settings.host()),
+ google_user_(user_settings.user()) {
+ // Hook up all the signals and observers.
+ net::NetworkChangeNotifier::AddObserver(this);
+ auto_reconnect_.SignalStartConnection.connect(this,
+ &Login::StartConnection);
+ auto_reconnect_.SignalTimerStartStop.connect(
+ this,
+ &Login::OnAutoReconnectTimerChange);
+ SignalClientStateChange.connect(&auto_reconnect_,
+ &AutoReconnect::OnClientStateChange);
+ SignalIdleChange.connect(&auto_reconnect_,
+ &AutoReconnect::set_idle);
+ SignalPowerSuspended.connect(&auto_reconnect_,
+ &AutoReconnect::OnPowerSuspend);
+
+ // Then check the initial state of the connection.
+ CheckConnection();
+}
+
+// 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;
+ }
+ net::NetworkChangeNotifier::RemoveObserver(this);
+}
+
+void Login::StartConnection() {
+ // If there is a server redirect, use it.
+ if (base::Time::Now() <
+ (redirect_time_ +
+ base::TimeDelta::FromMinutes(kRedirectTimeoutMinutes))) {
+ // Override server/port with redirect values.
+ DCHECK_NE(redirect_port_, 0);
+ net::HostPortPair server_override(redirect_server_, 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) {
+ LoginConnectionState 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:
+ DCHECK(false);
+ break;
+ }
+ HandleClientStateChange(new_state);
+}
+
+void Login::HandleClientStateChange(LoginConnectionState new_state) {
+ // Do we need to transition between the retrying and closed states?
+ if (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;
+ reset_unexpected_timer_.Stop();
+
+ 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_.Start(
+ base::TimeDelta::FromSeconds(kResetReconnectInfoDelaySec),
+ 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();
+}
+
+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) {
+ DCHECK_NE(redirect_port_, 0);
+
+ redirect_time_ = base::Time::Now();
+ redirect_server_ = redirect_server;
+ redirect_port_ = redirect_port;
+
+ // Drop the current connection, and start the login process again.
+ StartConnection();
+}
+
+void Login::OnUnexpectedDisconnect() {
+ reset_unexpected_timer_.Stop();
+
+ // 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() {
+ 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::OnIPAddressChanged() {
+ LOG(INFO) << "IP address change detected";
+ CheckConnection();
+}
+
+void Login::CheckConnection() {
+ LOG(INFO) << "Checking connection";
+ talk_base::PhysicalSocketServer physical;
+ scoped_ptr<talk_base::Socket> socket(physical.CreateSocket(SOCK_STREAM));
+ bool alive =
+ !socket->Connect(talk_base::SocketAddress("talk.google.com", 5222));
+ LOG(INFO) << "Network is " << (alive ? "alive" : "not alive");
+ if (alive) {
+ // Our connection is up. If we have a disconnect timer going,
+ // stop it so we don't disconnect.
+ disconnect_timer_.Stop();
+ } else {
+ // Our network connection is down. Start the disconnect timer if
+ // it's not already going. Don't disconnect immediately to avoid
+ // constant connection/disconnection due to flaky network
+ // interfaces.
+ if (!disconnect_timer_.IsRunning()) {
+ disconnect_timer_.Start(
+ base::TimeDelta::FromSeconds(kDisconnectionDelaySecs),
+ this, &Login::OnDisconnectTimeout);
+ }
+ }
+ auto_reconnect_.NetworkStateChanged(alive);
+}
+
+void Login::OnDisconnectTimeout() {
+ if (state_ != STATE_OPENED) {
+ return;
+ }
+
+ if (single_attempt_) {
+ single_attempt_->Abort();
+ single_attempt_ = NULL;
+ }
+
+ StartConnection();
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/communicator/login.h b/jingle/notifier/communicator/login.h
new file mode 100644
index 0000000..bd41cb5
--- /dev/null
+++ b/jingle/notifier/communicator/login.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_H_
+
+#include <string>
+
+#include "base/time.h"
+#include "base/timer.h"
+#include "jingle/notifier/base/sigslotrepeater.h"
+#include "jingle/notifier/communicator/auto_reconnect.h"
+#include "jingle/notifier/communicator/login_connection_state.h"
+#include "net/base/network_change_notifier.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 net {
+class HostResolver;
+} // namespace net
+
+namespace talk_base {
+class FirewallManager;
+struct ProxyInfo;
+class TaskParent;
+} // namespace talk_base
+
+namespace notifier {
+
+class ConnectionOptions;
+class LoginFailure;
+class LoginSettings;
+struct ServerInformation;
+class SingleLoginAttempt;
+
+// 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 net::NetworkChangeNotifier::Observer,
+ public sigslot::has_slots<> {
+ public:
+ // network_status and firewall may be NULL.
+ Login(talk_base::TaskParent* parent,
+ const buzz::XmppClientSettings& user_settings,
+ const ConnectionOptions& options,
+ std::string lang,
+ net::HostResolver* host_resolver,
+ ServerInformation* server_list,
+ int server_count,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only,
+ bool previous_login_successful);
+ virtual ~Login();
+
+ LoginConnectionState 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;
+
+ virtual void OnIPAddressChanged();
+
+ sigslot::signal1<LoginConnectionState> 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 CheckConnection();
+
+ 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(LoginConnectionState new_state);
+ void ResetUnexpectedDisconnect();
+
+ void OnDisconnectTimeout();
+
+ talk_base::TaskParent* parent_;
+ scoped_ptr<LoginSettings> login_settings_;
+ AutoReconnect auto_reconnect_;
+ SingleLoginAttempt* single_attempt_;
+ bool successful_connection_;
+
+ LoginConnectionState state_;
+
+ // server redirect information
+ base::Time redirect_time_;
+ std::string redirect_server_;
+ int redirect_port_;
+ bool unexpected_disconnect_occurred_;
+ base::OneShotTimer<Login> reset_unexpected_timer_;
+ std::string google_host_;
+ std::string google_user_;
+ talk_base::ProxyInfo proxy_info_;
+
+ base::OneShotTimer<Login> disconnect_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(Login);
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_H_
diff --git a/jingle/notifier/communicator/login_connection_state.h b/jingle/notifier/communicator/login_connection_state.h
new file mode 100644
index 0000000..db90451
--- /dev/null
+++ b/jingle/notifier/communicator/login_connection_state.h
@@ -0,0 +1,24 @@
+// 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.
+//
+// LoginConnectionState is an enum representing the state of the XMPP
+// connection.
+
+#ifndef JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_CONNECTION_STATE_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_CONNECTION_STATE_H_
+
+namespace notifier {
+
+enum LoginConnectionState {
+ 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,
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_CONNECTION_STATE_H_
diff --git a/jingle/notifier/communicator/login_failure.cc b/jingle/notifier/communicator/login_failure.cc
new file mode 100644
index 0000000..85b0fcf
--- /dev/null
+++ b/jingle/notifier/communicator/login_failure.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 "jingle/notifier/communicator/login_failure.h"
+
+#include "base/logging.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 {
+ DCHECK_EQ(error_, XMPP_ERROR);
+ return xmpp_error_;
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/communicator/login_failure.h b/jingle/notifier/communicator/login_failure.h
new file mode 100644
index 0000000..af4b66c
--- /dev/null
+++ b/jingle/notifier/communicator/login_failure.h
@@ -0,0 +1,50 @@
+// 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 JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_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:
+ const LoginError error_;
+ const buzz::XmppEngine::Error xmpp_error_;
+ const int subcode_;
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_
diff --git a/jingle/notifier/communicator/login_settings.cc b/jingle/notifier/communicator/login_settings.cc
new file mode 100644
index 0000000..43298eb
--- /dev/null
+++ b/jingle/notifier/communicator/login_settings.cc
@@ -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.
+
+#include <string>
+
+#include "jingle/notifier/communicator/login_settings.h"
+
+#include "base/logging.h"
+#include "jingle/notifier/communicator/connection_options.h"
+#include "jingle/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,
+ const std::string& lang,
+ net::HostResolver* host_resolver,
+ ServerInformation* server_list,
+ int server_count,
+ talk_base::FirewallManager* firewall,
+ bool proxy_only)
+ : proxy_only_(proxy_only),
+ firewall_(firewall),
+ lang_(lang),
+ host_resolver_(host_resolver),
+ 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.
+ DCHECK(server_list);
+ DCHECK(host_resolver);
+ DCHECK_GT(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 net::HostPortPair& 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/jingle/notifier/communicator/login_settings.h b/jingle/notifier/communicator/login_settings.h
new file mode 100644
index 0000000..32ea038
--- /dev/null
+++ b/jingle/notifier/communicator/login_settings.h
@@ -0,0 +1,102 @@
+// 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 JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_
+#include <string>
+
+#include "jingle/notifier/communicator/xmpp_connection_generator.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace buzz {
+class XmppClientSettings;
+}
+
+namespace net {
+struct HostPortPair;
+class HostResolver;
+}
+
+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,
+ const std::string& lang,
+ net::HostResolver* host_resolver,
+ 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_;
+ }
+
+ net::HostResolver* host_resolver() {
+ return host_resolver_;
+ }
+
+ 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 net::HostPortPair& server);
+ void clear_server_override();
+
+ private:
+ bool proxy_only_;
+ talk_base::FirewallManager* firewall_;
+ std::string lang_;
+
+ net::HostResolver* host_resolver_;
+ 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 // JINGLE_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_
diff --git a/jingle/notifier/communicator/product_info.cc b/jingle/notifier/communicator/product_info.cc
new file mode 100644
index 0000000..c1deafb
--- /dev/null
+++ b/jingle/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/jingle/notifier/communicator/product_info.h b/jingle/notifier/communicator/product_info.h
new file mode 100644
index 0000000..9e8e5d0
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_
+
+#include <string>
+
+namespace notifier {
+std::string GetUserAgentString();
+std::string GetProductSignature();
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_
diff --git a/jingle/notifier/communicator/single_login_attempt.cc b/jingle/notifier/communicator/single_login_attempt.cc
new file mode 100644
index 0000000..00163c09
--- /dev/null
+++ b/jingle/notifier/communicator/single_login_attempt.cc
@@ -0,0 +1,517 @@
+// 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 "jingle/notifier/communicator/single_login_attempt.h"
+
+#include "base/logging.h"
+#include "jingle/notifier/communicator/connection_options.h"
+#include "jingle/notifier/communicator/connection_settings.h"
+#include "jingle/notifier/communicator/const_communicator.h"
+#include "jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h"
+#include "jingle/notifier/communicator/login_failure.h"
+#include "jingle/notifier/communicator/login_settings.h"
+#include "jingle/notifier/communicator/product_info.h"
+#include "jingle/notifier/communicator/xmpp_connection_generator.h"
+#include "jingle/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/win32socketinit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/constants.h"
+
+namespace notifier {
+
+static void GetClientErrorInformation(
+ buzz::XmppClient* client,
+ buzz::XmppEngine::Error* error,
+ int* subcode,
+ buzz::XmlElement** stream_error) {
+ DCHECK(client);
+ DCHECK(error);
+ DCHECK(subcode);
+ DCHECK(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);
+ }
+ }
+}
+
+SingleLoginAttempt::SingleLoginAttempt(talk_base::TaskParent* 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_->host_resolver(),
+ &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.
+ DCHECK(!client_);
+}
+
+bool SingleLoginAttempt::auto_reconnect() const {
+ return login_settings_->connection_options().auto_reconnect();
+}
+
+const talk_base::ProxyInfo& SingleLoginAttempt::proxy() const {
+ DCHECK(connection_generator_.get());
+ return connection_generator_->proxy();
+}
+
+int SingleLoginAttempt::ProcessStart() {
+ DCHECK_EQ(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() {
+ DCHECK(connection_generator_.get());
+ ClearClient();
+ connection_generator_->UseNextConnection();
+}
+
+void SingleLoginAttempt::UseCurrentConnection() {
+ DCHECK(connection_generator_.get());
+ 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),
+ CreatePreXmppAuth(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::PreXmppAuth* SingleLoginAttempt::CreatePreXmppAuth(
+ const buzz::XmppClientSettings& xcs) {
+ buzz::Jid jid(xcs.user(), xcs.host(), buzz::STR_EMPTY);
+ return new GaiaTokenPreXmppAuth(
+ 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;
+ DCHECK(connection_generator_.get());
+ 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) {
+ DCHECK(thread);
+
+ 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
+ DCHECK(!client_);
+ }
+}
+
+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;
+ }
+ }
+ }
+
+ DCHECK(connection_generator_.get());
+ if (!connection_generator_.get()) {
+ return;
+ }
+
+ // Iterate to the next possible connection (still trying to connect).
+ UseNextConnection();
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/communicator/single_login_attempt.h b/jingle/notifier/communicator/single_login_attempt.h
new file mode 100644
index 0000000..3595c0fb
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_
+
+#include <string>
+
+#include "jingle/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 PreXmppAuth;
+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::TaskParent* 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::PreXmppAuth* CreatePreXmppAuth(
+ 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 // JINGLE_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_
diff --git a/jingle/notifier/communicator/ssl_socket_adapter.cc b/jingle/notifier/communicator/ssl_socket_adapter.cc
new file mode 100644
index 0000000..a753710
--- /dev/null
+++ b/jingle/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 "jingle/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_, 0));
+ 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/jingle/notifier/communicator/ssl_socket_adapter.h b/jingle/notifier/communicator/ssl_socket_adapter.h
new file mode 100644
index 0000000..a97cfaa
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_
diff --git a/jingle/notifier/communicator/xmpp_connection_generator.cc b/jingle/notifier/communicator/xmpp_connection_generator.cc
new file mode 100644
index 0000000..abadd35
--- /dev/null
+++ b/jingle/notifier/communicator/xmpp_connection_generator.cc
@@ -0,0 +1,214 @@
+// 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 "jingle/notifier/communicator/xmpp_connection_generator.h"
+
+#if defined(OS_WIN)
+#include <winsock2.h>
+#elif defined(OS_POSIX)
+#include <arpa/inet.h>
+#endif
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "jingle/notifier/base/signal_thread_task.h"
+#include "jingle/notifier/communicator/connection_options.h"
+#include "jingle/notifier/communicator/connection_settings.h"
+#include "jingle/notifier/communicator/product_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/sys_addrinfo.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 scoped_refptr<net::HostResolver>& host_resolver,
+ const ConnectionOptions* options,
+ bool proxy_only,
+ const ServerInformation* server_list,
+ int server_count)
+ : host_resolver_(host_resolver),
+ resolve_callback_(
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ NewCallback(this,
+ &XmppConnectionGenerator::OnServerDNSResolved))),
+ 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) {
+ DCHECK(host_resolver);
+ DCHECK(parent);
+ DCHECK(options);
+ DCHECK_GT(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 {
+ DCHECK(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";
+
+ // TODO(akalin): Detect proxy settings once we use Chrome sockets.
+
+ // Start iterating through the connections (which are generated on demand).
+ UseNextConnection();
+}
+
+void XmppConnectionGenerator::UseNextConnection() {
+ DCHECK(settings_list_.get());
+ // Loop until we can use a connection or we run out of connections
+ // to try.
+ while (true) {
+ // 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_) {
+ // All out of possibilities.
+ HandleExhaustedConnections();
+ return;
+ }
+
+ // Resolve the server.
+ const net::HostPortPair& server =
+ server_list_[server_index_].server;
+ net::HostResolver::RequestInfo request_info(
+ server.host, server.port);
+ int status =
+ host_resolver_.Resolve(
+ request_info, &address_list_, resolve_callback_.get(),
+ bound_net_log_);
+ if (status == net::ERR_IO_PENDING) {
+ // resolve_callback_ will call us when it's called.
+ return;
+ }
+ HandleServerDNSResolved(status);
+ }
+}
+
+void XmppConnectionGenerator::OnServerDNSResolved(int status) {
+ DCHECK_NE(status, net::ERR_IO_PENDING);
+ HandleServerDNSResolved(status);
+ // Reenter loop.
+ UseNextConnection();
+}
+
+void XmppConnectionGenerator::HandleServerDNSResolved(int status) {
+ DCHECK_NE(status, net::ERR_IO_PENDING);
+ LOG(INFO) << "XmppConnectionGenerator::HandleServerDNSResolved";
+ // Print logging info.
+ LOG(INFO) << " server: "
+ << server_list_[server_index_].server.ToString()
+ << ", error: " << status;
+ if (status != net::OK) {
+ if (first_dns_error_ == 0) {
+ first_dns_error_ = status;
+ }
+ return;
+ }
+
+ // Slurp the addresses into a vector.
+ std::vector<uint32> ip_list;
+ for (const addrinfo* addr = address_list_.head();
+ addr != NULL; addr = addr->ai_next) {
+ const sockaddr_in& sockaddr =
+ *reinterpret_cast<const sockaddr_in*>(addr->ai_addr);
+ uint32 ip = ntohl(sockaddr.sin_addr.s_addr);
+ ip_list.push_back(ip);
+ }
+ successfully_resolved_dns_ = !ip_list.empty();
+
+ for (int i = 0; i < static_cast<int>(ip_list.size()); ++i) {
+ LOG(INFO)
+ << " ip " << i << " : "
+ << talk_base::SocketAddress::IPToString(ip_list[i]);
+ }
+
+ // Build the ip list.
+ DCHECK(settings_list_.get());
+ settings_index_ = -1;
+ settings_list_->ClearPermutations();
+ settings_list_->AddPermutations(
+ server_list_[server_index_].server.host,
+ ip_list,
+ server_list_[server_index_].server.port,
+ server_list_[server_index_].special_port_magic,
+ proxy_only_);
+}
+
+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/jingle/notifier/communicator/xmpp_connection_generator.h b/jingle/notifier/communicator/xmpp_connection_generator.h
new file mode 100644
index 0000000..43b5329
--- /dev/null
+++ b/jingle/notifier/communicator/xmpp_connection_generator.h
@@ -0,0 +1,94 @@
+// 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 JINGLE_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_
+#define JINGLE_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_
+
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_log.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+struct ProxyInfo;
+class SignalThread;
+class Task;
+}
+
+namespace notifier {
+
+class ConnectionOptions;
+class ConnectionSettings;
+class ConnectionSettingsList;
+
+struct ServerInformation {
+ net::HostPortPair 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 scoped_refptr<net::HostResolver>& host_resolver,
+ 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 OnServerDNSResolved(int status);
+ void HandleServerDNSResolved(int status);
+ void HandleExhaustedConnections();
+
+ net::SingleRequestHostResolver host_resolver_;
+ scoped_ptr<net::CompletionCallback> resolve_callback_;
+ net::AddressList address_list_;
+ net::BoundNetLog bound_net_log_;
+ 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 // JINGLE_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_
diff --git a/jingle/notifier/communicator/xmpp_socket_adapter.cc b/jingle/notifier/communicator/xmpp_socket_adapter.cc
new file mode 100644
index 0000000..7d7a2e9
--- /dev/null
+++ b/jingle/notifier/communicator/xmpp_socket_adapter.cc
@@ -0,0 +1,429 @@
+// 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 "jingle/notifier/communicator/xmpp_socket_adapter.h"
+
+#include <iomanip>
+#include <string>
+
+#include "base/logging.h"
+#include "jingle/notifier/base/ssl_adapter.h"
+#include "jingle/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/ssladapter.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());
+ 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;
+ }
+
+ DCHECK(socket_);
+
+ 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;
+ }
+
+ DCHECK(socket_);
+
+ 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(DFATAL) << "unexpected XmppSocketAdapter::OnConnectEvent state: "
+ << state_;
+ }
+}
+
+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;
+
+ DCHECK_EQ(write_buffer_length_, 0U);
+
+ 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];
+ DCHECK_LE(write_buffer_length_, 64000U);
+ 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) {
+ DCHECK(error);
+ DCHECK(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/jingle/notifier/communicator/xmpp_socket_adapter.h b/jingle/notifier/communicator/xmpp_socket_adapter.h
new file mode 100644
index 0000000..ff22a9c
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_
diff --git a/jingle/notifier/listener/listen_task.cc b/jingle/notifier/listener/listen_task.cc
new file mode 100644
index 0000000..bfa79c1
--- /dev/null
+++ b/jingle/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 "jingle/notifier/listener/listen_task.h"
+
+#include "base/logging.h"
+#include "jingle/notifier/listener/notification_constants.h"
+#include "jingle/notifier/listener/xml_element_util.h"
+#include "talk/base/task.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+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("google:notifier", "getAll"));
+ if (get_all_element) {
+ const buzz::XmlElement* result_element =
+ get_all_element->FirstNamed(
+ buzz::QName(buzz::STR_EMPTY, "Result"));
+ while (result_element) {
+ IncomingNotificationData notification_data;
+ const buzz::XmlElement* id_element =
+ result_element->FirstNamed(buzz::QName(buzz::STR_EMPTY, "Id"));
+ if (id_element) {
+ const buzz::XmlElement* service_url_element =
+ id_element->FirstNamed(
+ buzz::QName(buzz::STR_EMPTY, "ServiceUrl"));
+ if (service_url_element) {
+ notification_data.service_url = service_url_element->Attr(
+ buzz::QName(buzz::STR_EMPTY, "data"));
+ }
+ }
+ const buzz::XmlElement* content_element =
+ result_element->FirstNamed(
+ buzz::QName(buzz::STR_EMPTY, "Content"));
+ if (content_element) {
+ const buzz::XmlElement* service_data_element =
+ content_element->FirstNamed(
+ buzz::QName(buzz::STR_EMPTY, "ServiceSpecificData"));
+ if (service_data_element) {
+ notification_data.service_specific_data = service_data_element->Attr(
+ buzz::QName(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(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(
+ 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/jingle/notifier/listener/listen_task.h b/jingle/notifier/listener/listen_task.h
new file mode 100644
index 0000000..6a86775
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_LISTENER_LISTEN_TASK_H_
+#define JINGLE_NOTIFIER_LISTENER_LISTEN_TASK_H_
+
+#include "jingle/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 // JINGLE_NOTIFIER_LISTENER_LISTEN_TASK_H_
diff --git a/jingle/notifier/listener/mediator_thread.h b/jingle/notifier/listener/mediator_thread.h
new file mode 100644
index 0000000..643b1e5
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
+#define JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
+
+#include <string>
+#include <vector>
+
+#include "jingle/notifier/listener/notification_defines.h"
+
+namespace buzz {
+class XmppClientSettings;
+} // namespace buzz
+
+namespace notifier {
+
+class MediatorThread {
+ public:
+ virtual ~MediatorThread() {}
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual void OnConnectionStateChange(bool logged_in) = 0;
+
+ virtual void OnSubscriptionStateChange(bool subscribed) = 0;
+
+ virtual void OnIncomingNotification(
+ const IncomingNotificationData& notification_data) = 0;
+
+ virtual void OnOutgoingNotification() = 0;
+ };
+
+ // |delegate| can be NULL if we're shutting down.
+ // TODO(akalin): Handle messages during shutdown gracefully so that
+ // we don't have to deal with NULL delegates.
+ virtual void SetDelegate(Delegate* delegate) = 0;
+ 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;
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
diff --git a/jingle/notifier/listener/mediator_thread_impl.cc b/jingle/notifier/listener/mediator_thread_impl.cc
new file mode 100644
index 0000000..9f740dd
--- /dev/null
+++ b/jingle/notifier/listener/mediator_thread_impl.cc
@@ -0,0 +1,368 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "jingle/notifier/listener/mediator_thread_impl.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "jingle/notifier/base/task_pump.h"
+#include "jingle/notifier/communicator/connection_options.h"
+#include "jingle/notifier/communicator/const_communicator.h"
+#include "jingle/notifier/communicator/xmpp_connection_generator.h"
+#include "jingle/notifier/listener/listen_task.h"
+#include "jingle/notifier/listener/send_update_task.h"
+#include "jingle/notifier/listener/subscribe_task.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/host_resolver.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+// We manage the lifetime of notifier::MediatorThreadImpl ourselves.
+DISABLE_RUNNABLE_METHOD_REFCOUNT(notifier::MediatorThreadImpl);
+
+namespace notifier {
+
+MediatorThreadImpl::MediatorThreadImpl()
+ : delegate_(NULL),
+ parent_message_loop_(MessageLoop::current()),
+ worker_thread_("MediatorThread worker thread") {
+ DCHECK(parent_message_loop_);
+}
+
+MediatorThreadImpl::~MediatorThreadImpl() {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+}
+
+void MediatorThreadImpl::SetDelegate(Delegate* delegate) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ delegate_ = delegate;
+}
+
+void MediatorThreadImpl::Start() {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ // We create the worker thread as an IO thread in preparation for
+ // making this use Chrome sockets.
+ const base::Thread::Options options(MessageLoop::TYPE_IO, 0);
+ // TODO(akalin): Make this function return a bool and remove this
+ // CHECK().
+ CHECK(worker_thread_.StartWithOptions(options));
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::StartLibjingleThread));
+}
+
+void MediatorThreadImpl::StartLibjingleThread() {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ socket_server_.reset(new talk_base::PhysicalSocketServer());
+ libjingle_thread_.reset(new talk_base::Thread());
+ talk_base::ThreadManager::SetCurrent(libjingle_thread_.get());
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::PumpLibjingleLoop));
+}
+
+void MediatorThreadImpl::StopLibjingleThread() {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ talk_base::ThreadManager::SetCurrent(NULL);
+ libjingle_thread_.reset();
+ socket_server_.reset();
+}
+
+void MediatorThreadImpl::PumpLibjingleLoop() {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ // Pump the libjingle message loop 100ms at a time.
+ if (!libjingle_thread_.get()) {
+ // StopLibjingleThread() was called.
+ return;
+ }
+ libjingle_thread_->ProcessMessages(100);
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::PumpLibjingleLoop));
+}
+
+void MediatorThreadImpl::Login(const buzz::XmppClientSettings& settings) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::DoLogin, settings));
+}
+
+void MediatorThreadImpl::Logout() {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::DoDisconnect));
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::StopLibjingleThread));
+ // TODO(akalin): Decomp this into a separate stop method.
+ worker_thread_.Stop();
+ // Process any messages the worker thread may be posted on our
+ // thread.
+ bool old_state = parent_message_loop_->NestableTasksAllowed();
+ parent_message_loop_->SetNestableTasksAllowed(true);
+ parent_message_loop_->RunAllPending();
+ parent_message_loop_->SetNestableTasksAllowed(old_state);
+ // worker_thread_ should have cleaned all this up.
+ CHECK(!login_.get());
+ CHECK(!pump_.get());
+}
+
+void MediatorThreadImpl::ListenForUpdates() {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::DoListenForUpdates));
+}
+
+void MediatorThreadImpl::SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::DoSubscribeForUpdates,
+ subscribed_services_list));
+}
+
+void MediatorThreadImpl::SendNotification(
+ const OutgoingNotificationData& data) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ worker_message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediatorThreadImpl::DoSendNotification,
+ data));
+}
+
+MessageLoop* MediatorThreadImpl::worker_message_loop() {
+ MessageLoop* current_message_loop = MessageLoop::current();
+ DCHECK(current_message_loop);
+ MessageLoop* worker_message_loop = worker_thread_.message_loop();
+ DCHECK(worker_message_loop);
+ DCHECK(current_message_loop == parent_message_loop_ ||
+ current_message_loop == worker_message_loop);
+ return worker_message_loop;
+}
+
+buzz::XmppClient* MediatorThreadImpl::xmpp_client() {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ DCHECK(login_.get());
+ buzz::XmppClient* xmpp_client = login_->xmpp_client();
+ DCHECK(xmpp_client);
+ return xmpp_client;
+}
+
+void MediatorThreadImpl::DoLogin(
+ const buzz::XmppClientSettings& settings) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ LOG(INFO) << "P2P: Thread logging into talk network.";
+
+ // TODO(akalin): Use an existing HostResolver from somewhere (maybe
+ // the IOThread one).
+ host_resolver_ = net::CreateSystemHostResolver();
+
+ // 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 = net::HostPortPair("talk.google.com",
+ notifier::kDefaultXmppPort);
+ server_list[0].special_port_magic = true;
+ server_list[1].server = net::HostPortPair("talkx.l.google.com",
+ notifier::kDefaultXmppPort);
+ 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(),
+ settings,
+ options,
+ lang,
+ host_resolver_.get(),
+ server_list,
+ server_list_count,
+ // 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::DoDisconnect() {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ 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();
+
+ host_resolver_ = NULL;
+}
+
+void MediatorThreadImpl::DoSubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ SubscribeTask* subscription =
+ new SubscribeTask(xmpp_client(), subscribed_services_list);
+ subscription->SignalStatusUpdate.connect(
+ this,
+ &MediatorThreadImpl::OnSubscriptionStateChange);
+ subscription->Start();
+}
+
+void MediatorThreadImpl::DoListenForUpdates() {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ ListenTask* listener = new ListenTask(xmpp_client());
+ listener->SignalUpdateAvailable.connect(
+ this,
+ &MediatorThreadImpl::OnIncomingNotification);
+ listener->Start();
+}
+
+void MediatorThreadImpl::DoSendNotification(
+ const OutgoingNotificationData& data) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ SendUpdateTask* task = new SendUpdateTask(xmpp_client(), data);
+ task->SignalStatusUpdate.connect(
+ this,
+ &MediatorThreadImpl::OnOutgoingNotification);
+ task->Start();
+}
+
+void MediatorThreadImpl::OnIncomingNotification(
+ const IncomingNotificationData& notification_data) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ parent_message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &MediatorThreadImpl::OnIncomingNotificationOnParentThread,
+ notification_data));
+}
+
+void MediatorThreadImpl::OnIncomingNotificationOnParentThread(
+ const IncomingNotificationData& notification_data) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ if (delegate_) {
+ delegate_->OnIncomingNotification(notification_data);
+ }
+}
+
+void MediatorThreadImpl::OnOutgoingNotification(bool success) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ parent_message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &MediatorThreadImpl::OnOutgoingNotificationOnParentThread,
+ success));
+}
+
+void MediatorThreadImpl::OnOutgoingNotificationOnParentThread(
+ bool success) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ if (delegate_ && success) {
+ delegate_->OnOutgoingNotification();
+ }
+}
+
+void MediatorThreadImpl::OnLoginFailureMessage(
+ const notifier::LoginFailure& failure) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ parent_message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &MediatorThreadImpl::OnLoginFailureMessageOnParentThread,
+ failure));
+}
+
+void MediatorThreadImpl::OnLoginFailureMessageOnParentThread(
+ const notifier::LoginFailure& failure) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ if (delegate_) {
+ delegate_->OnConnectionStateChange(false);
+ }
+}
+
+void MediatorThreadImpl::OnClientStateChangeMessage(
+ LoginConnectionState state) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ parent_message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &MediatorThreadImpl::OnClientStateChangeMessageOnParentThread,
+ state));
+}
+
+void MediatorThreadImpl::OnClientStateChangeMessageOnParentThread(
+ LoginConnectionState state) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ switch (state) {
+ case STATE_CLOSED:
+ if (delegate_) {
+ delegate_->OnConnectionStateChange(false);
+ }
+ break;
+ case STATE_RETRYING:
+ case 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.
+ if (delegate_) {
+ delegate_->OnSubscriptionStateChange(false);
+ }
+ break;
+ case STATE_OPENED:
+ if (delegate_) {
+ delegate_->OnConnectionStateChange(true);
+ }
+ break;
+ default:
+ LOG(WARNING) << "P2P: Unknown client state change.";
+ break;
+ }
+}
+
+void MediatorThreadImpl::OnSubscriptionStateChange(bool success) {
+ DCHECK_EQ(MessageLoop::current(), worker_message_loop());
+ parent_message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &MediatorThreadImpl::OnSubscriptionStateChangeOnParentThread,
+ success));
+}
+
+void MediatorThreadImpl::OnSubscriptionStateChangeOnParentThread(
+ bool success) {
+ DCHECK_EQ(MessageLoop::current(), parent_message_loop_);
+ if (delegate_) {
+ delegate_->OnSubscriptionStateChange(success);
+ }
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/listener/mediator_thread_impl.h b/jingle/notifier/listener/mediator_thread_impl.h
new file mode 100644
index 0000000..0e2e22f
--- /dev/null
+++ b/jingle/notifier/listener/mediator_thread_impl.h
@@ -0,0 +1,142 @@
+// 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.
+//
+// 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 JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
+#define JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/thread.h"
+#include "jingle/notifier/communicator/login.h"
+#include "jingle/notifier/communicator/login_connection_state.h"
+#include "jingle/notifier/communicator/login_failure.h"
+#include "jingle/notifier/listener/mediator_thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+class MessageLoop;
+
+namespace buzz {
+class XmppClient;
+} // namespace buzz
+
+namespace net {
+class HostResolver;
+} // namespace net
+
+namespace notifier {
+class TaskPump;
+} // namespace notifier
+
+namespace talk_base {
+class SocketServer;
+class Thread;
+} // namespace talk_base
+
+namespace notifier {
+
+class MediatorThreadImpl
+ : public MediatorThread,
+ public sigslot::has_slots<> {
+ public:
+ MediatorThreadImpl();
+ virtual ~MediatorThreadImpl();
+
+ virtual void SetDelegate(Delegate* delegate);
+
+ // Start the thread.
+ virtual void Start();
+
+ // These are called from outside threads, by the talk mediator object.
+ // They add messages to a queue which we poll in this thread.
+ virtual void Login(const buzz::XmppClientSettings& settings);
+ virtual void Logout();
+ virtual void ListenForUpdates();
+ virtual void SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list);
+ virtual void SendNotification(const OutgoingNotificationData& data);
+
+ protected:
+ // Should only be called after Start().
+ MessageLoop* worker_message_loop();
+
+ // Should only be called after OnConnectionStateChange() is called
+ // on the delegate with true.
+ buzz::XmppClient* xmpp_client();
+
+ Delegate* delegate_;
+ MessageLoop* parent_message_loop_;
+
+ private:
+ void StartLibjingleThread();
+ void PumpLibjingleLoop();
+ void StopLibjingleThread();
+
+ // Called from within the thread on internal events.
+ void DoLogin(const buzz::XmppClientSettings& settings);
+ void DoDisconnect();
+ void DoSubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list);
+ void DoListenForUpdates();
+ void DoSendNotification(
+ const OutgoingNotificationData& data);
+
+ // These handle messages indicating an event happened in the outside
+ // world. These are all called from the worker thread.
+ void OnIncomingNotification(
+ const IncomingNotificationData& notification_data);
+ void OnOutgoingNotification(bool success);
+ void OnLoginFailureMessage(const notifier::LoginFailure& failure);
+ void OnClientStateChangeMessage(LoginConnectionState state);
+ void OnSubscriptionStateChange(bool success);
+
+ // Equivalents of the above functions called from the parent thread.
+ void OnIncomingNotificationOnParentThread(
+ const IncomingNotificationData& notification_data);
+ void OnOutgoingNotificationOnParentThread(bool success);
+ void OnLoginFailureMessageOnParentThread(
+ const notifier::LoginFailure& failure);
+ void OnClientStateChangeMessageOnParentThread(
+ LoginConnectionState state);
+ void OnSubscriptionStateChangeOnParentThread(
+ bool success);
+
+ base::Thread worker_thread_;
+ scoped_refptr<net::HostResolver> host_resolver_;
+
+ // 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_;
+
+ scoped_ptr<talk_base::SocketServer> socket_server_;
+ scoped_ptr<talk_base::Thread> libjingle_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediatorThreadImpl);
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
diff --git a/jingle/notifier/listener/mediator_thread_mock.h b/jingle/notifier/listener/mediator_thread_mock.h
new file mode 100644
index 0000000..8a1eaec
--- /dev/null
+++ b/jingle/notifier/listener/mediator_thread_mock.h
@@ -0,0 +1,97 @@
+// 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 JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
+#define JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
+
+#include <string>
+#include <vector>
+
+#include "jingle/notifier/listener/mediator_thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+
+class MockMediatorThread : public MediatorThread {
+ public:
+ MockMediatorThread() : delegate_(NULL) {
+ Reset();
+ }
+
+ virtual ~MockMediatorThread() {}
+
+ void Reset() {
+ login_calls = 0;
+ logout_calls = 0;
+ start_calls = 0;
+ subscribe_calls = 0;
+ listen_calls = 0;
+ send_calls = 0;
+ }
+
+ virtual void SetDelegate(Delegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ // Overridden from MediatorThread
+ virtual void Login(const buzz::XmppClientSettings& settings) {
+ login_calls++;
+ if (delegate_) {
+ delegate_->OnConnectionStateChange(true);
+ }
+ }
+
+ virtual void Logout() {
+ logout_calls++;
+ if (delegate_) {
+ delegate_->OnConnectionStateChange(false);
+ }
+ }
+
+ virtual void Start() {
+ start_calls++;
+ }
+
+ virtual void SubscribeForUpdates(
+ const std::vector<std::string>& subscribed_services_list) {
+ subscribe_calls++;
+ if (delegate_) {
+ delegate_->OnSubscriptionStateChange(true);
+ }
+ }
+
+ virtual void ListenForUpdates() {
+ listen_calls++;
+ }
+
+ virtual void SendNotification(const OutgoingNotificationData &) {
+ send_calls++;
+ if (delegate_) {
+ delegate_->OnOutgoingNotification();
+ }
+ }
+
+ void ReceiveNotification(const IncomingNotificationData& data) {
+ if (delegate_) {
+ delegate_->OnIncomingNotification(data);
+ }
+ }
+
+ Delegate* delegate_;
+ // Intneral State
+ int login_calls;
+ int logout_calls;
+ int start_calls;
+ int subscribe_calls;
+ int listen_calls;
+ int send_calls;
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
diff --git a/jingle/notifier/listener/notification_constants.cc b/jingle/notifier/listener/notification_constants.cc
new file mode 100644
index 0000000..11e9157
--- /dev/null
+++ b/jingle/notifier/listener/notification_constants.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "jingle/notifier/listener/notification_constants.h"
+
+namespace notifier {
+
+const char kNotifierNamespace[] = "google:notifier";
+
+} // namespace notifier
diff --git a/jingle/notifier/listener/notification_constants.h b/jingle/notifier/listener/notification_constants.h
new file mode 100644
index 0000000..04d9921
--- /dev/null
+++ b/jingle/notifier/listener/notification_constants.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef JINGLE_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_
+#define JINGLE_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_
+
+namespace notifier {
+
+extern const char kNotifierNamespace[];
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_LISTENER_NOTIFICATION_CONSTANTS_H_
diff --git a/jingle/notifier/listener/notification_defines.h b/jingle/notifier/listener/notification_defines.h
new file mode 100644
index 0000000..8a42307
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_
+#define JINGLE_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 // JINGLE_NOTIFIER_LISTENER_NOTIFICATION_DEFINES_H_
+
diff --git a/jingle/notifier/listener/send_update_task.cc b/jingle/notifier/listener/send_update_task.cc
new file mode 100644
index 0000000..e4b2884
--- /dev/null
+++ b/jingle/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 "jingle/notifier/listener/send_update_task.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "jingle/notifier/listener/notification_constants.h"
+#include "jingle/notifier/listener/xml_element_util.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/constants.h"
+
+namespace notifier {
+
+SendUpdateTask::SendUpdateTask(TaskParent* 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(kNotifierNamespace, "set");
+ static const buzz::QName kQnId(buzz::STR_EMPTY, "Id");
+ static const buzz::QName kQnContent(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/jingle/notifier/listener/send_update_task.h b/jingle/notifier/listener/send_update_task.h
new file mode 100644
index 0000000..03285dd
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
+#define JINGLE_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
+
+#include <string>
+
+#include "jingle/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(TaskParent* 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 // JINGLE_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
diff --git a/jingle/notifier/listener/send_update_task_unittest.cc b/jingle/notifier/listener/send_update_task_unittest.cc
new file mode 100644
index 0000000..80a87aa
--- /dev/null
+++ b/jingle/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 "jingle/notifier/listener/send_update_task.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "jingle/notifier/listener/xml_element_util.h"
+#include "talk/xmpp/jid.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace notifier {
+
+class 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/jingle/notifier/listener/subscribe_task.cc b/jingle/notifier/listener/subscribe_task.cc
new file mode 100644
index 0000000..7636b48
--- /dev/null
+++ b/jingle/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 "jingle/notifier/listener/subscribe_task.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "jingle/notifier/listener/notification_constants.h"
+#include "jingle/notifier/listener/xml_element_util.h"
+#include "talk/base/task.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace notifier {
+
+SubscribeTask::SubscribeTask(
+ TaskParent* 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(
+ 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/jingle/notifier/listener/subscribe_task.h b/jingle/notifier/listener/subscribe_task.h
new file mode 100644
index 0000000..5b344d9
--- /dev/null
+++ b/jingle/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 JINGLE_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
+#define JINGLE_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(TaskParent* 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 // JINGLE_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
diff --git a/jingle/notifier/listener/subscribe_task_unittest.cc b/jingle/notifier/listener/subscribe_task_unittest.cc
new file mode 100644
index 0000000..3b44fd8
--- /dev/null
+++ b/jingle/notifier/listener/subscribe_task_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "jingle/notifier/listener/subscribe_task.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "jingle/notifier/listener/xml_element_util.h"
+#include "talk/xmpp/jid.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace notifier {
+
+class 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/jingle/notifier/listener/talk_mediator.h b/jingle/notifier/listener/talk_mediator.h
new file mode 100644
index 0000000..dfdc260
--- /dev/null
+++ b/jingle/notifier/listener/talk_mediator.h
@@ -0,0 +1,63 @@
+// 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 JINGLE_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
+#define JINGLE_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
+
+#include <string>
+
+#include "jingle/notifier/listener/notification_defines.h"
+
+namespace notifier {
+
+class TalkMediator {
+ public:
+ TalkMediator() {}
+ virtual ~TalkMediator() {}
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual void OnNotificationStateChange(
+ bool notifications_enabled) = 0;
+
+ virtual void OnIncomingNotification(
+ const IncomingNotificationData& notification_data) = 0;
+
+ virtual void OnOutgoingNotification() = 0;
+ };
+
+ // |delegate| may be NULL.
+ virtual void SetDelegate(Delegate* delegate) = 0;
+
+ // 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;
+
+ // Add a URL to subscribe to for notifications.
+ virtual void AddSubscribedServiceUrl(const std::string& service_url) = 0;
+};
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
diff --git a/jingle/notifier/listener/talk_mediator_impl.cc b/jingle/notifier/listener/talk_mediator_impl.cc
new file mode 100644
index 0000000..3276778
--- /dev/null
+++ b/jingle/notifier/listener/talk_mediator_impl.cc
@@ -0,0 +1,195 @@
+// 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 "jingle/notifier/listener/talk_mediator_impl.h"
+
+#include "base/logging.h"
+#include "base/singleton.h"
+#include "jingle/notifier/listener/mediator_thread_impl.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(
+ MediatorThread* mediator_thread,
+ bool initialize_ssl,
+ bool connect_immediately,
+ bool invalidate_xmpp_auth_token)
+ : delegate_(NULL),
+ mediator_thread_(mediator_thread),
+ invalidate_xmpp_auth_token_(invalidate_xmpp_auth_token) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ if (initialize_ssl) {
+ SslInitializationSingleton::GetInstance()->RegisterClient();
+ }
+ if (connect_immediately) {
+ mediator_thread_->SetDelegate(this);
+ state_.connected = 1;
+ }
+ mediator_thread_->Start();
+ state_.started = 1;
+}
+
+TalkMediatorImpl::~TalkMediatorImpl() {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ if (state_.started) {
+ Logout();
+ }
+}
+
+bool TalkMediatorImpl::Login() {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ // Connect to the mediator thread and start processing messages.
+ if (!state_.connected) {
+ mediator_thread_->SetDelegate(this);
+ state_.connected = 1;
+ }
+ if (state_.initialized && !state_.logging_in && !state_.logged_in) {
+ state_.logging_in = true;
+ mediator_thread_->Login(xmpp_settings_);
+ return true;
+ }
+ return false;
+}
+
+bool TalkMediatorImpl::Logout() {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ if (state_.connected) {
+ state_.connected = 0;
+ }
+ if (state_.started) {
+ state_.started = 0;
+ state_.logging_in = 0;
+ state_.logged_in = 0;
+ state_.subscribed = 0;
+ // We do not want to be called back during logout since we may be
+ // closing.
+ mediator_thread_->SetDelegate(NULL);
+ mediator_thread_->Logout();
+ return true;
+ }
+ return false;
+}
+
+bool TalkMediatorImpl::SendNotification(const OutgoingNotificationData& data) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ if (state_.logged_in && state_.subscribed) {
+ mediator_thread_->SendNotification(data);
+ return true;
+ }
+ return false;
+}
+
+void TalkMediatorImpl::SetDelegate(TalkMediator::Delegate* delegate) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ delegate_ = delegate;
+}
+
+bool TalkMediatorImpl::SetAuthToken(const std::string& email,
+ const std::string& token,
+ const std::string& token_service) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+
+ // 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) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ 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::OnConnectionStateChange(bool logged_in) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ state_.logging_in = 0;
+ state_.logged_in = logged_in;
+ if (logged_in) {
+ LOG(INFO) << "P2P: Logged in.";
+ // 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_);
+ } else {
+ LOG(INFO) << "P2P: Logged off.";
+ OnSubscriptionStateChange(false);
+ }
+}
+
+void TalkMediatorImpl::OnSubscriptionStateChange(bool subscribed) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ state_.subscribed = subscribed;
+ LOG(INFO) << "P2P: " << (subscribed ? "subscribed" : "unsubscribed");
+ if (delegate_) {
+ delegate_->OnNotificationStateChange(subscribed);
+ }
+}
+
+void TalkMediatorImpl::OnIncomingNotification(
+ const IncomingNotificationData& notification_data) {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ LOG(INFO) << "P2P: Updates are available on the server.";
+ if (delegate_) {
+ delegate_->OnIncomingNotification(notification_data);
+ }
+}
+
+void TalkMediatorImpl::OnOutgoingNotification() {
+ DCHECK(non_thread_safe_.CalledOnValidThread());
+ LOG(INFO) <<
+ "P2P: Peers were notified that updates are available on the server.";
+ if (delegate_) {
+ delegate_->OnOutgoingNotification();
+ }
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/listener/talk_mediator_impl.h b/jingle/notifier/listener/talk_mediator_impl.h
new file mode 100644
index 0000000..e6fc988
--- /dev/null
+++ b/jingle/notifier/listener/talk_mediator_impl.h
@@ -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.
+//
+// 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 JINGLE_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
+#define JINGLE_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/non_thread_safe.h"
+#include "base/scoped_ptr.h"
+#include "jingle/notifier/listener/mediator_thread.h"
+#include "jingle/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
+
+namespace notifier {
+
+class TalkMediatorImpl
+ : public TalkMediator, public MediatorThread::Delegate {
+ public:
+ // Takes ownership of |mediator_thread|.
+ TalkMediatorImpl(
+ MediatorThread* mediator_thread,
+ bool initialize_ssl,
+ bool connect_immediately,
+ bool invalidate_xmpp_auth_token);
+ virtual ~TalkMediatorImpl();
+
+ // TalkMediator implementation.
+
+ virtual void SetDelegate(TalkMediator::Delegate* delegate);
+
+ 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);
+
+ virtual void AddSubscribedServiceUrl(const std::string& service_url);
+
+ // MediatorThread::Delegate implementation.
+
+ virtual void OnConnectionStateChange(bool logged_in);
+
+ virtual void OnSubscriptionStateChange(bool subscribed);
+
+ virtual void OnIncomingNotification(
+ const IncomingNotificationData& notification_data);
+
+ virtual void OnOutgoingNotification();
+
+ 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.
+ };
+
+ NonThreadSafe non_thread_safe_;
+
+ // Delegate, which we don't own. May be NULL.
+ TalkMediator::Delegate* delegate_;
+
+ // Internal state.
+ TalkMediatorState state_;
+
+ // Cached and verfied from the SetAuthToken method.
+ buzz::XmppClientSettings xmpp_settings_;
+
+ // The worker thread through which talk events are posted and received.
+ scoped_ptr<MediatorThread> mediator_thread_;
+
+ const 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 // JINGLE_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
diff --git a/jingle/notifier/listener/talk_mediator_unittest.cc b/jingle/notifier/listener/talk_mediator_unittest.cc
new file mode 100644
index 0000000..88af342
--- /dev/null
+++ b/jingle/notifier/listener/talk_mediator_unittest.cc
@@ -0,0 +1,207 @@
+// 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 <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "jingle/notifier/listener/mediator_thread_mock.h"
+#include "jingle/notifier/listener/mediator_thread_impl.h"
+#include "jingle/notifier/listener/talk_mediator_impl.h"
+#include "talk/xmpp/xmppengine.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace notifier {
+
+using ::testing::_;
+
+class MockTalkMediatorDelegate : public TalkMediator::Delegate {
+ public:
+ MockTalkMediatorDelegate() {}
+ virtual ~MockTalkMediatorDelegate() {}
+
+ MOCK_METHOD1(OnNotificationStateChange,
+ void(bool notification_changed));
+ MOCK_METHOD1(OnIncomingNotification,
+ void(const IncomingNotificationData& data));
+ MOCK_METHOD0(OnOutgoingNotification, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockTalkMediatorDelegate);
+};
+
+class TalkMediatorImplTest : public testing::Test {
+ protected:
+ TalkMediatorImplTest() {}
+ virtual ~TalkMediatorImplTest() {}
+
+ TalkMediatorImpl* NewTalkMediator() {
+ const bool kInitializeSsl = true;
+ const bool kConnectImmediately = false;
+ const bool kInvalidateXmppAuthToken = false;
+ return new TalkMediatorImpl(new MediatorThreadImpl(), kInitializeSsl,
+ kConnectImmediately, kInvalidateXmppAuthToken);
+ }
+
+ TalkMediatorImpl* NewMockedTalkMediator(
+ MockMediatorThread* mock_mediator_thread) {
+ const bool kInitializeSsl = false;
+ const bool kConnectImmediately = true;
+ const bool kInvalidateXmppAuthToken = false;
+ return new TalkMediatorImpl(mock_mediator_thread,
+ kInitializeSsl, kConnectImmediately,
+ kInvalidateXmppAuthToken);
+ }
+
+ int last_message_;
+
+ private:
+ // TalkMediatorImpl expects a message loop.
+ MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(TalkMediatorImplTest);
+};
+
+TEST_F(TalkMediatorImplTest, ConstructionOfTheClass) {
+ // Constructing a single talk mediator enables SSL through the singleton.
+ scoped_ptr<TalkMediatorImpl> talk1(NewTalkMediator());
+}
+
+TEST_F(TalkMediatorImplTest, SetAuthTokenWithBadInput) {
+ scoped_ptr<TalkMediatorImpl> talk1(
+ NewMockedTalkMediator(new MockMediatorThread()));
+ EXPECT_FALSE(talk1->SetAuthToken("@missinguser.com", "", "fake_service"));
+ EXPECT_FALSE(talk1->state_.initialized);
+
+ scoped_ptr<TalkMediatorImpl> talk2(
+ NewMockedTalkMediator(new MockMediatorThread()));
+ EXPECT_FALSE(talk2->SetAuthToken("", "1234567890", "fake_service"));
+ EXPECT_FALSE(talk2->state_.initialized);
+
+ scoped_ptr<TalkMediatorImpl> talk3(
+ NewMockedTalkMediator(new MockMediatorThread()));
+ EXPECT_FALSE(talk3->SetAuthToken("missingdomain", "abcde", "fake_service"));
+ EXPECT_FALSE(talk3->state_.initialized);
+}
+
+TEST_F(TalkMediatorImplTest, SetAuthTokenWithGoodInput) {
+ scoped_ptr<TalkMediatorImpl> talk1(
+ NewMockedTalkMediator(new MockMediatorThread()));
+ EXPECT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service"));
+ EXPECT_TRUE(talk1->state_.initialized);
+
+ scoped_ptr<TalkMediatorImpl> talk2(
+ NewMockedTalkMediator(new MockMediatorThread()));
+ EXPECT_TRUE(talk2->SetAuthToken("chromium@mail.google.com", "token",
+ "fake_service"));
+ EXPECT_TRUE(talk2->state_.initialized);
+
+ scoped_ptr<TalkMediatorImpl> talk3(
+ NewMockedTalkMediator(new MockMediatorThread()));
+ EXPECT_TRUE(talk3->SetAuthToken("chromium@chromium.org", "token",
+ "fake_service"));
+ EXPECT_TRUE(talk3->state_.initialized);
+}
+
+TEST_F(TalkMediatorImplTest, LoginWiring) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(NewMockedTalkMediator(mock));
+
+ // Login checks states for initialization.
+ EXPECT_FALSE(talk1->Login());
+ EXPECT_EQ(0, mock->login_calls);
+
+ EXPECT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service"));
+ EXPECT_TRUE(talk1->Login());
+ EXPECT_EQ(1, mock->login_calls);
+
+ // Successive calls to login will fail. One needs to create a new talk
+ // mediator object.
+ EXPECT_FALSE(talk1->Login());
+ EXPECT_EQ(1, mock->login_calls);
+
+ EXPECT_TRUE(talk1->Logout());
+ EXPECT_EQ(1, mock->logout_calls);
+
+ // Successive logout calls do nothing.
+ EXPECT_FALSE(talk1->Logout());
+ EXPECT_EQ(1, mock->logout_calls);
+}
+
+TEST_F(TalkMediatorImplTest, SendNotification) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(NewMockedTalkMediator(mock));
+
+ // Failure due to not being logged in.
+ OutgoingNotificationData data;
+ EXPECT_FALSE(talk1->SendNotification(data));
+ EXPECT_EQ(0, mock->send_calls);
+
+ EXPECT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service"));
+ EXPECT_TRUE(talk1->Login());
+ talk1->OnConnectionStateChange(true);
+ EXPECT_EQ(1, mock->login_calls);
+
+ // Should be subscribed now.
+ EXPECT_TRUE(talk1->state_.subscribed);
+ EXPECT_TRUE(talk1->SendNotification(data));
+ EXPECT_EQ(1, mock->send_calls);
+ EXPECT_TRUE(talk1->SendNotification(data));
+ EXPECT_EQ(2, mock->send_calls);
+
+ EXPECT_TRUE(talk1->Logout());
+ EXPECT_EQ(1, mock->logout_calls);
+
+ // Failure due to being logged out.
+ EXPECT_FALSE(talk1->SendNotification(data));
+ EXPECT_EQ(2, mock->send_calls);
+}
+
+TEST_F(TalkMediatorImplTest, MediatorThreadCallbacks) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(NewMockedTalkMediator(mock));
+
+ MockTalkMediatorDelegate mock_delegate;
+ EXPECT_CALL(mock_delegate, OnNotificationStateChange(true));
+ EXPECT_CALL(mock_delegate, OnIncomingNotification(_));
+ EXPECT_CALL(mock_delegate, OnOutgoingNotification());
+
+ talk1->SetDelegate(&mock_delegate);
+
+ EXPECT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token",
+ "fake_service"));
+ EXPECT_TRUE(talk1->Login());
+ EXPECT_EQ(1, mock->login_calls);
+
+ // The message triggers calls to listen and subscribe.
+ EXPECT_EQ(1, mock->listen_calls);
+ EXPECT_EQ(1, mock->subscribe_calls);
+ EXPECT_TRUE(talk1->state_.subscribed);
+
+ // After subscription success is receieved, the talk mediator will allow
+ // sending of notifications.
+ OutgoingNotificationData outgoing_data;
+ EXPECT_TRUE(talk1->SendNotification(outgoing_data));
+ EXPECT_EQ(1, mock->send_calls);
+
+ IncomingNotificationData incoming_data;
+ incoming_data.service_url = "service_url";
+ incoming_data.service_specific_data = "service_data";
+ mock->ReceiveNotification(incoming_data);
+
+ // Shouldn't trigger a call to the delegate since we disconnect
+ // it before we logout.
+ talk1.reset();
+ EXPECT_EQ(1, mock->logout_calls);
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/listener/xml_element_util.cc b/jingle/notifier/listener/xml_element_util.cc
new file mode 100644
index 0000000..9db99c9
--- /dev/null
+++ b/jingle/notifier/listener/xml_element_util.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "jingle/notifier/listener/xml_element_util.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/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(buzz::STR_EMPTY, name);
+ const buzz::QName boolAttrQName(buzz::STR_EMPTY, "bool");
+ buzz::XmlElement* bool_xml_element =
+ new buzz::XmlElement(elementQName, true);
+ bool_xml_element->AddAttr(boolAttrQName, value ? "true" : "false");
+ return bool_xml_element;
+}
+
+buzz::XmlElement* MakeIntXmlElement(const char* name, int value) {
+ const buzz::QName elementQName(buzz::STR_EMPTY, name);
+ const buzz::QName intAttrQName(buzz::STR_EMPTY, "int");
+ buzz::XmlElement* int_xml_element =
+ new buzz::XmlElement(elementQName, true);
+ int_xml_element->AddAttr(intAttrQName, IntToString(value));
+ return int_xml_element;
+}
+
+buzz::XmlElement* MakeStringXmlElement(const char* name, const char* value) {
+ const buzz::QName elementQName(buzz::STR_EMPTY, name);
+ const buzz::QName dataAttrQName(buzz::STR_EMPTY, "data");
+ buzz::XmlElement* data_xml_element =
+ new buzz::XmlElement(elementQName, true);
+ data_xml_element->AddAttr(dataAttrQName, value);
+ return data_xml_element;
+}
+
+} // namespace notifier
diff --git a/jingle/notifier/listener/xml_element_util.h b/jingle/notifier/listener/xml_element_util.h
new file mode 100644
index 0000000..bde8657
--- /dev/null
+++ b/jingle/notifier/listener/xml_element_util.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef JINGLE_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_
+#define JINGLE_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_
+
+#include <string>
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace notifier {
+
+std::string XmlElementToString(const buzz::XmlElement& xml_element);
+
+// The functions below are helpful for building notifications-related
+// XML stanzas.
+
+buzz::XmlElement* MakeBoolXmlElement(const char* name, bool value);
+
+buzz::XmlElement* MakeIntXmlElement(const char* name, int value);
+
+buzz::XmlElement* MakeStringXmlElement(const char* name, const char* value);
+
+} // namespace notifier
+
+#endif // JINGLE_NOTIFIER_LISTENER_XML_ELEMENT_UTIL_H_
diff --git a/jingle/notifier/listener/xml_element_util_unittest.cc b/jingle/notifier/listener/xml_element_util_unittest.cc
new file mode 100644
index 0000000..4566fe8
--- /dev/null
+++ b/jingle/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 "jingle/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("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