- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / host / heartbeat_sender.cc
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/logging.h"
11 #include "base/message_loop/message_loop_proxy.h"
12 #include "base/rand_util.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringize_macros.h"
15 #include "base/time/time.h"
16 #include "remoting/base/constants.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
23 using buzz::QName;
24 using buzz::XmlElement;
25
26 namespace remoting {
27
28 namespace {
29
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";
35
36 const char kErrorTag[] = "error";
37 const char kNotFoundTag[] = "item-not-found";
38
39 const char kHeartbeatResultTag[] = "heartbeat-result";
40 const char kSetIntervalTag[] = "set-interval";
41 const char kExpectedSequenceIdTag[] = "expected-sequence-id";
42
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).
47
48 }  // namespace
49
50 HeartbeatSender::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
76 HeartbeatSender::~HeartbeatSender() {
77   signal_strategy_->RemoveListener(this);
78 }
79
80 void 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
94 bool HeartbeatSender::OnSignalStrategyIncomingStanza(
95     const buzz::XmlElement* stanza) {
96   return false;
97 }
98
99 void 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
106 void 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
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)));
119   ++sequence_id_;
120 }
121
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));
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
207 void 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
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_) {
229     ResendStanza();
230   } else {
231     LOG(INFO) << "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
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();
263 }
264
265 scoped_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