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