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.
5 #ifndef REMOTING_HOST_HEARTBEAT_SENDER_H_
6 #define REMOTING_HOST_HEARTBEAT_SENDER_H_
10 #include "base/callback.h"
11 #include "base/compiler_specific.h"
12 #include "base/gtest_prod_util.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/threading/thread_checker.h"
16 #include "base/timer/timer.h"
17 #include "remoting/base/rsa_key_pair.h"
18 #include "remoting/signaling/signal_strategy.h"
21 class MessageLoopProxy
;
34 // HeartbeatSender periodically sends heartbeat stanzas to the Chromoting Bot.
35 // Each heartbeat stanza looks as follows:
37 // <iq type="set" to="remoting@bot.talk.google.com"
38 // from="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
39 // <rem:heartbeat rem:hostid="a1ddb11e-8aef-11df-bccf-18a905b9cb5a"
40 // rem:sequence-id="456"
41 // xmlns:rem="google:remoting">
42 // <rem:signature>.signature.</rem:signature>
46 // Normally the heartbeat indicates that the host is healthy and ready to
47 // accept new connections from a client, but the rem:heartbeat xml element can
48 // optionally include a rem:host-offline-reason attribute, which indicates that
49 // the host cannot accept connections from the client (and might possibly be
50 // shutting down). The value of the host-offline-reason attribute can be a
51 // string from host_exit_codes.cc (i.e. "INVALID_HOST_CONFIGURATION" string).
53 // The sequence-id attribute of the heartbeat is a zero-based incrementally
54 // increasing integer unique to each heartbeat from a single host.
55 // The Bot checks the value, and if it is incorrect, includes the
56 // correct value in the result stanza. The host should then send another
57 // heartbeat, with the correct sequence-id, and increment the sequence-id in
58 // susbequent heartbeats.
59 // The signature is a base-64 encoded SHA-1 hash, signed with the host's
60 // private RSA key. The message being signed is the full Jid concatenated with
61 // the sequence-id, separated by one space. For example, for the heartbeat
62 // stanza above, the message that is signed is
63 // "user@gmail.com/chromoting123123 456".
65 // The Bot sends the following result stanza in response to each successful
68 // <iq type="set" from="remoting@bot.talk.google.com"
69 // to="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
70 // <rem:heartbeat-result xmlns:rem="google:remoting">
71 // <rem:set-interval>300</rem:set-interval>
72 // </rem:heartbeat-result>
75 // The set-interval tag is used to specify desired heartbeat interval
76 // in seconds. The heartbeat-result and the set-interval tags are
77 // optional. Host uses default heartbeat interval if it doesn't find
78 // set-interval tag in the result Iq stanza it receives from the
80 // If the heartbeat's sequence-id was incorrect, the Bot sends a result
81 // stanza of this form:
83 // <iq type="set" from="remoting@bot.talk.google.com"
84 // to="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
85 // <rem:heartbeat-result xmlns:rem="google:remoting">
86 // <rem:expected-sequence-id>654</rem:expected-sequence-id>
87 // </rem:heartbeat-result>
89 class HeartbeatSender
: public SignalStrategy::Listener
{
91 // |signal_strategy| and |delegate| must outlive this
92 // object. Heartbeats will start when the supplied SignalStrategy
93 // enters the CONNECTED state.
94 HeartbeatSender(const base::Closure
& on_heartbeat_successful_callback
,
95 const base::Closure
& on_unknown_host_id_error
,
96 const std::string
& host_id
,
97 SignalStrategy
* signal_strategy
,
98 scoped_refptr
<RsaKeyPair
> key_pair
,
99 const std::string
& directory_bot_jid
);
100 ~HeartbeatSender() override
;
102 // Sets host offline reason for future heartbeat stanzas,
103 // as well as intiates sending a stanza right away.
105 // See rem:host-offline-reason class-level comments for discussion
106 // of allowed values for |host_offline_reason| string.
108 // |ack_callback| will be called once, when the bot acks
109 // receiving the |host_offline_reason|.
110 void SetHostOfflineReason(
111 const std::string
& host_offline_reason
,
112 const base::Closure
& ack_callback
);
114 // SignalStrategy::Listener interface.
115 void OnSignalStrategyStateChange(SignalStrategy::State state
) override
;
116 bool OnSignalStrategyIncomingStanza(const buzz::XmlElement
* stanza
) override
;
119 FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest
,
120 DoSendStanzaWithExpectedSequenceId
);
121 FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest
, ProcessResponseSetInterval
);
122 FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest
,
123 ProcessResponseExpectedSequenceId
);
124 friend class HeartbeatSenderTest
;
129 void ProcessResponse(bool is_offline_heartbeat_response
,
131 const buzz::XmlElement
* response
);
132 void SetInterval(int interval
);
133 void SetSequenceId(int sequence_id
);
135 // Helper methods used by DoSendStanza() to generate heartbeat stanzas.
136 scoped_ptr
<buzz::XmlElement
> CreateHeartbeatMessage();
137 scoped_ptr
<buzz::XmlElement
> CreateSignature();
139 base::Closure on_heartbeat_successful_callback_
;
140 base::Closure on_unknown_host_id_error_
;
141 std::string host_id_
;
142 SignalStrategy
* signal_strategy_
;
143 scoped_refptr
<RsaKeyPair
> key_pair_
;
144 std::string directory_bot_jid_
;
145 scoped_ptr
<IqSender
> iq_sender_
;
146 scoped_ptr
<IqRequest
> request_
;
148 base::RepeatingTimer
<HeartbeatSender
> timer_
;
149 base::OneShotTimer
<HeartbeatSender
> timer_resend_
;
151 bool sequence_id_was_set_
;
152 int sequence_id_recent_set_num_
;
153 bool heartbeat_succeeded_
;
154 int failed_startup_heartbeat_count_
;
155 std::string host_offline_reason_
;
156 base::Closure host_offline_reason_ack_callback_
;
158 base::ThreadChecker thread_checker_
;
160 DISALLOW_COPY_AND_ASSIGN(HeartbeatSender
);
163 } // namespace remoting
165 #endif // REMOTING_HOST_HEARTBEAT_SENDER_H_