Upstream version 10.39.225.0
[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/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
24 using buzz::QName;
25 using buzz::XmlElement;
26
27 namespace remoting {
28
29 namespace {
30
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
37 const char kErrorTag[] = "error";
38 const char kNotFoundTag[] = "item-not-found";
39
40 const char kHeartbeatResultTag[] = "heartbeat-result";
41 const char kSetIntervalTag[] = "set-interval";
42 const char kExpectedSequenceIdTag[] = "expected-sequence-id";
43
44 const int64 kDefaultHeartbeatIntervalMs = 5 * 60 * 1000;  // 5 minutes.
45 const int64 kResendDelayMs = 10 * 1000;  // 10 seconds.
46 const int64 kResendDelayOnHostNotFoundMs = 10 * 1000; // 10 seconds.
47 const int kMaxResendOnHostNotFoundCount = 12;  // 2 minutes (12 x 10 seconds).
48
49 }  // namespace
50
51 HeartbeatSender::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
77 HeartbeatSender::~HeartbeatSender() {
78   signal_strategy_->RemoveListener(this);
79 }
80
81 void 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
95 bool HeartbeatSender::OnSignalStrategyIncomingStanza(
96     const buzz::XmlElement* stanza) {
97   return false;
98 }
99
100 void 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
107 void 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
114 void 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
123 void 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
208 void 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
221 void 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
244 scoped_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
266 scoped_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