Home | History | Annotate | Download | only in host
      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/jingle_glue/iq_sender.h"
     19 #include "remoting/jingle_glue/server_log_entry.h"
     20 #include "remoting/jingle_glue/signal_strategy.h"
     21 #include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
     22 #include "third_party/libjingle/source/talk/xmpp/constants.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
    279