// 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 #include "chrome/browser/sync/notifier/communicator/login.h" #include "chrome/browser/sync/notifier/base/network_status_detector_task.h" #include "chrome/browser/sync/notifier/base/time.h" #include "chrome/browser/sync/notifier/base/timer.h" #include "chrome/browser/sync/notifier/communicator/auto_reconnect.h" #include "chrome/browser/sync/notifier/communicator/connection_options.h" #include "chrome/browser/sync/notifier/communicator/login_settings.h" #include "chrome/browser/sync/notifier/communicator/product_info.h" #include "chrome/browser/sync/notifier/communicator/single_login_attempt.h" #include "talk/base/common.h" #include "talk/base/firewallsocketserver.h" #include "talk/base/logging.h" #include "talk/base/taskrunner.h" #include "talk/xmllite/xmlelement.h" #include "talk/xmpp/asyncsocket.h" #include "talk/xmpp/prexmppauth.h" #include "talk/xmpp/xmppclient.h" #include "talk/xmpp/xmppclientsettings.h" #include "talk/xmpp/xmppengine.h" namespace notifier { // Redirect valid for 5 minutes. static const time64 kRedirectTimeoutNs = 5 * kMinsTo100ns; // Disconnect if network stays down for more than 10 seconds. static const int kDisconnectionDelaySecs = 10; Login::Login(talk_base::Task* parent, const buzz::XmppClientSettings& user_settings, const ConnectionOptions& options, std::string lang, ServerInformation* server_list, int server_count, NetworkStatusDetectorTask* network_status, talk_base::FirewallManager* firewall, bool no_gaia_auth, bool proxy_only, bool previous_login_successful) : login_settings_(new LoginSettings(user_settings, options, lang, server_list, server_count, firewall, no_gaia_auth, proxy_only)), single_attempt_(NULL), successful_connection_(previous_login_successful), parent_(parent), state_(STATE_OPENING), redirect_time_ns_(0), redirect_port_(0), unexpected_disconnect_occurred_(false), reset_unexpected_timer_(NULL), google_host_(user_settings.host()), google_user_(user_settings.user()), disconnect_timer_(NULL) { if (!network_status) { network_status = NetworkStatusDetectorTask::Create(parent_); if (network_status) { // On linux we don't have an implementation of NetworkStatusDetectorTask. network_status->Start(); } } if (network_status) { network_status->SignalNetworkStateDetected.connect( this, &Login::OnNetworkStateDetected); auto_reconnect_.reset(new AutoReconnect(parent_, network_status)); auto_reconnect_->SignalStartConnection.connect(this, &Login::StartConnection); auto_reconnect_->SignalTimerStartStop.connect( this, &Login::OnAutoReconnectTimerChange); SignalClientStateChange.connect(auto_reconnect_.get(), &AutoReconnect::OnClientStateChange); SignalIdleChange.connect(auto_reconnect_.get(), &AutoReconnect::set_idle); SignalPowerSuspended.connect(auto_reconnect_.get(), &AutoReconnect::OnPowerSuspend); } } // Defined so that the destructors are executed here (and the corresponding // classes don't need to be included in the header file). Login::~Login() { if (single_attempt_) { single_attempt_->Abort(); single_attempt_ = NULL; } } void Login::StartConnection() { // If there is a server redirect, use it. if (GetCurrent100NSTime() < redirect_time_ns_ + kRedirectTimeoutNs) { // Override server/port with redirect values. talk_base::SocketAddress server_override; server_override.SetIP(redirect_server_, false); ASSERT(redirect_port_ != 0); server_override.SetPort(redirect_port_); login_settings_->set_server_override(server_override); } else { login_settings_->clear_server_override(); } if (single_attempt_) { single_attempt_->Abort(); single_attempt_ = NULL; } single_attempt_ = new SingleLoginAttempt(parent_, login_settings_.get(), successful_connection_); // Do the signaling hook-ups. single_attempt_->SignalLoginFailure.connect(this, &Login::OnLoginFailure); single_attempt_->SignalRedirect.connect(this, &Login::OnRedirect); single_attempt_->SignalClientStateChange.connect( this, &Login::OnClientStateChange); single_attempt_->SignalUnexpectedDisconnect.connect( this, &Login::OnUnexpectedDisconnect); single_attempt_->SignalLogoff.connect( this, &Login::OnLogoff); single_attempt_->SignalNeedAutoReconnect.connect( this, &Login::DoAutoReconnect); SignalLogInput.repeat(single_attempt_->SignalLogInput); SignalLogOutput.repeat(single_attempt_->SignalLogOutput); single_attempt_->Start(); } const std::string& Login::google_host() const { return google_host_; } const std::string& Login::google_user() const { return google_user_; } const talk_base::ProxyInfo& Login::proxy() const { return proxy_info_; } void Login::OnLoginFailure(const LoginFailure& failure) { auto_reconnect_->StopReconnectTimer(); HandleClientStateChange(STATE_CLOSED); SignalLoginFailure(failure); } void Login::OnLogoff() { HandleClientStateChange(STATE_CLOSED); } void Login::OnClientStateChange(buzz::XmppEngine::State state) { ConnectionState new_state = STATE_CLOSED; switch (state) { case buzz::XmppEngine::STATE_NONE: case buzz::XmppEngine::STATE_CLOSED: // Ignore the closed state (because we may be trying the next dns entry). // // But we go to this state for other signals when there is no retry // happening. new_state = state_; break; case buzz::XmppEngine::STATE_START: case buzz::XmppEngine::STATE_OPENING: new_state = STATE_OPENING; break; case buzz::XmppEngine::STATE_OPEN: new_state = STATE_OPENED; break; default: ASSERT(false); break; } HandleClientStateChange(new_state); } void Login::HandleClientStateChange(ConnectionState new_state) { // Do we need to transition between the retrying and closed states? if (auto_reconnect_.get() && auto_reconnect_->is_retrying()) { if (new_state == STATE_CLOSED) { new_state = STATE_RETRYING; } } else { if (new_state == STATE_RETRYING) { new_state = STATE_CLOSED; } } if (new_state != state_) { state_ = new_state; if (reset_unexpected_timer_) { reset_unexpected_timer_->Abort(); reset_unexpected_timer_ = NULL; } if (state_ == STATE_OPENED) { successful_connection_ = true; google_host_ = single_attempt_->xmpp_client()->jid().domain(); google_user_ = single_attempt_->xmpp_client()->jid().node(); proxy_info_ = single_attempt_->proxy(); reset_unexpected_timer_ = new Timer(parent_, kResetReconnectInfoDelaySec, false); // Repeat. reset_unexpected_timer_->SignalTimeout.connect( this, &Login::ResetUnexpectedDisconnect); } SignalClientStateChange(state_); } } void Login::OnAutoReconnectTimerChange() { if (!single_attempt_ || !single_attempt_->xmpp_client()) { HandleClientStateChange(STATE_CLOSED); return; } OnClientStateChange(single_attempt_->xmpp_client()->GetState()); } buzz::XmppClient* Login::xmpp_client() { if (!single_attempt_) { return NULL; } return single_attempt_->xmpp_client(); } int Login::seconds_until_reconnect() const { return auto_reconnect_->seconds_until(); } void Login::UseNextConnection() { if (!single_attempt_) { // Just in case, there is an obscure case that causes this to get called // when there is no single_attempt_. return; } single_attempt_->UseNextConnection(); } void Login::UseCurrentConnection() { if (!single_attempt_) { // Just in case, there is an obscure case that causes this to get called // when there is no single_attempt_. return; } single_attempt_->UseCurrentConnection(); } void Login::OnRedirect(const std::string& redirect_server, int redirect_port) { ASSERT(redirect_port_ != 0); redirect_time_ns_ = GetCurrent100NSTime(); redirect_server_ = redirect_server; redirect_port_ = redirect_port; // Drop the current connection, and start the login process again. StartConnection(); } void Login::OnUnexpectedDisconnect() { if (reset_unexpected_timer_) { reset_unexpected_timer_->Abort(); reset_unexpected_timer_ = NULL; } // Start the login process again. if (unexpected_disconnect_occurred_) { // If we already have received an unexpected disconnect recently, then our // account may have be jailed due to abuse, so we shouldn't make the // situation worse by trying really hard to reconnect. Instead, we'll do // the autoreconnect route, which has exponential back-off. DoAutoReconnect(); return; } StartConnection(); unexpected_disconnect_occurred_ = true; } void Login::ResetUnexpectedDisconnect() { reset_unexpected_timer_ = NULL; unexpected_disconnect_occurred_ = false; } void Login::DoAutoReconnect() { bool allow_auto_reconnect = login_settings_->connection_options().auto_reconnect(); // Start the reconnect time before aborting the connection to ensure that // AutoReconnect::is_retrying() is true, so that the Login doesn't // transition to the CLOSED state (which would cause the reconnection timer // to reset and not double). if (allow_auto_reconnect) { auto_reconnect_->StartReconnectTimer(); } if (single_attempt_) { single_attempt_->Abort(); single_attempt_ = NULL; } if (!allow_auto_reconnect) { HandleClientStateChange(STATE_CLOSED); return; } } void Login::OnNetworkStateDetected(bool was_alive, bool is_alive) { if (was_alive && !is_alive) { // Our network connection just went down. Setup a timer to disconnect. // Don't disconnect immediately to avoid constant // connection/disconnection due to flaky network interfaces. ASSERT(disconnect_timer_ == NULL); disconnect_timer_ = new Timer(parent_, kDisconnectionDelaySecs, false); disconnect_timer_->SignalTimeout.connect(this, &Login::OnDisconnectTimeout); } else if (!was_alive && is_alive) { // Our connection has come back up. If we have a disconnect timer going, // abort it so we don't disconnect. if (disconnect_timer_) { disconnect_timer_->Abort(); // It will free itself. disconnect_timer_ = NULL; } } } void Login::OnDisconnectTimeout() { disconnect_timer_ = NULL; if (state_ != STATE_OPENED) { return; } if (single_attempt_) { single_attempt_->Abort(); single_attempt_ = NULL; } StartConnection(); } } // namespace notifier