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/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"
24 using buzz::XmlElement
;
30 const char kHeartbeatQueryTag
[] = "heartbeat";
31 const char kHostIdAttr
[] = "hostid";
32 const char kHostVersionTag
[] = "host-version";
33 const char kHeartbeatSignatureTag
[] = "signature";
34 const char kSequenceIdAttr
[] = "sequence-id";
36 const char kErrorTag
[] = "error";
37 const char kNotFoundTag
[] = "item-not-found";
39 const char kHeartbeatResultTag
[] = "heartbeat-result";
40 const char kSetIntervalTag
[] = "set-interval";
41 const char kExpectedSequenceIdTag
[] = "expected-sequence-id";
43 const int64 kDefaultHeartbeatIntervalMs
= 5 * 60 * 1000; // 5 minutes.
44 const int64 kResendDelayMs
= 10 * 1000; // 10 seconds.
45 const int64 kResendDelayOnHostNotFoundMs
= 10 * 1000; // 10 seconds.
46 const int kMaxResendOnHostNotFoundCount
= 12; // 2 minutes (12 x 10 seconds).
50 HeartbeatSender::HeartbeatSender(
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
),
58 signal_strategy_(signal_strategy
),
60 directory_bot_jid_(directory_bot_jid
),
61 interval_ms_(kDefaultHeartbeatIntervalMs
),
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());
70 signal_strategy_
->AddListener(this);
72 // Start heartbeats if the |signal_strategy_| is already connected.
73 OnSignalStrategyStateChange(signal_strategy_
->GetState());
76 HeartbeatSender::~HeartbeatSender() {
77 signal_strategy_
->RemoveListener(this);
80 void HeartbeatSender::OnSignalStrategyStateChange(SignalStrategy::State state
) {
81 if (state
== SignalStrategy::CONNECTED
) {
82 iq_sender_
.reset(new IqSender(signal_strategy_
));
84 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(interval_ms_
),
85 this, &HeartbeatSender::SendStanza
);
86 } else if (state
== SignalStrategy::DISCONNECTED
) {
94 bool HeartbeatSender::OnSignalStrategyIncomingStanza(
95 const buzz::XmlElement
* stanza
) {
99 void HeartbeatSender::SendStanza() {
101 // Make sure we don't send another heartbeat before the heartbeat interval
103 timer_resend_
.Stop();
106 void HeartbeatSender::ResendStanza() {
108 // Make sure we don't send another heartbeat before the heartbeat interval
113 void 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)));
122 void 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
));
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
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
),
143 &HeartbeatSender::ResendStanza
);
146 listener_
->OnUnknownHostIdError();
151 LOG(ERROR
) << "Received error in response to heartbeat: "
156 // Notify listener of the first successful heartbeat.
157 if (!heartbeat_succeeded_
) {
158 listener_
->OnHeartbeatSuccessful();
160 heartbeat_succeeded_
= true;
162 // This method must only be called for error or result stanzas.
163 DCHECK_EQ(std::string(buzz::STR_RESULT
), type
);
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
,
171 if (set_interval_element
) {
172 const std::string
& interval_str
= set_interval_element
->BodyText();
174 if (!base::StringToInt(interval_str
, &interval
) || interval
<= 0) {
175 LOG(ERROR
) << "Received invalid set-interval: "
176 << set_interval_element
->Str();
178 SetInterval(interval
* base::Time::kMillisecondsPerSecond
);
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();
196 SetSequenceId(expected_sequence_id
);
197 sequence_id_recent_set_num_
++;
198 did_set_sequence_id
= true;
201 if (!did_set_sequence_id
) {
202 sequence_id_recent_set_num_
= 0;
207 void HeartbeatSender::SetInterval(int interval
) {
208 if (interval
!= interval_ms_
) {
209 interval_ms_
= interval
;
211 // Restart the timer with the new interval.
212 if (timer_
.IsRunning()) {
214 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(interval_ms_
),
215 this, &HeartbeatSender::SendStanza
);
220 void 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_
) {
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
);
240 sequence_id_was_set_
= true;
243 scoped_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();
265 scoped_ptr
<XmlElement
> HeartbeatSender::CreateSignature() {
266 scoped_ptr
<XmlElement
> signature_tag(new XmlElement(
267 QName(kChromotingXmlNamespace
, kHeartbeatSignatureTag
)));
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
);
274 return signature_tag
.Pass();
277 } // namespace remoting