1 // Copyright 2013 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 #include "google_apis/gcm/engine/mcs_client.h"
6
7 #include "base/files/scoped_temp_dir.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/run_loop.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "components/webdata/encryptor/encryptor.h"
12 #include "google_apis/gcm/base/mcs_util.h"
13 #include "google_apis/gcm/engine/fake_connection_factory.h"
14 #include "google_apis/gcm/engine/fake_connection_handler.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace gcm {
18
19 namespace {
20
21 const uint64 kAndroidId = 54321;
22 const uint64 kSecurityToken = 12345;
23
24 // Number of messages to send when testing batching.
25 // Note: must be even for tests that split batches in half.
26 const int kMessageBatchSize = 6;
27
28 // The number of unacked messages the client will receive before sending a
29 // stream ack.
30 // TODO(zea): get this (and other constants) directly from the mcs client.
31 const int kAckLimitSize = 10;
32
33 // Helper for building arbitrary data messages.
BuildDataMessage(const std::string & from,const std::string & category,int last_stream_id_received,const std::string persistent_id)34 MCSMessage BuildDataMessage(const std::string& from,
35 const std::string& category,
36 int last_stream_id_received,
37 const std::string persistent_id) {
38 mcs_proto::DataMessageStanza data_message;
39 data_message.set_from(from);
40 data_message.set_category(category);
41 data_message.set_last_stream_id_received(last_stream_id_received);
42 if (!persistent_id.empty())
43 data_message.set_persistent_id(persistent_id);
44 return MCSMessage(kDataMessageStanzaTag, data_message);
45 }
46
47 // MCSClient with overriden exposed persistent id logic.
48 class TestMCSClient : public MCSClient {
49 public:
TestMCSClient(const base::FilePath & rmq_path,ConnectionFactory * connection_factory,scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)50 TestMCSClient(const base::FilePath& rmq_path,
51 ConnectionFactory* connection_factory,
52 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
53 : MCSClient(rmq_path, connection_factory, blocking_task_runner),
54 next_id_(0) {
55 }
56
GetNextPersistentId()57 virtual std::string GetNextPersistentId() OVERRIDE {
58 return base::UintToString(++next_id_);
59 }
60
61 private:
62 uint32 next_id_;
63 };
64
65 class MCSClientTest : public testing::Test {
66 public:
67 MCSClientTest();
68 virtual ~MCSClientTest();
69
70 void BuildMCSClient();
71 void InitializeClient();
72 void LoginClient(const std::vector<std::string>& acknowledged_ids);
73
mcs_client() const74 TestMCSClient* mcs_client() const { return mcs_client_.get(); }
connection_factory()75 FakeConnectionFactory* connection_factory() {
76 return &connection_factory_;
77 }
init_success() const78 bool init_success() const { return init_success_; }
restored_android_id() const79 uint64 restored_android_id() const { return restored_android_id_; }
restored_security_token() const80 uint64 restored_security_token() const { return restored_security_token_; }
received_message() const81 MCSMessage* received_message() const { return received_message_.get(); }
sent_message_id() const82 std::string sent_message_id() const { return sent_message_id_;}
83
84 FakeConnectionHandler* GetFakeHandler() const;
85
86 void WaitForMCSEvent();
87 void PumpLoop();
88
89 private:
90 void InitializationCallback(bool success,
91 uint64 restored_android_id,
92 uint64 restored_security_token);
93 void MessageReceivedCallback(const MCSMessage& message);
94 void MessageSentCallback(const std::string& message_id);
95
96 base::ScopedTempDir temp_directory_;
97 base::MessageLoop message_loop_;
98 scoped_ptr<base::RunLoop> run_loop_;
99
100 FakeConnectionFactory connection_factory_;
101 scoped_ptr<TestMCSClient> mcs_client_;
102 bool init_success_;
103 uint64 restored_android_id_;
104 uint64 restored_security_token_;
105 scoped_ptr<MCSMessage> received_message_;
106 std::string sent_message_id_;
107 };
108
MCSClientTest()109 MCSClientTest::MCSClientTest()
110 : run_loop_(new base::RunLoop()),
111 init_success_(false),
112 restored_android_id_(0),
113 restored_security_token_(0) {
114 EXPECT_TRUE(temp_directory_.CreateUniqueTempDir());
115 run_loop_.reset(new base::RunLoop());
116
117 // On OSX, prevent the Keychain permissions popup during unit tests.
118 #if defined(OS_MACOSX)
119 Encryptor::UseMockKeychain(true);
120 #endif
121 }
122
~MCSClientTest()123 MCSClientTest::~MCSClientTest() {}
124
BuildMCSClient()125 void MCSClientTest::BuildMCSClient() {
126 mcs_client_.reset(
127 new TestMCSClient(temp_directory_.path(),
128 &connection_factory_,
129 message_loop_.message_loop_proxy()));
130 }
131
InitializeClient()132 void MCSClientTest::InitializeClient() {
133 mcs_client_->Initialize(base::Bind(&MCSClientTest::InitializationCallback,
134 base::Unretained(this)),
135 base::Bind(&MCSClientTest::MessageReceivedCallback,
136 base::Unretained(this)),
137 base::Bind(&MCSClientTest::MessageSentCallback,
138 base::Unretained(this)));
139 run_loop_->Run();
140 run_loop_.reset(new base::RunLoop());
141 }
142
LoginClient(const std::vector<std::string> & acknowledged_ids)143 void MCSClientTest::LoginClient(
144 const std::vector<std::string>& acknowledged_ids) {
145 scoped_ptr<mcs_proto::LoginRequest> login_request =
146 BuildLoginRequest(kAndroidId, kSecurityToken);
147 for (size_t i = 0; i < acknowledged_ids.size(); ++i)
148 login_request->add_received_persistent_id(acknowledged_ids[i]);
149 GetFakeHandler()->ExpectOutgoingMessage(
150 MCSMessage(kLoginRequestTag,
151 login_request.PassAs<const google::protobuf::MessageLite>()));
152 mcs_client_->Login(kAndroidId, kSecurityToken);
153 run_loop_->Run();
154 run_loop_.reset(new base::RunLoop());
155 }
156
GetFakeHandler() const157 FakeConnectionHandler* MCSClientTest::GetFakeHandler() const {
158 return reinterpret_cast<FakeConnectionHandler*>(
159 connection_factory_.GetConnectionHandler());
160 }
161
WaitForMCSEvent()162 void MCSClientTest::WaitForMCSEvent() {
163 run_loop_->Run();
164 run_loop_.reset(new base::RunLoop());
165 }
166
PumpLoop()167 void MCSClientTest::PumpLoop() {
168 run_loop_->RunUntilIdle();
169 run_loop_.reset(new base::RunLoop());
170 }
171
InitializationCallback(bool success,uint64 restored_android_id,uint64 restored_security_token)172 void MCSClientTest::InitializationCallback(bool success,
173 uint64 restored_android_id,
174 uint64 restored_security_token) {
175 init_success_ = success;
176 restored_android_id_ = restored_android_id;
177 restored_security_token_ = restored_security_token;
178 DVLOG(1) << "Initialization callback invoked, killing loop.";
179 run_loop_->Quit();
180 }
181
MessageReceivedCallback(const MCSMessage & message)182 void MCSClientTest::MessageReceivedCallback(const MCSMessage& message) {
183 received_message_.reset(new MCSMessage(message));
184 DVLOG(1) << "Message received callback invoked, killing loop.";
185 run_loop_->Quit();
186 }
187
MessageSentCallback(const std::string & message_id)188 void MCSClientTest::MessageSentCallback(const std::string& message_id) {
189 DVLOG(1) << "Message sent callback invoked, killing loop.";
190 run_loop_->Quit();
191 }
192
193 // Initialize a new client.
TEST_F(MCSClientTest,InitializeNew)194 TEST_F(MCSClientTest, InitializeNew) {
195 BuildMCSClient();
196 InitializeClient();
197 EXPECT_EQ(0U, restored_android_id());
198 EXPECT_EQ(0U, restored_security_token());
199 EXPECT_TRUE(init_success());
200 }
201
202 // Initialize a new client, shut it down, then restart the client. Should
203 // reload the existing device credentials.
TEST_F(MCSClientTest,InitializeExisting)204 TEST_F(MCSClientTest, InitializeExisting) {
205 BuildMCSClient();
206 InitializeClient();
207 LoginClient(std::vector<std::string>());
208
209 // Rebuild the client, to reload from the RMQ.
210 BuildMCSClient();
211 InitializeClient();
212 EXPECT_EQ(kAndroidId, restored_android_id());
213 EXPECT_EQ(kSecurityToken, restored_security_token());
214 EXPECT_TRUE(init_success());
215 }
216
217 // Log in successfully to the MCS endpoint.
TEST_F(MCSClientTest,LoginSuccess)218 TEST_F(MCSClientTest, LoginSuccess) {
219 BuildMCSClient();
220 InitializeClient();
221 LoginClient(std::vector<std::string>());
222 EXPECT_TRUE(connection_factory()->IsEndpointReachable());
223 EXPECT_TRUE(init_success());
224 ASSERT_TRUE(received_message());
225 EXPECT_EQ(kLoginResponseTag, received_message()->tag());
226 }
227
228 // Encounter a server error during the login attempt.
TEST_F(MCSClientTest,FailLogin)229 TEST_F(MCSClientTest, FailLogin) {
230 BuildMCSClient();
231 InitializeClient();
232 GetFakeHandler()->set_fail_login(true);
233 LoginClient(std::vector<std::string>());
234 EXPECT_FALSE(connection_factory()->IsEndpointReachable());
235 EXPECT_FALSE(init_success());
236 EXPECT_FALSE(received_message());
237 }
238
239 // Send a message without RMQ support.
TEST_F(MCSClientTest,SendMessageNoRMQ)240 TEST_F(MCSClientTest, SendMessageNoRMQ) {
241 BuildMCSClient();
242 InitializeClient();
243 LoginClient(std::vector<std::string>());
244 MCSMessage message(BuildDataMessage("from", "category", 1, ""));
245 GetFakeHandler()->ExpectOutgoingMessage(message);
246 mcs_client()->SendMessage(message, false);
247 EXPECT_TRUE(GetFakeHandler()->
248 AllOutgoingMessagesReceived());
249 }
250
251 // Send a message with RMQ support.
TEST_F(MCSClientTest,SendMessageRMQ)252 TEST_F(MCSClientTest, SendMessageRMQ) {
253 BuildMCSClient();
254 InitializeClient();
255 LoginClient(std::vector<std::string>());
256 MCSMessage message(BuildDataMessage("from", "category", 1, "1"));
257 GetFakeHandler()->ExpectOutgoingMessage(message);
258 mcs_client()->SendMessage(message, true);
259 EXPECT_TRUE(GetFakeHandler()->
260 AllOutgoingMessagesReceived());
261 }
262
263 // Send a message with RMQ support while disconnected. On reconnect, the message
264 // should be resent.
TEST_F(MCSClientTest,SendMessageRMQWhileDisconnected)265 TEST_F(MCSClientTest, SendMessageRMQWhileDisconnected) {
266 BuildMCSClient();
267 InitializeClient();
268 LoginClient(std::vector<std::string>());
269 GetFakeHandler()->set_fail_send(true);
270 MCSMessage message(BuildDataMessage("from", "category", 1, "1"));
271
272 // The initial (failed) send.
273 GetFakeHandler()->ExpectOutgoingMessage(message);
274 // The login request.
275 GetFakeHandler()->ExpectOutgoingMessage(
276 MCSMessage(kLoginRequestTag,
277 BuildLoginRequest(kAndroidId, kSecurityToken).
278 PassAs<const google::protobuf::MessageLite>()));
279 // The second (re)send.
280 GetFakeHandler()->ExpectOutgoingMessage(message);
281 mcs_client()->SendMessage(message, true);
282 EXPECT_FALSE(GetFakeHandler()->
283 AllOutgoingMessagesReceived());
284 GetFakeHandler()->set_fail_send(false);
285 connection_factory()->Connect();
286 WaitForMCSEvent(); // Wait for the login to finish.
287 PumpLoop(); // Wait for the send to happen.
288 EXPECT_TRUE(GetFakeHandler()->
289 AllOutgoingMessagesReceived());
290 }
291
292 // Send a message with RMQ support without receiving an acknowledgement. On
293 // restart the message should be resent.
TEST_F(MCSClientTest,SendMessageRMQOnRestart)294 TEST_F(MCSClientTest, SendMessageRMQOnRestart) {
295 BuildMCSClient();
296 InitializeClient();
297 LoginClient(std::vector<std::string>());
298 GetFakeHandler()->set_fail_send(true);
299 MCSMessage message(BuildDataMessage("from", "category", 1, "1"));
300
301 // The initial (failed) send.
302 GetFakeHandler()->ExpectOutgoingMessage(message);
303 GetFakeHandler()->set_fail_send(false);
304 mcs_client()->SendMessage(message, true);
305 EXPECT_TRUE(GetFakeHandler()->
306 AllOutgoingMessagesReceived());
307
308 // Rebuild the client, which should resend the old message.
309 BuildMCSClient();
310 InitializeClient();
311 LoginClient(std::vector<std::string>());
312 GetFakeHandler()->ExpectOutgoingMessage(message);
313 PumpLoop();
314 EXPECT_TRUE(GetFakeHandler()->
315 AllOutgoingMessagesReceived());
316 }
317
318 // Send messages with RMQ support, followed by receiving a stream ack. On
319 // restart nothing should be recent.
TEST_F(MCSClientTest,SendMessageRMQWithStreamAck)320 TEST_F(MCSClientTest, SendMessageRMQWithStreamAck) {
321 BuildMCSClient();
322 InitializeClient();
323 LoginClient(std::vector<std::string>());
324
325 // Send some messages.
326 for (int i = 1; i <= kMessageBatchSize; ++i) {
327 MCSMessage message(
328 BuildDataMessage("from", "category", 1, base::IntToString(i)));
329 GetFakeHandler()->ExpectOutgoingMessage(message);
330 mcs_client()->SendMessage(message, true);
331 }
332 EXPECT_TRUE(GetFakeHandler()->
333 AllOutgoingMessagesReceived());
334
335 // Receive the ack.
336 scoped_ptr<mcs_proto::IqStanza> ack = BuildStreamAck();
337 ack->set_last_stream_id_received(kMessageBatchSize + 1);
338 GetFakeHandler()->ReceiveMessage(
339 MCSMessage(kIqStanzaTag,
340 ack.PassAs<const google::protobuf::MessageLite>()));
341 WaitForMCSEvent();
342
343 // Reconnect and ensure no messages are resent.
344 BuildMCSClient();
345 InitializeClient();
346 LoginClient(std::vector<std::string>());
347 PumpLoop();
348 }
349
350 // Send messages with RMQ support. On restart, receive a SelectiveAck with
351 // the login response. No messages should be resent.
TEST_F(MCSClientTest,SendMessageRMQAckOnReconnect)352 TEST_F(MCSClientTest, SendMessageRMQAckOnReconnect) {
353 BuildMCSClient();
354 InitializeClient();
355 LoginClient(std::vector<std::string>());
356
357 // Send some messages.
358 std::vector<std::string> id_list;
359 for (int i = 1; i <= kMessageBatchSize; ++i) {
360 id_list.push_back(base::IntToString(i));
361 MCSMessage message(
362 BuildDataMessage("from", "category", 1, id_list.back()));
363 GetFakeHandler()->ExpectOutgoingMessage(message);
364 mcs_client()->SendMessage(message, true);
365 }
366 EXPECT_TRUE(GetFakeHandler()->
367 AllOutgoingMessagesReceived());
368
369 // Rebuild the client, and receive an acknowledgment for the messages as
370 // part of the login response.
371 BuildMCSClient();
372 InitializeClient();
373 LoginClient(std::vector<std::string>());
374 scoped_ptr<mcs_proto::IqStanza> ack(BuildSelectiveAck(id_list));
375 GetFakeHandler()->ReceiveMessage(
376 MCSMessage(kIqStanzaTag,
377 ack.PassAs<const google::protobuf::MessageLite>()));
378 WaitForMCSEvent();
379 EXPECT_TRUE(GetFakeHandler()->
380 AllOutgoingMessagesReceived());
381 }
382
383 // Send messages with RMQ support. On restart, receive a SelectiveAck with
384 // the login response that only acks some messages. The unacked messages should
385 // be resent.
TEST_F(MCSClientTest,SendMessageRMQPartialAckOnReconnect)386 TEST_F(MCSClientTest, SendMessageRMQPartialAckOnReconnect) {
387 BuildMCSClient();
388 InitializeClient();
389 LoginClient(std::vector<std::string>());
390
391 // Send some messages.
392 std::vector<std::string> id_list;
393 for (int i = 1; i <= kMessageBatchSize; ++i) {
394 id_list.push_back(base::IntToString(i));
395 MCSMessage message(
396 BuildDataMessage("from", "category", 1, id_list.back()));
397 GetFakeHandler()->ExpectOutgoingMessage(message);
398 mcs_client()->SendMessage(message, true);
399 }
400 EXPECT_TRUE(GetFakeHandler()->
401 AllOutgoingMessagesReceived());
402
403 // Rebuild the client, and receive an acknowledgment for the messages as
404 // part of the login response.
405 BuildMCSClient();
406 InitializeClient();
407 LoginClient(std::vector<std::string>());
408
409 std::vector<std::string> acked_ids, remaining_ids;
410 acked_ids.insert(acked_ids.end(),
411 id_list.begin(),
412 id_list.begin() + kMessageBatchSize / 2);
413 remaining_ids.insert(remaining_ids.end(),
414 id_list.begin() + kMessageBatchSize / 2,
415 id_list.end());
416 for (int i = 1; i <= kMessageBatchSize / 2; ++i) {
417 MCSMessage message(
418 BuildDataMessage("from",
419 "category",
420 2,
421 remaining_ids[i - 1]));
422 GetFakeHandler()->ExpectOutgoingMessage(message);
423 }
424 scoped_ptr<mcs_proto::IqStanza> ack(BuildSelectiveAck(acked_ids));
425 GetFakeHandler()->ReceiveMessage(
426 MCSMessage(kIqStanzaTag,
427 ack.PassAs<const google::protobuf::MessageLite>()));
428 WaitForMCSEvent();
429 EXPECT_TRUE(GetFakeHandler()->
430 AllOutgoingMessagesReceived());
431 }
432
433 // Receive some messages. On restart, the login request should contain the
434 // appropriate acknowledged ids.
TEST_F(MCSClientTest,AckOnLogin)435 TEST_F(MCSClientTest, AckOnLogin) {
436 BuildMCSClient();
437 InitializeClient();
438 LoginClient(std::vector<std::string>());
439
440 // Receive some messages.
441 std::vector<std::string> id_list;
442 for (int i = 1; i <= kMessageBatchSize; ++i) {
443 id_list.push_back(base::IntToString(i));
444 MCSMessage message(
445 BuildDataMessage("from", "category", i, id_list.back()));
446 GetFakeHandler()->ReceiveMessage(message);
447 WaitForMCSEvent();
448 PumpLoop();
449 }
450
451 // Restart the client.
452 BuildMCSClient();
453 InitializeClient();
454 LoginClient(id_list);
455 }
456
457 // Receive some messages. On the next send, the outgoing message should contain
458 // the appropriate last stream id received field to ack the received messages.
TEST_F(MCSClientTest,AckOnSend)459 TEST_F(MCSClientTest, AckOnSend) {
460 BuildMCSClient();
461 InitializeClient();
462 LoginClient(std::vector<std::string>());
463
464 // Receive some messages.
465 std::vector<std::string> id_list;
466 for (int i = 1; i <= kMessageBatchSize; ++i) {
467 id_list.push_back(base::IntToString(i));
468 MCSMessage message(
469 BuildDataMessage("from", "category", i, id_list.back()));
470 GetFakeHandler()->ReceiveMessage(message);
471 WaitForMCSEvent();
472 PumpLoop();
473 }
474
475 // Trigger a message send, which should acknowledge via stream ack.
476 MCSMessage message(
477 BuildDataMessage("from", "category", kMessageBatchSize + 1, "1"));
478 GetFakeHandler()->ExpectOutgoingMessage(message);
479 mcs_client()->SendMessage(message, true);
480 EXPECT_TRUE(GetFakeHandler()->
481 AllOutgoingMessagesReceived());
482 }
483
484 // Receive the ack limit in messages, which should trigger an automatic
485 // stream ack. Receive a heartbeat to confirm the ack.
TEST_F(MCSClientTest,AckWhenLimitReachedWithHeartbeat)486 TEST_F(MCSClientTest, AckWhenLimitReachedWithHeartbeat) {
487 BuildMCSClient();
488 InitializeClient();
489 LoginClient(std::vector<std::string>());
490
491 // The stream ack.
492 scoped_ptr<mcs_proto::IqStanza> ack = BuildStreamAck();
493 ack->set_last_stream_id_received(kAckLimitSize + 1);
494 GetFakeHandler()->ExpectOutgoingMessage(
495 MCSMessage(kIqStanzaTag,
496 ack.PassAs<const google::protobuf::MessageLite>()));
497
498 // Receive some messages.
499 std::vector<std::string> id_list;
500 for (int i = 1; i <= kAckLimitSize; ++i) {
501 id_list.push_back(base::IntToString(i));
502 MCSMessage message(
503 BuildDataMessage("from", "category", i, id_list.back()));
504 GetFakeHandler()->ReceiveMessage(message);
505 WaitForMCSEvent();
506 PumpLoop();
507 }
508 EXPECT_TRUE(GetFakeHandler()->
509 AllOutgoingMessagesReceived());
510
511 // Receive a heartbeat confirming the ack (and receive the heartbeat ack).
512 scoped_ptr<mcs_proto::HeartbeatPing> heartbeat(
513 new mcs_proto::HeartbeatPing());
514 heartbeat->set_last_stream_id_received(2);
515
516 scoped_ptr<mcs_proto::HeartbeatAck> heartbeat_ack(
517 new mcs_proto::HeartbeatAck());
518 heartbeat_ack->set_last_stream_id_received(kAckLimitSize + 2);
519 GetFakeHandler()->ExpectOutgoingMessage(
520 MCSMessage(kHeartbeatAckTag,
521 heartbeat_ack.PassAs<const google::protobuf::MessageLite>()));
522
523 GetFakeHandler()->ReceiveMessage(
524 MCSMessage(kHeartbeatPingTag,
525 heartbeat.PassAs<const google::protobuf::MessageLite>()));
526 WaitForMCSEvent();
527 EXPECT_TRUE(GetFakeHandler()->
528 AllOutgoingMessagesReceived());
529
530 // Rebuild the client. Nothing should be sent on login.
531 BuildMCSClient();
532 InitializeClient();
533 LoginClient(std::vector<std::string>());
534 EXPECT_TRUE(GetFakeHandler()->
535 AllOutgoingMessagesReceived());
536 }
537
538 } // namespace
539
540 } // namespace gcm
541