GN + Android: extract android_standalone_library rule.
[chromium-blink-merge.git] / remoting / host / heartbeat_sender.cc
blobc64f2f49cf1c6b536afa13481ebde83f6dea8882
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"
7 #include <math.h>
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/webrtc/libjingle/xmllite/xmlelement.h"
22 #include "third_party/webrtc/libjingle/xmpp/constants.h"
24 using buzz::QName;
25 using buzz::XmlElement;
27 namespace remoting {
29 namespace {
31 const char kHeartbeatQueryTag[] = "heartbeat";
32 const char kHostIdAttr[] = "hostid";
33 const char kHostVersionTag[] = "host-version";
34 const char kHeartbeatSignatureTag[] = "signature";
35 const char kSequenceIdAttr[] = "sequence-id";
36 const char kHostOfflineReasonAttr[] = "host-offline-reason";
38 const char kErrorTag[] = "error";
39 const char kNotFoundTag[] = "item-not-found";
41 const char kHeartbeatResultTag[] = "heartbeat-result";
42 const char kSetIntervalTag[] = "set-interval";
43 const char kExpectedSequenceIdTag[] = "expected-sequence-id";
45 const int64 kDefaultHeartbeatIntervalMs = 5 * 60 * 1000; // 5 minutes.
46 const int64 kResendDelayMs = 10 * 1000; // 10 seconds.
47 const int64 kResendDelayOnHostNotFoundMs = 10 * 1000; // 10 seconds.
48 const int kMaxResendOnHostNotFoundCount = 12; // 2 minutes (12 x 10 seconds).
50 } // namespace
52 HeartbeatSender::HeartbeatSender(
53 const base::Closure& on_heartbeat_successful_callback,
54 const base::Closure& on_unknown_host_id_error,
55 const std::string& host_id,
56 SignalStrategy* signal_strategy,
57 scoped_refptr<RsaKeyPair> key_pair,
58 const std::string& directory_bot_jid)
59 : on_heartbeat_successful_callback_(on_heartbeat_successful_callback),
60 on_unknown_host_id_error_(on_unknown_host_id_error),
61 host_id_(host_id),
62 signal_strategy_(signal_strategy),
63 key_pair_(key_pair),
64 directory_bot_jid_(directory_bot_jid),
65 interval_ms_(kDefaultHeartbeatIntervalMs),
66 sequence_id_(0),
67 sequence_id_was_set_(false),
68 sequence_id_recent_set_num_(0),
69 heartbeat_succeeded_(false),
70 failed_startup_heartbeat_count_(0) {
71 DCHECK(signal_strategy_);
72 DCHECK(key_pair_.get());
73 DCHECK(thread_checker_.CalledOnValidThread());
75 signal_strategy_->AddListener(this);
77 // Start heartbeats if the |signal_strategy_| is already connected.
78 OnSignalStrategyStateChange(signal_strategy_->GetState());
81 HeartbeatSender::~HeartbeatSender() {
82 signal_strategy_->RemoveListener(this);
85 void HeartbeatSender::OnSignalStrategyStateChange(SignalStrategy::State state) {
86 DCHECK(thread_checker_.CalledOnValidThread());
87 if (state == SignalStrategy::CONNECTED) {
88 iq_sender_.reset(new IqSender(signal_strategy_));
89 SendStanza();
90 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(interval_ms_),
91 this, &HeartbeatSender::SendStanza);
92 } else if (state == SignalStrategy::DISCONNECTED) {
93 request_.reset();
94 iq_sender_.reset();
95 timer_.Stop();
96 timer_resend_.Stop();
100 bool HeartbeatSender::OnSignalStrategyIncomingStanza(
101 const buzz::XmlElement* stanza) {
102 return false;
105 void HeartbeatSender::SetHostOfflineReason(
106 const std::string& host_offline_reason,
107 const base::Closure& ack_callback) {
108 DCHECK(thread_checker_.CalledOnValidThread());
109 DCHECK(host_offline_reason_ack_callback_.is_null());
110 host_offline_reason_ = host_offline_reason;
111 host_offline_reason_ack_callback_ = ack_callback;
112 if (signal_strategy_->GetState() == SignalStrategy::CONNECTED) {
113 DoSendStanza();
117 void HeartbeatSender::SendStanza() {
118 // Make sure we don't send another heartbeat before the heartbeat interval
119 // has expired.
120 timer_resend_.Stop();
121 DoSendStanza();
124 void HeartbeatSender::ResendStanza() {
125 // Make sure we don't send another heartbeat before the heartbeat interval
126 // has expired.
127 timer_.Reset();
128 DoSendStanza();
131 void HeartbeatSender::DoSendStanza() {
132 DCHECK(thread_checker_.CalledOnValidThread());
133 DCHECK(signal_strategy_->GetState() == SignalStrategy::CONNECTED);
134 VLOG(1) << "Sending heartbeat stanza to " << directory_bot_jid_;
136 request_ = iq_sender_->SendIq(
137 buzz::STR_SET, directory_bot_jid_, CreateHeartbeatMessage(),
138 base::Bind(&HeartbeatSender::ProcessResponse,
139 base::Unretained(this),
140 !host_offline_reason_.empty()));
141 ++sequence_id_;
144 void HeartbeatSender::ProcessResponse(
145 bool is_offline_heartbeat_response,
146 IqRequest* request,
147 const XmlElement* response) {
148 DCHECK(thread_checker_.CalledOnValidThread());
150 std::string type = response->Attr(buzz::QN_TYPE);
151 if (type == buzz::STR_ERROR) {
152 const XmlElement* error_element =
153 response->FirstNamed(QName(buzz::NS_CLIENT, kErrorTag));
154 if (error_element) {
155 if (error_element->FirstNamed(QName(buzz::NS_STANZA, kNotFoundTag))) {
156 LOG(ERROR) << "Received error: Host ID not found";
157 // If the host was registered immediately before it sends a heartbeat,
158 // then server-side latency may prevent the server recognizing the
159 // host ID in the heartbeat. So even if all of the first few heartbeats
160 // get a "host ID not found" error, that's not a good enough reason to
161 // exit.
162 failed_startup_heartbeat_count_++;
163 if (!heartbeat_succeeded_ && (failed_startup_heartbeat_count_ <=
164 kMaxResendOnHostNotFoundCount)) {
165 timer_resend_.Start(FROM_HERE,
166 base::TimeDelta::FromMilliseconds(
167 kResendDelayOnHostNotFoundMs),
168 this,
169 &HeartbeatSender::ResendStanza);
170 return;
172 on_unknown_host_id_error_.Run();
173 return;
177 LOG(ERROR) << "Received error in response to heartbeat: "
178 << response->Str();
179 return;
182 // This method must only be called for error or result stanzas.
183 DCHECK_EQ(std::string(buzz::STR_RESULT), type);
185 const XmlElement* result_element =
186 response->FirstNamed(QName(kChromotingXmlNamespace, kHeartbeatResultTag));
187 if (result_element) {
188 const XmlElement* set_interval_element =
189 result_element->FirstNamed(QName(kChromotingXmlNamespace,
190 kSetIntervalTag));
191 if (set_interval_element) {
192 const std::string& interval_str = set_interval_element->BodyText();
193 int interval;
194 if (!base::StringToInt(interval_str, &interval) || interval <= 0) {
195 LOG(ERROR) << "Received invalid set-interval: "
196 << set_interval_element->Str();
197 } else {
198 SetInterval(interval * base::Time::kMillisecondsPerSecond);
202 bool did_set_sequence_id = false;
203 const XmlElement* expected_sequence_id_element =
204 result_element->FirstNamed(QName(kChromotingXmlNamespace,
205 kExpectedSequenceIdTag));
206 if (expected_sequence_id_element) {
207 // The sequence ID sent in the previous heartbeat was not what the server
208 // expected, so send another heartbeat with the expected sequence ID.
209 const std::string& expected_sequence_id_str =
210 expected_sequence_id_element->BodyText();
211 int expected_sequence_id;
212 if (!base::StringToInt(expected_sequence_id_str, &expected_sequence_id)) {
213 LOG(ERROR) << "Received invalid " << kExpectedSequenceIdTag << ": " <<
214 expected_sequence_id_element->Str();
215 } else {
216 SetSequenceId(expected_sequence_id);
217 sequence_id_recent_set_num_++;
218 did_set_sequence_id = true;
221 if (!did_set_sequence_id) {
222 // It seems the bot accepted our signature and our message.
223 sequence_id_recent_set_num_ = 0;
225 // Notify listener of the first successful heartbeat.
226 if (!heartbeat_succeeded_) {
227 on_heartbeat_successful_callback_.Run();
229 heartbeat_succeeded_ = true;
231 // Notify caller of SetHostOfflineReason that we got an ack.
232 if (is_offline_heartbeat_response) {
233 if (!host_offline_reason_ack_callback_.is_null()) {
234 base::MessageLoop::current()->PostTask(
235 FROM_HERE,
236 host_offline_reason_ack_callback_);
237 host_offline_reason_ack_callback_.Reset();
244 void HeartbeatSender::SetInterval(int interval) {
245 if (interval != interval_ms_) {
246 interval_ms_ = interval;
248 // Restart the timer with the new interval.
249 if (timer_.IsRunning()) {
250 timer_.Stop();
251 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(interval_ms_),
252 this, &HeartbeatSender::SendStanza);
257 void HeartbeatSender::SetSequenceId(int sequence_id) {
258 sequence_id_ = sequence_id;
259 // Setting the sequence ID may be a symptom of a temporary server-side
260 // problem, which would affect many hosts, so don't send a new heartbeat
261 // immediately, as many hosts doing so may overload the server.
262 // But the server will usually set the sequence ID when it receives the first
263 // heartbeat from a host. In that case, we can send a new heartbeat
264 // immediately, as that only happens once per host instance.
265 if (!sequence_id_was_set_) {
266 ResendStanza();
267 } else {
268 HOST_LOG << "The heartbeat sequence ID has been set more than once: "
269 << "the new value is " << sequence_id;
270 double delay = pow(2.0, sequence_id_recent_set_num_) *
271 (1 + base::RandDouble()) * kResendDelayMs;
272 if (delay <= interval_ms_) {
273 timer_resend_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay),
274 this, &HeartbeatSender::ResendStanza);
277 sequence_id_was_set_ = true;
280 scoped_ptr<XmlElement> HeartbeatSender::CreateHeartbeatMessage() {
281 // Create heartbeat stanza.
282 scoped_ptr<XmlElement> heartbeat(new XmlElement(
283 QName(kChromotingXmlNamespace, kHeartbeatQueryTag)));
284 heartbeat->AddAttr(QName(kChromotingXmlNamespace, kHostIdAttr), host_id_);
285 heartbeat->AddAttr(QName(kChromotingXmlNamespace, kSequenceIdAttr),
286 base::IntToString(sequence_id_));
287 if (!host_offline_reason_.empty()) {
288 heartbeat->AddAttr(
289 QName(kChromotingXmlNamespace, kHostOfflineReasonAttr),
290 host_offline_reason_);
292 heartbeat->AddElement(CreateSignature().release());
293 // Append host version.
294 scoped_ptr<XmlElement> version_tag(new XmlElement(
295 QName(kChromotingXmlNamespace, kHostVersionTag)));
296 version_tag->AddText(STRINGIZE(VERSION));
297 heartbeat->AddElement(version_tag.release());
298 // Append log message (which isn't signed).
299 scoped_ptr<XmlElement> log(ServerLogEntry::MakeStanza());
300 scoped_ptr<ServerLogEntry> log_entry(MakeLogEntryForHeartbeat());
301 AddHostFieldsToLogEntry(log_entry.get());
302 log->AddElement(log_entry->ToStanza().release());
303 heartbeat->AddElement(log.release());
304 return heartbeat.Pass();
307 scoped_ptr<XmlElement> HeartbeatSender::CreateSignature() {
308 scoped_ptr<XmlElement> signature_tag(new XmlElement(
309 QName(kChromotingXmlNamespace, kHeartbeatSignatureTag)));
311 std::string message = signal_strategy_->GetLocalJid() + ' ' +
312 base::IntToString(sequence_id_);
313 std::string signature(key_pair_->SignMessage(message));
314 signature_tag->AddText(signature);
316 return signature_tag.Pass();
319 } // namespace remoting