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#ifndef REMOTING_HOST_HEARTBEAT_SENDER_H_
6#define REMOTING_HOST_HEARTBEAT_SENDER_H_
7
8#include <string>
9
10#include "base/compiler_specific.h"
11#include "base/gtest_prod_util.h"
12#include "base/memory/ref_counted.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/timer/timer.h"
15#include "remoting/base/rsa_key_pair.h"
16#include "remoting/signaling/signal_strategy.h"
17
18namespace base {
19class MessageLoopProxy;
20}  // namespace base
21
22namespace buzz {
23class XmlElement;
24}  // namespace buzz
25
26namespace remoting {
27
28class RsaKeyPair;
29class IqRequest;
30class IqSender;
31
32// HeartbeatSender periodically sends heartbeat stanzas to the Chromoting Bot.
33// Each heartbeat stanza looks as follows:
34//
35// <iq type="set" to="remoting@bot.talk.google.com"
36//     from="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
37//   <rem:heartbeat rem:hostid="a1ddb11e-8aef-11df-bccf-18a905b9cb5a"
38//                  rem:sequence-id="456"
39//                  xmlns:rem="google:remoting">
40//     <rem:signature>.signature.</rem:signature>
41//   </rem:heartbeat>
42// </iq>
43//
44// The sequence-id attribute of the heartbeat is a zero-based incrementally
45// increasing integer unique to each heartbeat from a single host.
46// The Bot checks the value, and if it is incorrect, includes the
47// correct value in the result stanza. The host should then send another
48// heartbeat, with the correct sequence-id, and increment the sequence-id in
49// susbequent heartbeats.
50// The signature is a base-64 encoded SHA-1 hash, signed with the host's
51// private RSA key. The message being signed is the full Jid concatenated with
52// the sequence-id, separated by one space. For example, for the heartbeat
53// stanza above, the message that is signed is
54// "user@gmail.com/chromoting123123 456".
55//
56// The Bot sends the following result stanza in response to each successful
57// heartbeat:
58//
59//  <iq type="set" from="remoting@bot.talk.google.com"
60//      to="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
61//    <rem:heartbeat-result xmlns:rem="google:remoting">
62//      <rem:set-interval>300</rem:set-interval>
63//    </rem:heartbeat-result>
64//  </iq>
65//
66// The set-interval tag is used to specify desired heartbeat interval
67// in seconds. The heartbeat-result and the set-interval tags are
68// optional. Host uses default heartbeat interval if it doesn't find
69// set-interval tag in the result Iq stanza it receives from the
70// server.
71// If the heartbeat's sequence-id was incorrect, the Bot sends a result
72// stanza of this form:
73//
74//  <iq type="set" from="remoting@bot.talk.google.com"
75//      to="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
76//    <rem:heartbeat-result xmlns:rem="google:remoting">
77//      <rem:expected-sequence-id>654</rem:expected-sequence-id>
78//    </rem:heartbeat-result>
79//  </iq>
80class HeartbeatSender : public SignalStrategy::Listener {
81 public:
82  class Listener {
83   public:
84    virtual ~Listener() { }
85
86    // Invoked after the first successful heartbeat.
87    virtual void OnHeartbeatSuccessful() = 0;
88
89    // Invoked when the host ID is permanently not recognized by the server.
90    virtual void OnUnknownHostIdError() = 0;
91  };
92
93  // |signal_strategy| and |delegate| must outlive this
94  // object. Heartbeats will start when the supplied SignalStrategy
95  // enters the CONNECTED state.
96  HeartbeatSender(Listener* listener,
97                  const std::string& host_id,
98                  SignalStrategy* signal_strategy,
99                  scoped_refptr<RsaKeyPair> key_pair,
100                  const std::string& directory_bot_jid);
101  virtual ~HeartbeatSender();
102
103  // SignalStrategy::Listener interface.
104  virtual void OnSignalStrategyStateChange(
105      SignalStrategy::State state) OVERRIDE;
106  virtual bool OnSignalStrategyIncomingStanza(
107      const buzz::XmlElement* stanza) OVERRIDE;
108
109 private:
110  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, DoSendStanza);
111  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest,
112                           DoSendStanzaWithExpectedSequenceId);
113  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, CreateHeartbeatMessage);
114  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, ProcessResponseSetInterval);
115  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest,
116                           ProcessResponseExpectedSequenceId);
117
118  void SendStanza();
119  void ResendStanza();
120  void DoSendStanza();
121  void ProcessResponse(IqRequest* request, const buzz::XmlElement* response);
122  void SetInterval(int interval);
123  void SetSequenceId(int sequence_id);
124
125  // Helper methods used by DoSendStanza() to generate heartbeat stanzas.
126  scoped_ptr<buzz::XmlElement> CreateHeartbeatMessage();
127  scoped_ptr<buzz::XmlElement> CreateSignature();
128
129  Listener* listener_;
130  std::string host_id_;
131  SignalStrategy* signal_strategy_;
132  scoped_refptr<RsaKeyPair> key_pair_;
133  std::string directory_bot_jid_;
134  scoped_ptr<IqSender> iq_sender_;
135  scoped_ptr<IqRequest> request_;
136  int interval_ms_;
137  base::RepeatingTimer<HeartbeatSender> timer_;
138  base::OneShotTimer<HeartbeatSender> timer_resend_;
139  int sequence_id_;
140  bool sequence_id_was_set_;
141  int sequence_id_recent_set_num_;
142  bool heartbeat_succeeded_;
143  int failed_startup_heartbeat_count_;
144
145  DISALLOW_COPY_AND_ASSIGN(HeartbeatSender);
146};
147
148}  // namespace remoting
149
150#endif  // REMOTING_HOST_HEARTBEAT_SENDER_H_
151