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.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 23using buzz::QName; 24using buzz::XmlElement; 25 26namespace remoting { 27 28namespace { 29 30const char kHeartbeatQueryTag[] = "heartbeat"; 31const char kHostIdAttr[] = "hostid"; 32const char kHostVersionTag[] = "host-version"; 33const char kHeartbeatSignatureTag[] = "signature"; 34const char kSequenceIdAttr[] = "sequence-id"; 35 36const char kErrorTag[] = "error"; 37const char kNotFoundTag[] = "item-not-found"; 38 39const char kHeartbeatResultTag[] = "heartbeat-result"; 40const char kSetIntervalTag[] = "set-interval"; 41const char kExpectedSequenceIdTag[] = "expected-sequence-id"; 42 43const int64 kDefaultHeartbeatIntervalMs = 5 * 60 * 1000; // 5 minutes. 44const int64 kResendDelayMs = 10 * 1000; // 10 seconds. 45const int64 kResendDelayOnHostNotFoundMs = 10 * 1000; // 10 seconds. 46const int kMaxResendOnHostNotFoundCount = 12; // 2 minutes (12 x 10 seconds). 47 48} // namespace 49 50HeartbeatSender::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 76HeartbeatSender::~HeartbeatSender() { 77 signal_strategy_->RemoveListener(this); 78} 79 80void 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 94bool HeartbeatSender::OnSignalStrategyIncomingStanza( 95 const buzz::XmlElement* stanza) { 96 return false; 97} 98 99void 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 106void 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 113void 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 122void 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 207void 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 220void 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 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); 238 } 239 } 240 sequence_id_was_set_ = true; 241} 242 243scoped_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 265scoped_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 278