C++程序  |  151行  |  5.33 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef REMOTING_HOST_HEARTBEAT_SENDER_H_
#define REMOTING_HOST_HEARTBEAT_SENDER_H_

#include <string>

#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/timer/timer.h"
#include "remoting/base/rsa_key_pair.h"
#include "remoting/signaling/signal_strategy.h"

namespace base {
class MessageLoopProxy;
}  // namespace base

namespace buzz {
class XmlElement;
}  // namespace buzz

namespace remoting {

class RsaKeyPair;
class IqRequest;
class IqSender;

// HeartbeatSender periodically sends heartbeat stanzas to the Chromoting Bot.
// Each heartbeat stanza looks as follows:
//
// <iq type="set" to="remoting@bot.talk.google.com"
//     from="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
//   <rem:heartbeat rem:hostid="a1ddb11e-8aef-11df-bccf-18a905b9cb5a"
//                  rem:sequence-id="456"
//                  xmlns:rem="google:remoting">
//     <rem:signature>.signature.</rem:signature>
//   </rem:heartbeat>
// </iq>
//
// The sequence-id attribute of the heartbeat is a zero-based incrementally
// increasing integer unique to each heartbeat from a single host.
// The Bot checks the value, and if it is incorrect, includes the
// correct value in the result stanza. The host should then send another
// heartbeat, with the correct sequence-id, and increment the sequence-id in
// susbequent heartbeats.
// The signature is a base-64 encoded SHA-1 hash, signed with the host's
// private RSA key. The message being signed is the full Jid concatenated with
// the sequence-id, separated by one space. For example, for the heartbeat
// stanza above, the message that is signed is
// "user@gmail.com/chromoting123123 456".
//
// The Bot sends the following result stanza in response to each successful
// heartbeat:
//
//  <iq type="set" from="remoting@bot.talk.google.com"
//      to="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
//    <rem:heartbeat-result xmlns:rem="google:remoting">
//      <rem:set-interval>300</rem:set-interval>
//    </rem:heartbeat-result>
//  </iq>
//
// The set-interval tag is used to specify desired heartbeat interval
// in seconds. The heartbeat-result and the set-interval tags are
// optional. Host uses default heartbeat interval if it doesn't find
// set-interval tag in the result Iq stanza it receives from the
// server.
// If the heartbeat's sequence-id was incorrect, the Bot sends a result
// stanza of this form:
//
//  <iq type="set" from="remoting@bot.talk.google.com"
//      to="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
//    <rem:heartbeat-result xmlns:rem="google:remoting">
//      <rem:expected-sequence-id>654</rem:expected-sequence-id>
//    </rem:heartbeat-result>
//  </iq>
class HeartbeatSender : public SignalStrategy::Listener {
 public:
  class Listener {
   public:
    virtual ~Listener() { }

    // Invoked after the first successful heartbeat.
    virtual void OnHeartbeatSuccessful() = 0;

    // Invoked when the host ID is permanently not recognized by the server.
    virtual void OnUnknownHostIdError() = 0;
  };

  // |signal_strategy| and |delegate| must outlive this
  // object. Heartbeats will start when the supplied SignalStrategy
  // enters the CONNECTED state.
  HeartbeatSender(Listener* listener,
                  const std::string& host_id,
                  SignalStrategy* signal_strategy,
                  scoped_refptr<RsaKeyPair> key_pair,
                  const std::string& directory_bot_jid);
  virtual ~HeartbeatSender();

  // SignalStrategy::Listener interface.
  virtual void OnSignalStrategyStateChange(
      SignalStrategy::State state) OVERRIDE;
  virtual bool OnSignalStrategyIncomingStanza(
      const buzz::XmlElement* stanza) OVERRIDE;

 private:
  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, DoSendStanza);
  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest,
                           DoSendStanzaWithExpectedSequenceId);
  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, CreateHeartbeatMessage);
  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, ProcessResponseSetInterval);
  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest,
                           ProcessResponseExpectedSequenceId);

  void SendStanza();
  void ResendStanza();
  void DoSendStanza();
  void ProcessResponse(IqRequest* request, const buzz::XmlElement* response);
  void SetInterval(int interval);
  void SetSequenceId(int sequence_id);

  // Helper methods used by DoSendStanza() to generate heartbeat stanzas.
  scoped_ptr<buzz::XmlElement> CreateHeartbeatMessage();
  scoped_ptr<buzz::XmlElement> CreateSignature();

  Listener* listener_;
  std::string host_id_;
  SignalStrategy* signal_strategy_;
  scoped_refptr<RsaKeyPair> key_pair_;
  std::string directory_bot_jid_;
  scoped_ptr<IqSender> iq_sender_;
  scoped_ptr<IqRequest> request_;
  int interval_ms_;
  base::RepeatingTimer<HeartbeatSender> timer_;
  base::OneShotTimer<HeartbeatSender> timer_resend_;
  int sequence_id_;
  bool sequence_id_was_set_;
  int sequence_id_recent_set_num_;
  bool heartbeat_succeeded_;
  int failed_startup_heartbeat_count_;

  DISALLOW_COPY_AND_ASSIGN(HeartbeatSender);
};

}  // namespace remoting

#endif  // REMOTING_HOST_HEARTBEAT_SENDER_H_