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/heartbeat_sender.h"
6
7#include <math.h>
8
9#include "base/bind.h"
10#include "base/message_loop/message_loop_proxy.h"
11#include "base/rand_util.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/stringize_macros.h"
14#include "base/time/time.h"
15#include "remoting/base/constants.h"
16#include "remoting/base/logging.h"
17#include "remoting/host/server_log_entry.h"
18#include "remoting/jingle_glue/iq_sender.h"
19#include "remoting/jingle_glue/signal_strategy.h"
20#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
21#include "third_party/libjingle/source/talk/xmpp/constants.h"
22
23using buzz::QName;
24using buzz::XmlElement;
25
26namespace remoting {
27
28namespace {
29
30const char kHeartbeatQueryTag[] = "heartbeat";
31const char kHostIdAttr[] = "hostid";
32const char kHostVersionTag[] = "host-version";
33const char kHeartbeatSignatureTag[] = "signature";
34const char kSequenceIdAttr[] = "sequence-id";
35
36const char kErrorTag[] = "error";
37const char kNotFoundTag[] = "item-not-found";
38
39const char kHeartbeatResultTag[] = "heartbeat-result";
40const char kSetIntervalTag[] = "set-interval";
41const char kExpectedSequenceIdTag[] = "expected-sequence-id";
42
43const int64 kDefaultHeartbeatIntervalMs = 5 * 60 * 1000;  // 5 minutes.
44const int64 kResendDelayMs = 10 * 1000;  // 10 seconds.
45const int64 kResendDelayOnHostNotFoundMs = 10 * 1000; // 10 seconds.
46const int kMaxResendOnHostNotFoundCount = 12;  // 2 minutes (12 x 10 seconds).
47
48}  // namespace
49
50HeartbeatSender::HeartbeatSender(
51    Listener* listener,
52    const std::string& host_id,
53    SignalStrategy* signal_strategy,
54    scoped_refptr<RsaKeyPair> key_pair,
55    const std::string& directory_bot_jid)
56    : listener_(listener),
57      host_id_(host_id),
58      signal_strategy_(signal_strategy),
59      key_pair_(key_pair),
60      directory_bot_jid_(directory_bot_jid),
61      interval_ms_(kDefaultHeartbeatIntervalMs),
62      sequence_id_(0),
63      sequence_id_was_set_(false),
64      sequence_id_recent_set_num_(0),
65      heartbeat_succeeded_(false),
66      failed_startup_heartbeat_count_(0) {
67  DCHECK(signal_strategy_);
68  DCHECK(key_pair_.get());
69
70  signal_strategy_->AddListener(this);
71
72  // Start heartbeats if the |signal_strategy_| is already connected.
73  OnSignalStrategyStateChange(signal_strategy_->GetState());
74}
75
76HeartbeatSender::~HeartbeatSender() {
77  signal_strategy_->RemoveListener(this);
78}
79
80void HeartbeatSender::OnSignalStrategyStateChange(SignalStrategy::State state) {
81  if (state == SignalStrategy::CONNECTED) {
82    iq_sender_.reset(new IqSender(signal_strategy_));
83    SendStanza();
84    timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(interval_ms_),
85                 this, &HeartbeatSender::SendStanza);
86  } else if (state == SignalStrategy::DISCONNECTED) {
87    request_.reset();
88    iq_sender_.reset();
89    timer_.Stop();
90    timer_resend_.Stop();
91  }
92}
93
94bool HeartbeatSender::OnSignalStrategyIncomingStanza(
95    const buzz::XmlElement* stanza) {
96  return false;
97}
98
99void HeartbeatSender::SendStanza() {
100  DoSendStanza();
101  // Make sure we don't send another heartbeat before the heartbeat interval
102  // has expired.
103  timer_resend_.Stop();
104}
105
106void HeartbeatSender::ResendStanza() {
107  DoSendStanza();
108  // Make sure we don't send another heartbeat before the heartbeat interval
109  // has expired.
110  timer_.Reset();
111}
112
113void HeartbeatSender::DoSendStanza() {
114  VLOG(1) << "Sending heartbeat stanza to " << directory_bot_jid_;
115  request_ = iq_sender_->SendIq(
116      buzz::STR_SET, directory_bot_jid_, CreateHeartbeatMessage(),
117      base::Bind(&HeartbeatSender::ProcessResponse,
118                 base::Unretained(this)));
119  ++sequence_id_;
120}
121
122void HeartbeatSender::ProcessResponse(IqRequest* request,
123                                      const XmlElement* response) {
124  std::string type = response->Attr(buzz::QN_TYPE);
125  if (type == buzz::STR_ERROR) {
126    const XmlElement* error_element =
127        response->FirstNamed(QName(buzz::NS_CLIENT, kErrorTag));
128    if (error_element) {
129      if (error_element->FirstNamed(QName(buzz::NS_STANZA, kNotFoundTag))) {
130        LOG(ERROR) << "Received error: Host ID not found";
131        // If the host was registered immediately before it sends a heartbeat,
132        // then server-side latency may prevent the server recognizing the
133        // host ID in the heartbeat. So even if all of the first few heartbeats
134        // get a "host ID not found" error, that's not a good enough reason to
135        // exit.
136        failed_startup_heartbeat_count_++;
137        if (!heartbeat_succeeded_ && (failed_startup_heartbeat_count_ <=
138                kMaxResendOnHostNotFoundCount)) {
139          timer_resend_.Start(FROM_HERE,
140                              base::TimeDelta::FromMilliseconds(
141                                  kResendDelayOnHostNotFoundMs),
142                              this,
143                              &HeartbeatSender::ResendStanza);
144          return;
145        }
146        listener_->OnUnknownHostIdError();
147        return;
148      }
149    }
150
151    LOG(ERROR) << "Received error in response to heartbeat: "
152               << response->Str();
153    return;
154  }
155
156  // Notify listener of the first successful heartbeat.
157  if (!heartbeat_succeeded_) {
158    listener_->OnHeartbeatSuccessful();
159  }
160  heartbeat_succeeded_ = true;
161
162  // This method must only be called for error or result stanzas.
163  DCHECK_EQ(std::string(buzz::STR_RESULT), type);
164
165  const XmlElement* result_element =
166      response->FirstNamed(QName(kChromotingXmlNamespace, kHeartbeatResultTag));
167  if (result_element) {
168    const XmlElement* set_interval_element =
169        result_element->FirstNamed(QName(kChromotingXmlNamespace,
170                                         kSetIntervalTag));
171    if (set_interval_element) {
172      const std::string& interval_str = set_interval_element->BodyText();
173      int interval;
174      if (!base::StringToInt(interval_str, &interval) || interval <= 0) {
175        LOG(ERROR) << "Received invalid set-interval: "
176                   << set_interval_element->Str();
177      } else {
178        SetInterval(interval * base::Time::kMillisecondsPerSecond);
179      }
180    }
181
182    bool did_set_sequence_id = false;
183    const XmlElement* expected_sequence_id_element =
184        result_element->FirstNamed(QName(kChromotingXmlNamespace,
185                                         kExpectedSequenceIdTag));
186    if (expected_sequence_id_element) {
187      // The sequence ID sent in the previous heartbeat was not what the server
188      // expected, so send another heartbeat with the expected sequence ID.
189      const std::string& expected_sequence_id_str =
190          expected_sequence_id_element->BodyText();
191      int expected_sequence_id;
192      if (!base::StringToInt(expected_sequence_id_str, &expected_sequence_id)) {
193        LOG(ERROR) << "Received invalid " << kExpectedSequenceIdTag << ": " <<
194            expected_sequence_id_element->Str();
195      } else {
196        SetSequenceId(expected_sequence_id);
197        sequence_id_recent_set_num_++;
198        did_set_sequence_id = true;
199      }
200    }
201    if (!did_set_sequence_id) {
202      sequence_id_recent_set_num_ = 0;
203    }
204  }
205}
206
207void HeartbeatSender::SetInterval(int interval) {
208  if (interval != interval_ms_) {
209    interval_ms_ = interval;
210
211    // Restart the timer with the new interval.
212    if (timer_.IsRunning()) {
213      timer_.Stop();
214      timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(interval_ms_),
215                   this, &HeartbeatSender::SendStanza);
216    }
217  }
218}
219
220void HeartbeatSender::SetSequenceId(int sequence_id) {
221  sequence_id_ = sequence_id;
222  // Setting the sequence ID may be a symptom of a temporary server-side
223  // problem, which would affect many hosts, so don't send a new heartbeat
224  // immediately, as many hosts doing so may overload the server.
225  // But the server will usually set the sequence ID when it receives the first
226  // heartbeat from a host. In that case, we can send a new heartbeat
227  // immediately, as that only happens once per host instance.
228  if (!sequence_id_was_set_) {
229    ResendStanza();
230  } else {
231    HOST_LOG << "The heartbeat sequence ID has been set more than once: "
232              << "the new value is " << sequence_id;
233    double delay = pow(2.0, sequence_id_recent_set_num_) *
234        (1 + base::RandDouble()) * kResendDelayMs;
235    if (delay <= interval_ms_) {
236      timer_resend_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay),
237                          this, &HeartbeatSender::ResendStanza);
238    }
239  }
240  sequence_id_was_set_ = true;
241}
242
243scoped_ptr<XmlElement> HeartbeatSender::CreateHeartbeatMessage() {
244  // Create heartbeat stanza.
245  scoped_ptr<XmlElement> heartbeat(new XmlElement(
246      QName(kChromotingXmlNamespace, kHeartbeatQueryTag)));
247  heartbeat->AddAttr(QName(kChromotingXmlNamespace, kHostIdAttr), host_id_);
248  heartbeat->AddAttr(QName(kChromotingXmlNamespace, kSequenceIdAttr),
249                 base::IntToString(sequence_id_));
250  heartbeat->AddElement(CreateSignature().release());
251  // Append host version.
252  scoped_ptr<XmlElement> version_tag(new XmlElement(
253      QName(kChromotingXmlNamespace, kHostVersionTag)));
254  version_tag->AddText(STRINGIZE(VERSION));
255  heartbeat->AddElement(version_tag.release());
256  // Append log message (which isn't signed).
257  scoped_ptr<XmlElement> log(ServerLogEntry::MakeStanza());
258  scoped_ptr<ServerLogEntry> log_entry(ServerLogEntry::MakeForHeartbeat());
259  log_entry->AddHostFields();
260  log->AddElement(log_entry->ToStanza().release());
261  heartbeat->AddElement(log.release());
262  return heartbeat.Pass();
263}
264
265scoped_ptr<XmlElement> HeartbeatSender::CreateSignature() {
266  scoped_ptr<XmlElement> signature_tag(new XmlElement(
267      QName(kChromotingXmlNamespace, kHeartbeatSignatureTag)));
268
269  std::string message = signal_strategy_->GetLocalJid() + ' ' +
270      base::IntToString(sequence_id_);
271  std::string signature(key_pair_->SignMessage(message));
272  signature_tag->AddText(signature);
273
274  return signature_tag.Pass();
275}
276
277}  // namespace remoting
278