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// A standalone tool for testing MCS connections and the MCS client on their
6// own.
7
8#include <cstddef>
9#include <cstdio>
10#include <string>
11#include <vector>
12
13#include "base/at_exit.h"
14#include "base/command_line.h"
15#include "base/compiler_specific.h"
16#include "base/logging.h"
17#include "base/memory/ref_counted.h"
18#include "base/memory/scoped_ptr.h"
19#include "base/message_loop/message_loop.h"
20#include "base/run_loop.h"
21#include "base/strings/string_number_conversions.h"
22#include "base/threading/thread.h"
23#include "base/threading/worker_pool.h"
24#include "base/time/default_clock.h"
25#include "base/values.h"
26#include "google_apis/gcm/base/fake_encryptor.h"
27#include "google_apis/gcm/base/mcs_message.h"
28#include "google_apis/gcm/base/mcs_util.h"
29#include "google_apis/gcm/engine/checkin_request.h"
30#include "google_apis/gcm/engine/connection_factory_impl.h"
31#include "google_apis/gcm/engine/gcm_store_impl.h"
32#include "google_apis/gcm/engine/gservices_settings.h"
33#include "google_apis/gcm/engine/mcs_client.h"
34#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
35#include "net/base/host_mapping_rules.h"
36#include "net/base/net_log_logger.h"
37#include "net/cert/cert_verifier.h"
38#include "net/dns/host_resolver.h"
39#include "net/http/http_auth_handler_factory.h"
40#include "net/http/http_network_session.h"
41#include "net/http/http_server_properties_impl.h"
42#include "net/http/transport_security_state.h"
43#include "net/socket/client_socket_factory.h"
44#include "net/socket/ssl_client_socket.h"
45#include "net/ssl/channel_id_service.h"
46#include "net/ssl/default_channel_id_store.h"
47#include "net/url_request/url_request_test_util.h"
48
49#if defined(OS_MACOSX)
50#include "base/mac/scoped_nsautorelease_pool.h"
51#endif
52
53// This is a simple utility that initializes an mcs client and
54// prints out any events.
55namespace gcm {
56namespace {
57
58const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
59  // Number of initial errors (in sequence) to ignore before applying
60  // exponential back-off rules.
61  0,
62
63  // Initial delay for exponential back-off in ms.
64  15000,  // 15 seconds.
65
66  // Factor by which the waiting time will be multiplied.
67  2,
68
69  // Fuzzing percentage. ex: 10% will spread requests randomly
70  // between 90%-100% of the calculated time.
71  0.5,  // 50%.
72
73  // Maximum amount of time we are willing to delay our request in ms.
74  1000 * 60 * 5, // 5 minutes.
75
76  // Time to keep an entry from being discarded even when it
77  // has no significant state, -1 to never discard.
78  -1,
79
80  // Don't use initial delay unless the last request was an error.
81  false,
82};
83
84// Default values used to communicate with the check-in server.
85const char kChromeVersion[] = "Chrome MCS Probe";
86
87// The default server to communicate with.
88const char kMCSServerHost[] = "mtalk.google.com";
89const uint16 kMCSServerPort = 5228;
90
91// Command line switches.
92const char kRMQFileName[] = "rmq_file";
93const char kAndroidIdSwitch[] = "android_id";
94const char kSecretSwitch[] = "secret";
95const char kLogFileSwitch[] = "log-file";
96const char kIgnoreCertSwitch[] = "ignore-certs";
97const char kServerHostSwitch[] = "host";
98const char kServerPortSwitch[] = "port";
99
100void MessageReceivedCallback(const MCSMessage& message) {
101  LOG(INFO) << "Received message with id "
102            << GetPersistentId(message.GetProtobuf()) << " and tag "
103            << static_cast<int>(message.tag());
104
105  if (message.tag() == kDataMessageStanzaTag) {
106    const mcs_proto::DataMessageStanza& data_message =
107        reinterpret_cast<const mcs_proto::DataMessageStanza&>(
108            message.GetProtobuf());
109    DVLOG(1) << "  to: " << data_message.to();
110    DVLOG(1) << "  from: " << data_message.from();
111    DVLOG(1) << "  category: " << data_message.category();
112    DVLOG(1) << "  sent: " << data_message.sent();
113    for (int i = 0; i < data_message.app_data_size(); ++i) {
114      DVLOG(1) << "  App data " << i << " "
115               << data_message.app_data(i).key() << " : "
116               << data_message.app_data(i).value();
117    }
118  }
119}
120
121void MessageSentCallback(int64 user_serial_number,
122                         const std::string& app_id,
123                         const std::string& message_id,
124                         MCSClient::MessageSendStatus status) {
125  LOG(INFO) << "Message sent. Serial number: " << user_serial_number
126            << " Application ID: " << app_id
127            << " Message ID: " << message_id
128            << " Message send status: " << status;
129}
130
131// Needed to use a real host resolver.
132class MyTestURLRequestContext : public net::TestURLRequestContext {
133 public:
134  MyTestURLRequestContext() : TestURLRequestContext(true) {
135    context_storage_.set_host_resolver(
136        net::HostResolver::CreateDefaultResolver(NULL));
137    context_storage_.set_transport_security_state(
138        new net::TransportSecurityState());
139    Init();
140  }
141
142  virtual ~MyTestURLRequestContext() {}
143};
144
145class MyTestURLRequestContextGetter : public net::TestURLRequestContextGetter {
146 public:
147  explicit MyTestURLRequestContextGetter(
148      const scoped_refptr<base::MessageLoopProxy>& io_message_loop_proxy)
149      : TestURLRequestContextGetter(io_message_loop_proxy) {}
150
151  virtual net::TestURLRequestContext* GetURLRequestContext() OVERRIDE {
152    // Construct |context_| lazily so it gets constructed on the right
153    // thread (the IO thread).
154    if (!context_)
155      context_.reset(new MyTestURLRequestContext());
156    return context_.get();
157  }
158
159 private:
160  virtual ~MyTestURLRequestContextGetter() {}
161
162  scoped_ptr<MyTestURLRequestContext> context_;
163};
164
165// A net log that logs all events by default.
166class MyTestNetLog : public net::NetLog {
167 public:
168  MyTestNetLog() {
169    SetBaseLogLevel(LOG_ALL);
170  }
171  virtual ~MyTestNetLog() {}
172};
173
174// A cert verifier that access all certificates.
175class MyTestCertVerifier : public net::CertVerifier {
176 public:
177  MyTestCertVerifier() {}
178  virtual ~MyTestCertVerifier() {}
179
180  virtual int Verify(net::X509Certificate* cert,
181                     const std::string& hostname,
182                     int flags,
183                     net::CRLSet* crl_set,
184                     net::CertVerifyResult* verify_result,
185                     const net::CompletionCallback& callback,
186                     RequestHandle* out_req,
187                     const net::BoundNetLog& net_log) OVERRIDE {
188    return net::OK;
189  }
190
191  virtual void CancelRequest(RequestHandle req) OVERRIDE {
192    // Do nothing.
193  }
194};
195
196class MCSProbe {
197 public:
198  MCSProbe(
199      const CommandLine& command_line,
200      scoped_refptr<net::URLRequestContextGetter> url_request_context_getter);
201  ~MCSProbe();
202
203  void Start();
204
205  uint64 android_id() const { return android_id_; }
206  uint64 secret() const { return secret_; }
207
208 private:
209  void CheckIn();
210  void InitializeNetworkState();
211  void BuildNetworkSession();
212
213  void LoadCallback(scoped_ptr<GCMStore::LoadResult> load_result);
214  void UpdateCallback(bool success);
215  void ErrorCallback();
216  void OnCheckInCompleted(
217      const checkin_proto::AndroidCheckinResponse& checkin_response);
218  void StartMCSLogin();
219
220  base::DefaultClock clock_;
221
222  CommandLine command_line_;
223
224  base::FilePath gcm_store_path_;
225  uint64 android_id_;
226  uint64 secret_;
227  std::string server_host_;
228  int server_port_;
229
230  // Network state.
231  scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
232  MyTestNetLog net_log_;
233  scoped_ptr<net::NetLogLogger> logger_;
234  scoped_ptr<base::Value> net_constants_;
235  scoped_ptr<net::HostResolver> host_resolver_;
236  scoped_ptr<net::CertVerifier> cert_verifier_;
237  scoped_ptr<net::ChannelIDService> system_channel_id_service_;
238  scoped_ptr<net::TransportSecurityState> transport_security_state_;
239  scoped_ptr<net::URLSecurityManager> url_security_manager_;
240  scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory_;
241  scoped_ptr<net::HttpServerPropertiesImpl> http_server_properties_;
242  scoped_ptr<net::HostMappingRules> host_mapping_rules_;
243  scoped_refptr<net::HttpNetworkSession> network_session_;
244  scoped_ptr<net::ProxyService> proxy_service_;
245
246  FakeGCMStatsRecorder recorder_;
247  scoped_ptr<GCMStore> gcm_store_;
248  scoped_ptr<MCSClient> mcs_client_;
249  scoped_ptr<CheckinRequest> checkin_request_;
250
251  scoped_ptr<ConnectionFactoryImpl> connection_factory_;
252
253  base::Thread file_thread_;
254
255  scoped_ptr<base::RunLoop> run_loop_;
256};
257
258MCSProbe::MCSProbe(
259    const CommandLine& command_line,
260    scoped_refptr<net::URLRequestContextGetter> url_request_context_getter)
261    : command_line_(command_line),
262      gcm_store_path_(base::FilePath(FILE_PATH_LITERAL("gcm_store"))),
263      android_id_(0),
264      secret_(0),
265      server_port_(0),
266      url_request_context_getter_(url_request_context_getter),
267      file_thread_("FileThread") {
268  if (command_line.HasSwitch(kRMQFileName)) {
269    gcm_store_path_ = command_line.GetSwitchValuePath(kRMQFileName);
270  }
271  if (command_line.HasSwitch(kAndroidIdSwitch)) {
272    base::StringToUint64(command_line.GetSwitchValueASCII(kAndroidIdSwitch),
273                         &android_id_);
274  }
275  if (command_line.HasSwitch(kSecretSwitch)) {
276    base::StringToUint64(command_line.GetSwitchValueASCII(kSecretSwitch),
277                         &secret_);
278  }
279  server_host_ = kMCSServerHost;
280  if (command_line.HasSwitch(kServerHostSwitch)) {
281    server_host_ = command_line.GetSwitchValueASCII(kServerHostSwitch);
282  }
283  server_port_ = kMCSServerPort;
284  if (command_line.HasSwitch(kServerPortSwitch)) {
285    base::StringToInt(command_line.GetSwitchValueASCII(kServerPortSwitch),
286                      &server_port_);
287  }
288}
289
290MCSProbe::~MCSProbe() {
291  file_thread_.Stop();
292}
293
294void MCSProbe::Start() {
295  file_thread_.Start();
296  InitializeNetworkState();
297  BuildNetworkSession();
298  std::vector<GURL> endpoints(1,
299                              GURL("https://" +
300                                   net::HostPortPair(server_host_,
301                                                     server_port_).ToString()));
302  connection_factory_.reset(
303      new ConnectionFactoryImpl(endpoints,
304                                kDefaultBackoffPolicy,
305                                network_session_,
306                                NULL,
307                                &net_log_,
308                                &recorder_));
309  gcm_store_.reset(
310      new GCMStoreImpl(gcm_store_path_,
311                       file_thread_.message_loop_proxy(),
312                       make_scoped_ptr<Encryptor>(new FakeEncryptor)));
313  mcs_client_.reset(new MCSClient("probe",
314                                  &clock_,
315                                  connection_factory_.get(),
316                                  gcm_store_.get(),
317                                  &recorder_));
318  run_loop_.reset(new base::RunLoop());
319  gcm_store_->Load(base::Bind(&MCSProbe::LoadCallback,
320                              base::Unretained(this)));
321  run_loop_->Run();
322}
323
324void MCSProbe::LoadCallback(scoped_ptr<GCMStore::LoadResult> load_result) {
325  DCHECK(load_result->success);
326  if (android_id_ != 0 && secret_ != 0) {
327    DVLOG(1) << "Presetting MCS id " << android_id_;
328    load_result->device_android_id = android_id_;
329    load_result->device_security_token = secret_;
330    gcm_store_->SetDeviceCredentials(android_id_,
331                                     secret_,
332                                     base::Bind(&MCSProbe::UpdateCallback,
333                                                base::Unretained(this)));
334  } else {
335    android_id_ = load_result->device_android_id;
336    secret_ = load_result->device_security_token;
337    DVLOG(1) << "Loaded MCS id " << android_id_;
338  }
339  mcs_client_->Initialize(
340      base::Bind(&MCSProbe::ErrorCallback, base::Unretained(this)),
341      base::Bind(&MessageReceivedCallback),
342      base::Bind(&MessageSentCallback),
343      load_result.Pass());
344
345  if (!android_id_ || !secret_) {
346    DVLOG(1) << "Checkin to generate new MCS credentials.";
347    CheckIn();
348    return;
349  }
350
351  StartMCSLogin();
352}
353
354void MCSProbe::UpdateCallback(bool success) {
355}
356
357void MCSProbe::InitializeNetworkState() {
358  FILE* log_file = NULL;
359  if (command_line_.HasSwitch(kLogFileSwitch)) {
360    base::FilePath log_path = command_line_.GetSwitchValuePath(kLogFileSwitch);
361#if defined(OS_WIN)
362    log_file = _wfopen(log_path.value().c_str(), L"w");
363#elif defined(OS_POSIX)
364    log_file = fopen(log_path.value().c_str(), "w");
365#endif
366  }
367  net_constants_.reset(net::NetLogLogger::GetConstants());
368  if (log_file != NULL) {
369    logger_.reset(new net::NetLogLogger(log_file, *net_constants_));
370    logger_->StartObserving(&net_log_);
371  }
372
373  host_resolver_ = net::HostResolver::CreateDefaultResolver(&net_log_);
374
375  if (command_line_.HasSwitch(kIgnoreCertSwitch)) {
376    cert_verifier_.reset(new MyTestCertVerifier());
377  } else {
378    cert_verifier_.reset(net::CertVerifier::CreateDefault());
379  }
380  system_channel_id_service_.reset(
381      new net::ChannelIDService(
382          new net::DefaultChannelIDStore(NULL),
383          base::WorkerPool::GetTaskRunner(true)));
384
385  transport_security_state_.reset(new net::TransportSecurityState());
386  url_security_manager_.reset(net::URLSecurityManager::Create(NULL, NULL));
387  http_auth_handler_factory_.reset(
388      net::HttpAuthHandlerRegistryFactory::Create(
389          std::vector<std::string>(1, "basic"),
390          url_security_manager_.get(),
391          host_resolver_.get(),
392          std::string(),
393          false,
394          false));
395  http_server_properties_.reset(new net::HttpServerPropertiesImpl());
396  host_mapping_rules_.reset(new net::HostMappingRules());
397  proxy_service_.reset(net::ProxyService::CreateDirectWithNetLog(&net_log_));
398}
399
400void MCSProbe::BuildNetworkSession() {
401  net::HttpNetworkSession::Params session_params;
402  session_params.host_resolver = host_resolver_.get();
403  session_params.cert_verifier = cert_verifier_.get();
404  session_params.channel_id_service = system_channel_id_service_.get();
405  session_params.transport_security_state = transport_security_state_.get();
406  session_params.ssl_config_service = new net::SSLConfigServiceDefaults();
407  session_params.http_auth_handler_factory = http_auth_handler_factory_.get();
408  session_params.http_server_properties =
409      http_server_properties_->GetWeakPtr();
410  session_params.network_delegate = NULL;  // TODO(zea): implement?
411  session_params.host_mapping_rules = host_mapping_rules_.get();
412  session_params.ignore_certificate_errors = true;
413  session_params.testing_fixed_http_port = 0;
414  session_params.testing_fixed_https_port = 0;
415  session_params.net_log = &net_log_;
416  session_params.proxy_service = proxy_service_.get();
417
418  network_session_ = new net::HttpNetworkSession(session_params);
419}
420
421void MCSProbe::ErrorCallback() {
422  LOG(INFO) << "MCS error happened";
423}
424
425void MCSProbe::CheckIn() {
426  LOG(INFO) << "Check-in request initiated.";
427  checkin_proto::ChromeBuildProto chrome_build_proto;
428  chrome_build_proto.set_platform(
429      checkin_proto::ChromeBuildProto::PLATFORM_LINUX);
430  chrome_build_proto.set_channel(
431      checkin_proto::ChromeBuildProto::CHANNEL_CANARY);
432  chrome_build_proto.set_chrome_version(kChromeVersion);
433
434  CheckinRequest::RequestInfo request_info(0,
435                                           0,
436                                           std::map<std::string, std::string>(),
437                                           std::string(),
438                                           chrome_build_proto);
439
440  checkin_request_.reset(new CheckinRequest(
441      GServicesSettings::DefaultCheckinURL(),
442      request_info,
443      kDefaultBackoffPolicy,
444      base::Bind(&MCSProbe::OnCheckInCompleted, base::Unretained(this)),
445      url_request_context_getter_.get(),
446      &recorder_));
447  checkin_request_->Start();
448}
449
450void MCSProbe::OnCheckInCompleted(
451    const checkin_proto::AndroidCheckinResponse& checkin_response) {
452  bool success = checkin_response.has_android_id() &&
453                 checkin_response.android_id() != 0UL &&
454                 checkin_response.has_security_token() &&
455                 checkin_response.security_token() != 0UL;
456  LOG(INFO) << "Check-in request completion "
457            << (success ? "success!" : "failure!");
458
459  if (!success)
460    return;
461
462  android_id_ = checkin_response.android_id();
463  secret_ = checkin_response.security_token();
464
465  gcm_store_->SetDeviceCredentials(android_id_,
466                                   secret_,
467                                   base::Bind(&MCSProbe::UpdateCallback,
468                                              base::Unretained(this)));
469
470  StartMCSLogin();
471}
472
473void MCSProbe::StartMCSLogin() {
474  LOG(INFO) << "MCS login initiated.";
475
476  mcs_client_->Login(android_id_, secret_);
477}
478
479int MCSProbeMain(int argc, char* argv[]) {
480  base::AtExitManager exit_manager;
481
482  CommandLine::Init(argc, argv);
483  logging::LoggingSettings settings;
484  settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
485  logging::InitLogging(settings);
486
487  base::MessageLoopForIO message_loop;
488
489  // For check-in and creating registration ids.
490  const scoped_refptr<MyTestURLRequestContextGetter> context_getter =
491      new MyTestURLRequestContextGetter(
492          base::MessageLoop::current()->message_loop_proxy());
493
494  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
495
496  MCSProbe mcs_probe(command_line, context_getter);
497  mcs_probe.Start();
498
499  base::RunLoop run_loop;
500  run_loop.Run();
501
502  return 0;
503}
504
505}  // namespace
506}  // namespace gcm
507
508int main(int argc, char* argv[]) {
509  return gcm::MCSProbeMain(argc, argv);
510}
511