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.
5 #include "remoting/host/heartbeat_sender.h"
10 #include "base/callback_helpers.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/thread_task_runner_handle.h"
15 #include "base/time/time.h"
16 #include "remoting/base/constants.h"
17 #include "remoting/base/logging.h"
18 #include "remoting/host/server_log_entry_host.h"
19 #include "remoting/signaling/iq_sender.h"
20 #include "remoting/signaling/server_log_entry.h"
21 #include "remoting/signaling/signal_strategy.h"
22 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
23 #include "third_party/webrtc/libjingle/xmpp/constants.h"
26 using buzz::XmlElement
;
32 const char kHeartbeatQueryTag
[] = "heartbeat";
33 const char kHostIdAttr
[] = "hostid";
34 const char kHostVersionTag
[] = "host-version";
35 const char kHeartbeatSignatureTag
[] = "signature";
36 const char kSequenceIdAttr
[] = "sequence-id";
37 const char kHostOfflineReasonAttr
[] = "host-offline-reason";
39 const char kErrorTag
[] = "error";
40 const char kNotFoundTag
[] = "item-not-found";
42 const char kHeartbeatResultTag
[] = "heartbeat-result";
43 const char kSetIntervalTag
[] = "set-interval";
44 const char kExpectedSequenceIdTag
[] = "expected-sequence-id";
46 const int64 kDefaultHeartbeatIntervalMs
= 5 * 60 * 1000; // 5 minutes.
47 const int64 kResendDelayMs
= 10 * 1000; // 10 seconds.
48 const int64 kResendDelayOnHostNotFoundMs
= 10 * 1000; // 10 seconds.
49 const int kMaxResendOnHostNotFoundCount
= 12; // 2 minutes (12 x 10 seconds).
53 HeartbeatSender::HeartbeatSender(
54 const base::Closure
& on_heartbeat_successful_callback
,
55 const base::Closure
& on_unknown_host_id_error
,
56 const std::string
& host_id
,
57 SignalStrategy
* signal_strategy
,
58 const scoped_refptr
<const RsaKeyPair
>& host_key_pair
,
59 const std::string
& directory_bot_jid
)
60 : on_heartbeat_successful_callback_(on_heartbeat_successful_callback
),
61 on_unknown_host_id_error_(on_unknown_host_id_error
),
63 signal_strategy_(signal_strategy
),
64 host_key_pair_(host_key_pair
),
65 directory_bot_jid_(directory_bot_jid
),
66 interval_ms_(kDefaultHeartbeatIntervalMs
),
68 sequence_id_was_set_(false),
69 sequence_id_recent_set_num_(0),
70 heartbeat_succeeded_(false),
71 failed_startup_heartbeat_count_(0) {
72 DCHECK(signal_strategy_
);
73 DCHECK(host_key_pair_
.get());
74 DCHECK(thread_checker_
.CalledOnValidThread());
76 signal_strategy_
->AddListener(this);
78 // Start heartbeats if the |signal_strategy_| is already connected.
79 OnSignalStrategyStateChange(signal_strategy_
->GetState());
82 HeartbeatSender::~HeartbeatSender() {
83 signal_strategy_
->RemoveListener(this);
86 void HeartbeatSender::OnSignalStrategyStateChange(SignalStrategy::State state
) {
87 DCHECK(thread_checker_
.CalledOnValidThread());
88 if (state
== SignalStrategy::CONNECTED
) {
89 iq_sender_
.reset(new IqSender(signal_strategy_
));
91 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(interval_ms_
),
92 this, &HeartbeatSender::SendStanza
);
93 } else if (state
== SignalStrategy::DISCONNECTED
) {
101 bool HeartbeatSender::OnSignalStrategyIncomingStanza(
102 const buzz::XmlElement
* stanza
) {
106 void HeartbeatSender::OnHostOfflineReasonTimeout() {
107 DCHECK(!host_offline_reason_ack_callback_
.is_null());
109 base::ResetAndReturn(&host_offline_reason_ack_callback_
).Run(false);
112 void HeartbeatSender::OnHostOfflineReasonAck() {
113 if (host_offline_reason_ack_callback_
.is_null()) {
114 DCHECK(!host_offline_reason_timeout_timer_
.IsRunning());
118 DCHECK(host_offline_reason_timeout_timer_
.IsRunning());
119 host_offline_reason_timeout_timer_
.Stop();
121 // Run the ACK callback under a clean stack via PostTask() (because the
122 // callback can end up deleting |this| HeartbeatSender [i.e. when used from
123 // HostSignalingManager]).
124 base::ThreadTaskRunnerHandle::Get()->PostTask(
126 base::Bind(base::ResetAndReturn(&host_offline_reason_ack_callback_
),
130 void HeartbeatSender::SetHostOfflineReason(
131 const std::string
& host_offline_reason
,
132 const base::TimeDelta
& timeout
,
133 const base::Callback
<void(bool success
)>& ack_callback
) {
134 DCHECK(thread_checker_
.CalledOnValidThread());
135 DCHECK(host_offline_reason_ack_callback_
.is_null());
136 host_offline_reason_
= host_offline_reason
;
137 host_offline_reason_ack_callback_
= ack_callback
;
138 host_offline_reason_timeout_timer_
.Start(
139 FROM_HERE
, timeout
, this, &HeartbeatSender::OnHostOfflineReasonTimeout
);
140 if (signal_strategy_
->GetState() == SignalStrategy::CONNECTED
) {
145 void HeartbeatSender::SendStanza() {
146 // Make sure we don't send another heartbeat before the heartbeat interval
148 timer_resend_
.Stop();
152 void HeartbeatSender::ResendStanza() {
153 // Make sure we don't send another heartbeat before the heartbeat interval
159 void HeartbeatSender::DoSendStanza() {
160 DCHECK(thread_checker_
.CalledOnValidThread());
161 DCHECK(signal_strategy_
->GetState() == SignalStrategy::CONNECTED
);
162 VLOG(1) << "Sending heartbeat stanza to " << directory_bot_jid_
;
164 request_
= iq_sender_
->SendIq(
165 buzz::STR_SET
, directory_bot_jid_
, CreateHeartbeatMessage(),
166 base::Bind(&HeartbeatSender::ProcessResponse
,
167 base::Unretained(this),
168 !host_offline_reason_
.empty()));
172 void HeartbeatSender::ProcessResponse(
173 bool is_offline_heartbeat_response
,
175 const XmlElement
* response
) {
176 DCHECK(thread_checker_
.CalledOnValidThread());
178 std::string type
= response
->Attr(buzz::QN_TYPE
);
179 if (type
== buzz::STR_ERROR
) {
180 const XmlElement
* error_element
=
181 response
->FirstNamed(QName(buzz::NS_CLIENT
, kErrorTag
));
183 if (error_element
->FirstNamed(QName(buzz::NS_STANZA
, kNotFoundTag
))) {
184 LOG(ERROR
) << "Received error: Host ID not found";
185 // If the host was registered immediately before it sends a heartbeat,
186 // then server-side latency may prevent the server recognizing the
187 // host ID in the heartbeat. So even if all of the first few heartbeats
188 // get a "host ID not found" error, that's not a good enough reason to
190 failed_startup_heartbeat_count_
++;
191 if (!heartbeat_succeeded_
&& (failed_startup_heartbeat_count_
<=
192 kMaxResendOnHostNotFoundCount
)) {
193 timer_resend_
.Start(FROM_HERE
,
194 base::TimeDelta::FromMilliseconds(
195 kResendDelayOnHostNotFoundMs
),
197 &HeartbeatSender::ResendStanza
);
200 on_unknown_host_id_error_
.Run();
205 LOG(ERROR
) << "Received error in response to heartbeat: "
210 // This method must only be called for error or result stanzas.
211 DCHECK_EQ(std::string(buzz::STR_RESULT
), type
);
213 const XmlElement
* result_element
=
214 response
->FirstNamed(QName(kChromotingXmlNamespace
, kHeartbeatResultTag
));
215 if (result_element
) {
216 const XmlElement
* set_interval_element
=
217 result_element
->FirstNamed(QName(kChromotingXmlNamespace
,
219 if (set_interval_element
) {
220 const std::string
& interval_str
= set_interval_element
->BodyText();
222 if (!base::StringToInt(interval_str
, &interval
) || interval
<= 0) {
223 LOG(ERROR
) << "Received invalid set-interval: "
224 << set_interval_element
->Str();
226 SetInterval(interval
* base::Time::kMillisecondsPerSecond
);
230 bool did_set_sequence_id
= false;
231 const XmlElement
* expected_sequence_id_element
=
232 result_element
->FirstNamed(QName(kChromotingXmlNamespace
,
233 kExpectedSequenceIdTag
));
234 if (expected_sequence_id_element
) {
235 // The sequence ID sent in the previous heartbeat was not what the server
236 // expected, so send another heartbeat with the expected sequence ID.
237 const std::string
& expected_sequence_id_str
=
238 expected_sequence_id_element
->BodyText();
239 int expected_sequence_id
;
240 if (!base::StringToInt(expected_sequence_id_str
, &expected_sequence_id
)) {
241 LOG(ERROR
) << "Received invalid " << kExpectedSequenceIdTag
<< ": " <<
242 expected_sequence_id_element
->Str();
244 SetSequenceId(expected_sequence_id
);
245 sequence_id_recent_set_num_
++;
246 did_set_sequence_id
= true;
249 if (!did_set_sequence_id
) {
250 // It seems the bot accepted our signature and our message.
251 sequence_id_recent_set_num_
= 0;
253 // Notify listener of the first successful heartbeat.
254 if (!heartbeat_succeeded_
) {
255 on_heartbeat_successful_callback_
.Run();
257 heartbeat_succeeded_
= true;
259 // Notify caller of SetHostOfflineReason that we got an ack.
260 if (is_offline_heartbeat_response
) {
261 OnHostOfflineReasonAck();
267 void HeartbeatSender::SetInterval(int interval
) {
268 if (interval
!= interval_ms_
) {
269 interval_ms_
= interval
;
271 // Restart the timer with the new interval.
272 if (timer_
.IsRunning()) {
274 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(interval_ms_
),
275 this, &HeartbeatSender::SendStanza
);
280 void HeartbeatSender::SetSequenceId(int sequence_id
) {
281 sequence_id_
= sequence_id
;
282 // Setting the sequence ID may be a symptom of a temporary server-side
283 // problem, which would affect many hosts, so don't send a new heartbeat
284 // immediately, as many hosts doing so may overload the server.
285 // But the server will usually set the sequence ID when it receives the first
286 // heartbeat from a host. In that case, we can send a new heartbeat
287 // immediately, as that only happens once per host instance.
288 if (!sequence_id_was_set_
) {
291 HOST_LOG
<< "The heartbeat sequence ID has been set more than once: "
292 << "the new value is " << sequence_id
;
293 double delay
= pow(2.0, sequence_id_recent_set_num_
) *
294 (1 + base::RandDouble()) * kResendDelayMs
;
295 if (delay
<= interval_ms_
) {
296 timer_resend_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(delay
),
297 this, &HeartbeatSender::ResendStanza
);
300 sequence_id_was_set_
= true;
303 scoped_ptr
<XmlElement
> HeartbeatSender::CreateHeartbeatMessage() {
304 // Create heartbeat stanza.
305 scoped_ptr
<XmlElement
> heartbeat(new XmlElement(
306 QName(kChromotingXmlNamespace
, kHeartbeatQueryTag
)));
307 heartbeat
->AddAttr(QName(kChromotingXmlNamespace
, kHostIdAttr
), host_id_
);
308 heartbeat
->AddAttr(QName(kChromotingXmlNamespace
, kSequenceIdAttr
),
309 base::IntToString(sequence_id_
));
310 if (!host_offline_reason_
.empty()) {
312 QName(kChromotingXmlNamespace
, kHostOfflineReasonAttr
),
313 host_offline_reason_
);
315 heartbeat
->AddElement(CreateSignature().release());
316 // Append host version.
317 scoped_ptr
<XmlElement
> version_tag(new XmlElement(
318 QName(kChromotingXmlNamespace
, kHostVersionTag
)));
319 version_tag
->AddText(STRINGIZE(VERSION
));
320 heartbeat
->AddElement(version_tag
.release());
321 // Append log message (which isn't signed).
322 scoped_ptr
<XmlElement
> log(ServerLogEntry::MakeStanza());
323 scoped_ptr
<ServerLogEntry
> log_entry(MakeLogEntryForHeartbeat());
324 AddHostFieldsToLogEntry(log_entry
.get());
325 log
->AddElement(log_entry
->ToStanza().release());
326 heartbeat
->AddElement(log
.release());
327 return heartbeat
.Pass();
330 scoped_ptr
<XmlElement
> HeartbeatSender::CreateSignature() {
331 scoped_ptr
<XmlElement
> signature_tag(new XmlElement(
332 QName(kChromotingXmlNamespace
, kHeartbeatSignatureTag
)));
334 std::string message
= signal_strategy_
->GetLocalJid() + ' ' +
335 base::IntToString(sequence_id_
);
336 std::string
signature(host_key_pair_
->SignMessage(message
));
337 signature_tag
->AddText(signature
);
339 return signature_tag
.Pass();
342 } // namespace remoting