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
17namespace gcm {
18
19namespace {
20
21const uint64 kAndroidId = 54321;
22const 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.
26const 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.
31const int kAckLimitSize = 10;
32
33// Helper for building arbitrary data messages.
34MCSMessage 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.
48class TestMCSClient : public MCSClient {
49 public:
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
57  virtual std::string GetNextPersistentId() OVERRIDE {
58    return base::UintToString(++next_id_);
59  }
60
61 private:
62  uint32 next_id_;
63};
64
65class 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
74  TestMCSClient* mcs_client() const { return mcs_client_.get(); }
75  FakeConnectionFactory* connection_factory() {
76    return &connection_factory_;
77  }
78  bool init_success() const { return init_success_; }
79  uint64 restored_android_id() const { return restored_android_id_; }
80  uint64 restored_security_token() const { return restored_security_token_; }
81  MCSMessage* received_message() const { return received_message_.get(); }
82  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
109MCSClientTest::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
123MCSClientTest::~MCSClientTest() {}
124
125void MCSClientTest::BuildMCSClient() {
126  mcs_client_.reset(
127      new TestMCSClient(temp_directory_.path(),
128                        &connection_factory_,
129                        message_loop_.message_loop_proxy()));
130}
131
132void 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
143void 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
157FakeConnectionHandler* MCSClientTest::GetFakeHandler() const {
158  return reinterpret_cast<FakeConnectionHandler*>(
159      connection_factory_.GetConnectionHandler());
160}
161
162void MCSClientTest::WaitForMCSEvent() {
163  run_loop_->Run();
164  run_loop_.reset(new base::RunLoop());
165}
166
167void MCSClientTest::PumpLoop() {
168  run_loop_->RunUntilIdle();
169  run_loop_.reset(new base::RunLoop());
170}
171
172void 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
182void 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
188void 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.
194TEST_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.
204TEST_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.
218TEST_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.
229TEST_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.
240TEST_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.
252TEST_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.
265TEST_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.
294TEST_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.
320TEST_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.
352TEST_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.
386TEST_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.
435TEST_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.
459TEST_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.
486TEST_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