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