// 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 #include "base/callback.h" #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/threading/thread_checker.h" #include "base/timer/timer.h" #include "remoting/base/rsa_key_pair.h" #include "remoting/signaling/signal_strategy.h" namespace base { class TimeDelta; } // 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: // // // // .signature. // // // // Normally the heartbeat indicates that the host is healthy and ready to // accept new connections from a client, but the rem:heartbeat xml element can // optionally include a rem:host-offline-reason attribute, which indicates that // the host cannot accept connections from the client (and might possibly be // shutting down). The value of the host-offline-reason attribute can be either // a string from host_exit_codes.cc (i.e. "INVALID_HOST_CONFIGURATION" string) // or one of kHostOfflineReasonXxx constants (i.e. "POLICY_READ_ERROR" string). // // 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: // // // // 300 // // // // 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: // // // // 654 // // class HeartbeatSender : public SignalStrategy::Listener { public: // |signal_strategy| and |delegate| must outlive this // object. Heartbeats will start when the supplied SignalStrategy // enters the CONNECTED state. // // |on_heartbeat_successful_callback| is invoked after the first successful // heartbeat. // // |on_unknown_host_id_error| is invoked when the host ID is permanently not // recognized by the server. HeartbeatSender(const base::Closure& on_heartbeat_successful_callback, const base::Closure& on_unknown_host_id_error, const std::string& host_id, SignalStrategy* signal_strategy, const scoped_refptr& host_key_pair, const std::string& directory_bot_jid); ~HeartbeatSender() override; // Sets host offline reason for future heartbeat stanzas, and initiates // sending a stanza right away. // // For discussion of allowed values for |host_offline_reason| argument, // please see the description of rem:host-offline-reason xml attribute in // the class-level comments above. // // |ack_callback| will be called once, when the bot acks receiving the // |host_offline_reason| or when |timeout| is reached. void SetHostOfflineReason( const std::string& host_offline_reason, const base::TimeDelta& timeout, const base::Callback& ack_callback); // SignalStrategy::Listener interface. void OnSignalStrategyStateChange(SignalStrategy::State state) override; bool OnSignalStrategyIncomingStanza(const buzz::XmlElement* stanza) override; private: FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, DoSendStanzaWithExpectedSequenceId); FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, ProcessResponseSetInterval); FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, ProcessResponseExpectedSequenceId); friend class HeartbeatSenderTest; void SendStanza(); void ResendStanza(); void DoSendStanza(); void ProcessResponse(bool is_offline_heartbeat_response, IqRequest* request, const buzz::XmlElement* response); void SetInterval(int interval); void SetSequenceId(int sequence_id); // Handlers for host-offline-reason completion and timeout. void OnHostOfflineReasonTimeout(); void OnHostOfflineReasonAck(); // Helper methods used by DoSendStanza() to generate heartbeat stanzas. scoped_ptr CreateHeartbeatMessage(); scoped_ptr CreateSignature(); base::Closure on_heartbeat_successful_callback_; base::Closure on_unknown_host_id_error_; std::string host_id_; SignalStrategy* signal_strategy_; scoped_refptr host_key_pair_; std::string directory_bot_jid_; scoped_ptr iq_sender_; scoped_ptr request_; int interval_ms_; base::RepeatingTimer timer_; base::OneShotTimer timer_resend_; int sequence_id_; bool sequence_id_was_set_; int sequence_id_recent_set_num_; bool heartbeat_succeeded_; int failed_startup_heartbeat_count_; // Fields to send and indicate completion of sending host-offline-reason. std::string host_offline_reason_; base::Callback host_offline_reason_ack_callback_; base::OneShotTimer host_offline_reason_timeout_timer_; base::ThreadChecker thread_checker_; DISALLOW_COPY_AND_ASSIGN(HeartbeatSender); }; } // namespace remoting #endif // REMOTING_HOST_HEARTBEAT_SENDER_H_