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 #include "remoting/host/heartbeat_sender.h"
9 #include "base/memory/ref_counted.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/message_loop/message_loop_proxy.h"
12 #include "base/run_loop.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "remoting/base/constants.h"
15 #include "remoting/base/rsa_key_pair.h"
16 #include "remoting/base/test_rsa_key_pair.h"
17 #include "remoting/host/mock_callback.h"
18 #include "remoting/signaling/iq_sender.h"
19 #include "remoting/signaling/mock_signal_strategy.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
23 #include "third_party/webrtc/libjingle/xmpp/constants.h"
26 using buzz::XmlElement
;
29 using testing::DeleteArg
;
31 using testing::Invoke
;
32 using testing::NotNull
;
33 using testing::Return
;
34 using testing::SaveArg
;
40 const char kTestBotJid
[] = "remotingunittest@bot.talk.google.com";
41 const char kHostId
[] = "0";
42 const char kTestJid
[] = "user@gmail.com/chromoting123";
43 const char kStanzaId
[] = "123";
44 const int kTestInterval
= 123;
48 ACTION_P(AddListener
, list
) {
51 ACTION_P(RemoveListener
, list
) {
52 EXPECT_TRUE(list
->find(arg0
) != list
->end());
56 class HeartbeatSenderTest
57 : public testing::Test
{
59 void SetUp() override
{
60 key_pair_
= RsaKeyPair::FromString(kTestRsaKeyPair
);
61 ASSERT_TRUE(key_pair_
.get());
63 EXPECT_CALL(signal_strategy_
, GetState())
64 .WillOnce(Return(SignalStrategy::DISCONNECTED
));
65 EXPECT_CALL(signal_strategy_
, AddListener(NotNull()))
66 .WillRepeatedly(AddListener(&signal_strategy_listeners_
));
67 EXPECT_CALL(signal_strategy_
, RemoveListener(NotNull()))
68 .WillRepeatedly(RemoveListener(&signal_strategy_listeners_
));
69 EXPECT_CALL(signal_strategy_
, GetLocalJid())
70 .WillRepeatedly(Return(kTestJid
));
71 EXPECT_CALL(mock_unknown_host_id_error_callback_
, Run())
74 heartbeat_sender_
.reset(new HeartbeatSender(
75 mock_heartbeat_successful_callback_
.GetCallback(),
76 mock_unknown_host_id_error_callback_
.GetCallback(),
77 kHostId
, &signal_strategy_
, key_pair_
, kTestBotJid
));
80 void TearDown() override
{
81 heartbeat_sender_
.reset();
82 EXPECT_TRUE(signal_strategy_listeners_
.empty());
85 void ValidateHeartbeatStanza(XmlElement
* stanza
,
86 const char* expected_sequence_id
,
87 const char* expected_host_offline_reason
);
89 void ProcessResponseWithInterval(
90 bool is_offline_heartbeat_response
,
93 base::MessageLoop message_loop_
;
94 MockSignalStrategy signal_strategy_
;
95 MockClosure mock_heartbeat_successful_callback_
;
96 MockClosure mock_unknown_host_id_error_callback_
;
97 std::set
<SignalStrategy::Listener
*> signal_strategy_listeners_
;
98 scoped_refptr
<RsaKeyPair
> key_pair_
;
99 scoped_ptr
<HeartbeatSender
> heartbeat_sender_
;
102 // Call Start() followed by Stop(), and make sure a valid heartbeat is sent.
103 TEST_F(HeartbeatSenderTest
, DoSendStanza
) {
104 XmlElement
* sent_iq
= NULL
;
105 EXPECT_CALL(signal_strategy_
, GetLocalJid())
106 .WillRepeatedly(Return(kTestJid
));
107 EXPECT_CALL(signal_strategy_
, GetNextId())
108 .WillOnce(Return(kStanzaId
));
109 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
110 .WillOnce(DoAll(SaveArg
<0>(&sent_iq
), Return(true)));
111 EXPECT_CALL(signal_strategy_
, GetState())
112 .WillRepeatedly(Return(SignalStrategy::CONNECTED
));
114 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::CONNECTED
);
115 base::RunLoop().RunUntilIdle();
117 scoped_ptr
<XmlElement
> stanza(sent_iq
);
118 ASSERT_TRUE(stanza
!= NULL
);
119 ValidateHeartbeatStanza(stanza
.get(), "0", nullptr);
121 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::DISCONNECTED
);
122 base::RunLoop().RunUntilIdle();
125 // Call Start() followed by Stop(), twice, and make sure two valid heartbeats
126 // are sent, with the correct sequence IDs.
127 TEST_F(HeartbeatSenderTest
, DoSendStanzaTwice
) {
128 XmlElement
* sent_iq
= NULL
;
129 EXPECT_CALL(signal_strategy_
, GetLocalJid())
130 .WillRepeatedly(Return(kTestJid
));
131 EXPECT_CALL(signal_strategy_
, GetNextId())
132 .WillOnce(Return(kStanzaId
));
133 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
134 .WillOnce(DoAll(SaveArg
<0>(&sent_iq
), Return(true)));
135 EXPECT_CALL(signal_strategy_
, GetState())
136 .WillRepeatedly(Return(SignalStrategy::CONNECTED
));
138 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::CONNECTED
);
139 base::RunLoop().RunUntilIdle();
141 scoped_ptr
<XmlElement
> stanza(sent_iq
);
142 ASSERT_TRUE(stanza
!= NULL
);
143 ValidateHeartbeatStanza(stanza
.get(), "0", nullptr);
145 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::DISCONNECTED
);
146 base::RunLoop().RunUntilIdle();
148 EXPECT_CALL(signal_strategy_
, GetLocalJid())
149 .WillRepeatedly(Return(kTestJid
));
150 EXPECT_CALL(signal_strategy_
, GetNextId())
151 .WillOnce(Return(kStanzaId
+ 1));
152 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
153 .WillOnce(DoAll(SaveArg
<0>(&sent_iq
), Return(true)));
155 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::CONNECTED
);
156 base::RunLoop().RunUntilIdle();
158 scoped_ptr
<XmlElement
> stanza2(sent_iq
);
159 ValidateHeartbeatStanza(stanza2
.get(), "1", nullptr);
161 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::DISCONNECTED
);
162 base::RunLoop().RunUntilIdle();
165 // Call Start() followed by Stop(), make sure a valid Iq stanza is sent,
166 // reply with an expected sequence ID, and make sure two valid heartbeats
167 // are sent, with the correct sequence IDs.
168 TEST_F(HeartbeatSenderTest
, DoSendStanzaWithExpectedSequenceId
) {
169 XmlElement
* sent_iq
= NULL
;
170 EXPECT_CALL(signal_strategy_
, GetLocalJid())
171 .WillRepeatedly(Return(kTestJid
));
172 EXPECT_CALL(signal_strategy_
, GetNextId())
173 .WillOnce(Return(kStanzaId
));
174 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
175 .WillOnce(DoAll(SaveArg
<0>(&sent_iq
), Return(true)));
176 EXPECT_CALL(signal_strategy_
, GetState())
177 .WillRepeatedly(Return(SignalStrategy::CONNECTED
));
179 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::CONNECTED
);
180 base::RunLoop().RunUntilIdle();
182 scoped_ptr
<XmlElement
> stanza(sent_iq
);
183 ASSERT_TRUE(stanza
!= NULL
);
184 ValidateHeartbeatStanza(stanza
.get(), "0", nullptr);
186 XmlElement
* sent_iq2
= NULL
;
187 EXPECT_CALL(signal_strategy_
, GetLocalJid())
188 .WillRepeatedly(Return(kTestJid
));
189 EXPECT_CALL(signal_strategy_
, GetNextId())
190 .WillOnce(Return(kStanzaId
+ 1));
191 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
192 .WillOnce(DoAll(SaveArg
<0>(&sent_iq2
), Return(true)));
194 scoped_ptr
<XmlElement
> response(new XmlElement(buzz::QN_IQ
));
195 response
->AddAttr(QName(std::string(), "type"), "result");
197 new XmlElement(QName(kChromotingXmlNamespace
, "heartbeat-result"));
198 response
->AddElement(result
);
199 XmlElement
* expected_sequence_id
= new XmlElement(
200 QName(kChromotingXmlNamespace
, "expected-sequence-id"));
201 result
->AddElement(expected_sequence_id
);
202 const int kExpectedSequenceId
= 456;
203 expected_sequence_id
->AddText(base::IntToString(kExpectedSequenceId
));
204 heartbeat_sender_
->ProcessResponse(false, NULL
, response
.get());
205 base::RunLoop().RunUntilIdle();
207 scoped_ptr
<XmlElement
> stanza2(sent_iq2
);
208 ASSERT_TRUE(stanza2
!= NULL
);
209 ValidateHeartbeatStanza(stanza2
.get(),
210 base::IntToString(kExpectedSequenceId
).c_str(),
213 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::DISCONNECTED
);
214 base::RunLoop().RunUntilIdle();
217 void HeartbeatSenderTest::ProcessResponseWithInterval(
218 bool is_offline_heartbeat_response
,
220 scoped_ptr
<XmlElement
> response(new XmlElement(buzz::QN_IQ
));
221 response
->AddAttr(QName(std::string(), "type"), "result");
223 XmlElement
* result
= new XmlElement(
224 QName(kChromotingXmlNamespace
, "heartbeat-result"));
225 response
->AddElement(result
);
227 XmlElement
* set_interval
= new XmlElement(
228 QName(kChromotingXmlNamespace
, "set-interval"));
229 result
->AddElement(set_interval
);
231 set_interval
->AddText(base::IntToString(interval
));
233 heartbeat_sender_
->ProcessResponse(
234 is_offline_heartbeat_response
, NULL
, response
.get());
237 // Verify that ProcessResponse parses set-interval result.
238 TEST_F(HeartbeatSenderTest
, ProcessResponseSetInterval
) {
239 EXPECT_CALL(mock_heartbeat_successful_callback_
, Run());
241 ProcessResponseWithInterval(false, kTestInterval
);
243 EXPECT_EQ(kTestInterval
* 1000, heartbeat_sender_
->interval_ms_
);
246 // Make sure SetHostOfflineReason sends a correct stanza.
247 TEST_F(HeartbeatSenderTest
, DoSetHostOfflineReason
) {
248 XmlElement
* sent_iq
= NULL
;
249 EXPECT_CALL(signal_strategy_
, GetLocalJid())
250 .WillRepeatedly(Return(kTestJid
));
251 EXPECT_CALL(signal_strategy_
, GetNextId())
252 .WillOnce(Return(kStanzaId
));
253 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
254 .WillOnce(DoAll(SaveArg
<0>(&sent_iq
), Return(true)));
255 EXPECT_CALL(signal_strategy_
, GetState())
256 .WillOnce(Return(SignalStrategy::DISCONNECTED
))
257 .WillRepeatedly(Return(SignalStrategy::CONNECTED
));
259 heartbeat_sender_
->SetHostOfflineReason(
261 base::Bind(base::DoNothing
));
262 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::CONNECTED
);
263 base::RunLoop().RunUntilIdle();
265 scoped_ptr
<XmlElement
> stanza(sent_iq
);
266 ASSERT_TRUE(stanza
!= NULL
);
267 ValidateHeartbeatStanza(stanza
.get(), "0", "test_error");
269 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::DISCONNECTED
);
270 base::RunLoop().RunUntilIdle();
273 // Make sure SetHostOfflineReason triggers a callback when bot responds.
274 TEST_F(HeartbeatSenderTest
, ProcessHostOfflineResponses
) {
275 MockClosure mock_ack_callback
;
277 EXPECT_CALL(signal_strategy_
, GetLocalJid())
278 .WillRepeatedly(Return(kTestJid
));
279 EXPECT_CALL(signal_strategy_
, GetNextId())
280 .WillOnce(Return(kStanzaId
));
281 EXPECT_CALL(signal_strategy_
, SendStanzaPtr(NotNull()))
282 .WillOnce(DoAll(DeleteArg
<0>(), Return(true)));
283 EXPECT_CALL(signal_strategy_
, GetState())
284 .WillOnce(Return(SignalStrategy::DISCONNECTED
))
285 .WillRepeatedly(Return(SignalStrategy::CONNECTED
));
286 EXPECT_CALL(mock_heartbeat_successful_callback_
, Run())
287 .WillRepeatedly(Return());
289 // Callback should not run, until response to offline-reason.
290 EXPECT_CALL(mock_ack_callback
, Run()).Times(0);
292 heartbeat_sender_
->SetHostOfflineReason(
294 mock_ack_callback
.GetCallback());
295 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::CONNECTED
);
296 base::RunLoop().RunUntilIdle();
298 ProcessResponseWithInterval(
299 false, // <- This is not a response to offline-reason.
301 base::RunLoop().RunUntilIdle();
303 // Callback should run once, when we get response to offline-reason.
304 EXPECT_CALL(mock_ack_callback
, Run()).Times(1);
305 ProcessResponseWithInterval(
306 true, // <- This is a response to offline-reason.
308 base::RunLoop().RunUntilIdle();
310 // When subsequent responses to offline-reason come,
311 // the callback should not be called again.
312 EXPECT_CALL(mock_ack_callback
, Run()).Times(0);
313 ProcessResponseWithInterval(true, kTestInterval
);
314 base::RunLoop().RunUntilIdle();
316 heartbeat_sender_
->OnSignalStrategyStateChange(SignalStrategy::DISCONNECTED
);
317 base::RunLoop().RunUntilIdle();
320 // Validate a heartbeat stanza.
321 void HeartbeatSenderTest::ValidateHeartbeatStanza(
323 const char* expected_sequence_id
,
324 const char* expected_host_offline_reason
) {
325 EXPECT_EQ(stanza
->Attr(buzz::QName(std::string(), "to")),
326 std::string(kTestBotJid
));
327 EXPECT_EQ(stanza
->Attr(buzz::QName(std::string(), "type")), "set");
328 XmlElement
* heartbeat_stanza
=
329 stanza
->FirstNamed(QName(kChromotingXmlNamespace
, "heartbeat"));
330 ASSERT_TRUE(heartbeat_stanza
!= NULL
);
331 EXPECT_EQ(expected_sequence_id
, heartbeat_stanza
->Attr(
332 buzz::QName(kChromotingXmlNamespace
, "sequence-id")));
333 if (expected_host_offline_reason
== nullptr) {
334 EXPECT_FALSE(heartbeat_stanza
->HasAttr(
335 buzz::QName(kChromotingXmlNamespace
, "host-offline-reason")));
337 EXPECT_EQ(expected_host_offline_reason
, heartbeat_stanza
->Attr(
338 buzz::QName(kChromotingXmlNamespace
, "host-offline-reason")));
340 EXPECT_EQ(std::string(kHostId
),
341 heartbeat_stanza
->Attr(QName(kChromotingXmlNamespace
, "hostid")));
343 QName
signature_tag(kChromotingXmlNamespace
, "signature");
344 XmlElement
* signature
= heartbeat_stanza
->FirstNamed(signature_tag
);
345 ASSERT_TRUE(signature
!= NULL
);
346 EXPECT_TRUE(heartbeat_stanza
->NextNamed(signature_tag
) == NULL
);
348 scoped_refptr
<RsaKeyPair
> key_pair
= RsaKeyPair::FromString(kTestRsaKeyPair
);
349 ASSERT_TRUE(key_pair
.get());
350 std::string expected_signature
=
351 key_pair
->SignMessage(std::string(kTestJid
) + ' ' + expected_sequence_id
);
352 EXPECT_EQ(expected_signature
, signature
->BodyText());
355 } // namespace remoting