1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/signaling_connector.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/strings/string_util.h"
10#include "google_apis/google_api_keys.h"
11#include "net/url_request/url_fetcher.h"
12#include "net/url_request/url_request_context_getter.h"
13#include "remoting/base/logging.h"
14#include "remoting/host/dns_blackhole_checker.h"
15
16namespace remoting {
17
18namespace {
19
20// The delay between reconnect attempts will increase exponentially up
21// to the maximum specified here.
22const int kMaxReconnectDelaySeconds = 10 * 60;
23
24}  // namespace
25
26SignalingConnector::SignalingConnector(
27    XmppSignalStrategy* signal_strategy,
28    scoped_ptr<DnsBlackholeChecker> dns_blackhole_checker,
29    const base::Closure& auth_failed_callback)
30    : signal_strategy_(signal_strategy),
31      auth_failed_callback_(auth_failed_callback),
32      dns_blackhole_checker_(dns_blackhole_checker.Pass()),
33      reconnect_attempts_(0) {
34  DCHECK(!auth_failed_callback_.is_null());
35  DCHECK(dns_blackhole_checker_.get());
36  net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
37  net::NetworkChangeNotifier::AddIPAddressObserver(this);
38  signal_strategy_->AddListener(this);
39  ScheduleTryReconnect();
40}
41
42SignalingConnector::~SignalingConnector() {
43  signal_strategy_->RemoveListener(this);
44  net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
45  net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
46}
47
48void SignalingConnector::EnableOAuth(OAuthTokenGetter* oauth_token_getter) {
49  oauth_token_getter_ = oauth_token_getter;
50}
51
52void SignalingConnector::OnSignalStrategyStateChange(
53    SignalStrategy::State state) {
54  DCHECK(CalledOnValidThread());
55
56  if (state == SignalStrategy::CONNECTED) {
57    HOST_LOG << "Signaling connected.";
58    reconnect_attempts_ = 0;
59  } else if (state == SignalStrategy::DISCONNECTED) {
60    HOST_LOG << "Signaling disconnected.";
61    reconnect_attempts_++;
62
63    // If authentication failed then we have an invalid OAuth token,
64    // inform the upper layer about it.
65    if (signal_strategy_->GetError() == SignalStrategy::AUTHENTICATION_FAILED) {
66      auth_failed_callback_.Run();
67    } else {
68      ScheduleTryReconnect();
69    }
70  }
71}
72
73bool SignalingConnector::OnSignalStrategyIncomingStanza(
74    const buzz::XmlElement* stanza) {
75  return false;
76}
77
78void SignalingConnector::OnConnectionTypeChanged(
79    net::NetworkChangeNotifier::ConnectionType type) {
80  DCHECK(CalledOnValidThread());
81  if (type != net::NetworkChangeNotifier::CONNECTION_NONE &&
82      signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) {
83    HOST_LOG << "Network state changed to online.";
84    ResetAndTryReconnect();
85  }
86}
87
88void SignalingConnector::OnIPAddressChanged() {
89  DCHECK(CalledOnValidThread());
90  if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) {
91    HOST_LOG << "IP address has changed.";
92    ResetAndTryReconnect();
93  }
94}
95
96void SignalingConnector::OnAccessToken(OAuthTokenGetter::Status status,
97                                       const std::string& user_email,
98                                       const std::string& access_token) {
99  DCHECK(CalledOnValidThread());
100
101  if (status == OAuthTokenGetter::AUTH_ERROR) {
102    auth_failed_callback_.Run();
103    return;
104  } else if (status == OAuthTokenGetter::NETWORK_ERROR) {
105    OnNetworkError();
106    return;
107  }
108
109  DCHECK_EQ(status, OAuthTokenGetter::SUCCESS);
110  HOST_LOG << "Received user info.";
111
112  signal_strategy_->SetAuthInfo(user_email, access_token, "oauth2");
113
114  // Now that we've refreshed the token and verified that it's for the correct
115  // user account, try to connect using the new token.
116  DCHECK_EQ(signal_strategy_->GetState(), SignalStrategy::DISCONNECTED);
117  signal_strategy_->Connect();
118}
119
120void SignalingConnector::OnNetworkError() {
121  DCHECK(CalledOnValidThread());
122  reconnect_attempts_++;
123  ScheduleTryReconnect();
124}
125
126void SignalingConnector::ScheduleTryReconnect() {
127  DCHECK(CalledOnValidThread());
128  if (timer_.IsRunning() || net::NetworkChangeNotifier::IsOffline())
129    return;
130  int delay_s = std::min(1 << reconnect_attempts_,
131                         kMaxReconnectDelaySeconds);
132  timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(delay_s),
133               this, &SignalingConnector::TryReconnect);
134}
135
136void SignalingConnector::ResetAndTryReconnect() {
137  DCHECK(CalledOnValidThread());
138  signal_strategy_->Disconnect();
139  reconnect_attempts_ = 0;
140  timer_.Stop();
141  ScheduleTryReconnect();
142}
143
144void SignalingConnector::TryReconnect() {
145  DCHECK(CalledOnValidThread());
146  DCHECK(dns_blackhole_checker_.get());
147
148  // This will check if this machine is allowed to access the chromoting
149  // host talkgadget.
150  dns_blackhole_checker_->CheckForDnsBlackhole(
151      base::Bind(&SignalingConnector::OnDnsBlackholeCheckerDone,
152                 base::Unretained(this)));
153}
154
155void SignalingConnector::OnDnsBlackholeCheckerDone(bool allow) {
156  DCHECK(CalledOnValidThread());
157
158  // Unable to access the host talkgadget. Don't allow the connection, but
159  // schedule a reconnect in case this is a transient problem rather than
160  // an outright block.
161  if (!allow) {
162    reconnect_attempts_++;
163    HOST_LOG << "Talkgadget check failed. Scheduling reconnect. Attempt "
164              << reconnect_attempts_;
165    ScheduleTryReconnect();
166    return;
167  }
168
169  if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) {
170    HOST_LOG << "Attempting to connect signaling.";
171    oauth_token_getter_->CallWithToken(
172        base::Bind(&SignalingConnector::OnAccessToken, AsWeakPtr()));
173  }
174}
175
176}  // namespace remoting
177