1// Copyright 2014 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/gservices_settings.h"
6
7#include "base/bind.h"
8#include "base/sha1.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_util.h"
11#include "base/strings/stringprintf.h"
12
13namespace {
14// The expected time in seconds between periodic checkins.
15const char kCheckinIntervalKey[] = "checkin_interval";
16// The override URL to the checkin server.
17const char kCheckinURLKey[] = "checkin_url";
18// The MCS machine name to connect to.
19const char kMCSHostnameKey[] = "gcm_hostname";
20// The MCS port to connect to.
21const char kMCSSecurePortKey[] = "gcm_secure_port";
22// The URL to get MCS registration IDs.
23const char kRegistrationURLKey[] = "gcm_registration_url";
24
25const int64 kDefaultCheckinInterval = 2 * 24 * 60 * 60;  // seconds = 2 days.
26const int64 kMinimumCheckinInterval = 12 * 60 * 60;      // seconds = 12 hours.
27const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin";
28const char kDefaultMCSHostname[] = "mtalk.google.com";
29const int kDefaultMCSMainSecurePort = 5228;
30const int kDefaultMCSFallbackSecurePort = 443;
31const char kDefaultRegistrationURL[] =
32    "https://android.clients.google.com/c2dm/register3";
33// Settings that are to be deleted are marked with this prefix in checkin
34// response.
35const char kDeleteSettingPrefix[] = "delete_";
36// Settings digest starts with verison number followed by '-'.
37const char kDigestVersionPrefix[] = "1-";
38const char kMCSEnpointTemplate[] = "https://%s:%d";
39const int kMaxSecurePort = 65535;
40
41std::string MakeMCSEndpoint(const std::string& mcs_hostname, int port) {
42  return base::StringPrintf(kMCSEnpointTemplate, mcs_hostname.c_str(), port);
43}
44
45// Default settings can be omitted, as GServicesSettings class provides
46// reasonable defaults.
47bool CanBeOmitted(const std::string& settings_name) {
48  return settings_name == kCheckinIntervalKey ||
49         settings_name == kCheckinURLKey ||
50         settings_name == kMCSHostnameKey ||
51         settings_name == kMCSSecurePortKey ||
52         settings_name == kRegistrationURLKey;
53}
54
55bool VerifyCheckinInterval(
56    const gcm::GServicesSettings::SettingsMap& settings) {
57  gcm::GServicesSettings::SettingsMap::const_iterator iter =
58      settings.find(kCheckinIntervalKey);
59  if (iter == settings.end())
60    return CanBeOmitted(kCheckinIntervalKey);
61
62  int64 checkin_interval = kMinimumCheckinInterval;
63  if (!base::StringToInt64(iter->second, &checkin_interval)) {
64    DVLOG(1) << "Failed to parse checkin interval: " << iter->second;
65    return false;
66  }
67  if (checkin_interval == std::numeric_limits<int64>::max()) {
68    DVLOG(1) << "Checkin interval is too big: " << checkin_interval;
69    return false;
70  }
71  if (checkin_interval < kMinimumCheckinInterval) {
72    DVLOG(1) << "Checkin interval: " << checkin_interval
73             << " is less than allowed minimum: " << kMinimumCheckinInterval;
74  }
75
76  return true;
77}
78
79bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap& settings) {
80  std::string mcs_hostname;
81  gcm::GServicesSettings::SettingsMap::const_iterator iter =
82      settings.find(kMCSHostnameKey);
83  if (iter == settings.end()) {
84    // Because endpoint has 2 parts (hostname and port) we are defaulting and
85    // moving on with verification.
86    if (CanBeOmitted(kMCSHostnameKey))
87      mcs_hostname = kDefaultMCSHostname;
88    else
89      return false;
90  } else if (iter->second.empty()) {
91    DVLOG(1) << "Empty MCS hostname provided.";
92    return false;
93  } else {
94    mcs_hostname = iter->second;
95  }
96
97  int mcs_secure_port = 0;
98  iter = settings.find(kMCSSecurePortKey);
99  if (iter == settings.end()) {
100    // Simlarly we might have to default the port, when only hostname is
101    // provided.
102    if (CanBeOmitted(kMCSSecurePortKey))
103      mcs_secure_port = kDefaultMCSMainSecurePort;
104    else
105      return false;
106  } else if (!base::StringToInt(iter->second, &mcs_secure_port)) {
107    DVLOG(1) << "Failed to parse MCS secure port: " << iter->second;
108    return false;
109  }
110
111  if (mcs_secure_port < 0 || mcs_secure_port > kMaxSecurePort) {
112    DVLOG(1) << "Incorrect port value: " << mcs_secure_port;
113    return false;
114  }
115
116  GURL mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port));
117  if (!mcs_main_endpoint.is_valid()) {
118    DVLOG(1) << "Invalid main MCS endpoint: "
119             << mcs_main_endpoint.possibly_invalid_spec();
120    return false;
121  }
122  GURL mcs_fallback_endpoint(
123      MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort));
124  if (!mcs_fallback_endpoint.is_valid()) {
125    DVLOG(1) << "Invalid fallback MCS endpoint: "
126             << mcs_fallback_endpoint.possibly_invalid_spec();
127    return false;
128  }
129
130  return true;
131}
132
133bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap& settings) {
134  gcm::GServicesSettings::SettingsMap::const_iterator iter =
135      settings.find(kCheckinURLKey);
136  if (iter == settings.end())
137    return CanBeOmitted(kCheckinURLKey);
138
139  GURL checkin_url(iter->second);
140  if (!checkin_url.is_valid()) {
141    DVLOG(1) << "Invalid checkin URL provided: " << iter->second;
142    return false;
143  }
144
145  return true;
146}
147
148bool VerifyRegistrationURL(
149    const gcm::GServicesSettings::SettingsMap& settings) {
150  gcm::GServicesSettings::SettingsMap::const_iterator iter =
151      settings.find(kRegistrationURLKey);
152  if (iter == settings.end())
153    return CanBeOmitted(kRegistrationURLKey);
154
155  GURL registration_url(iter->second);
156  if (!registration_url.is_valid()) {
157    DVLOG(1) << "Invalid registration URL provided: " << iter->second;
158    return false;
159  }
160
161  return true;
162}
163
164bool VerifySettings(const gcm::GServicesSettings::SettingsMap& settings) {
165  return VerifyCheckinInterval(settings) && VerifyMCSEndpoint(settings) &&
166         VerifyCheckinURL(settings) && VerifyRegistrationURL(settings);
167}
168
169}  // namespace
170
171namespace gcm {
172
173// static
174const base::TimeDelta GServicesSettings::MinimumCheckinInterval() {
175  return base::TimeDelta::FromSeconds(kMinimumCheckinInterval);
176}
177
178// static
179const GURL GServicesSettings::DefaultCheckinURL() {
180  return GURL(kDefaultCheckinURL);
181}
182
183// static
184std::string GServicesSettings::CalculateDigest(const SettingsMap& settings) {
185  unsigned char hash[base::kSHA1Length];
186  std::string data;
187  for (SettingsMap::const_iterator iter = settings.begin();
188       iter != settings.end();
189       ++iter) {
190    data += iter->first;
191    data += '\0';
192    data += iter->second;
193    data += '\0';
194  }
195  base::SHA1HashBytes(
196      reinterpret_cast<const unsigned char*>(&data[0]), data.size(), hash);
197  std::string digest =
198      kDigestVersionPrefix + base::HexEncode(hash, base::kSHA1Length);
199  digest = base::StringToLowerASCII(digest);
200  return digest;
201}
202
203GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) {
204  digest_ = CalculateDigest(settings_);
205}
206
207GServicesSettings::~GServicesSettings() {
208}
209
210bool GServicesSettings::UpdateFromCheckinResponse(
211    const checkin_proto::AndroidCheckinResponse& checkin_response) {
212  if (!checkin_response.has_settings_diff()) {
213    DVLOG(1) << "Field settings_diff not set in response.";
214    return false;
215  }
216
217  bool settings_diff = checkin_response.settings_diff();
218  SettingsMap new_settings;
219  // Only reuse the existing settings, if we are given a settings difference.
220  if (settings_diff)
221    new_settings = settings_map();
222
223  for (int i = 0; i < checkin_response.setting_size(); ++i) {
224    std::string name = checkin_response.setting(i).name();
225    if (name.empty()) {
226      DVLOG(1) << "Setting name is empty";
227      return false;
228    }
229
230    if (settings_diff && name.find(kDeleteSettingPrefix) == 0) {
231      std::string setting_to_delete =
232          name.substr(arraysize(kDeleteSettingPrefix) - 1);
233      new_settings.erase(setting_to_delete);
234      DVLOG(1) << "Setting deleted: " << setting_to_delete;
235    } else {
236      std::string value = checkin_response.setting(i).value();
237      new_settings[name] = value;
238      DVLOG(1) << "New setting: '" << name << "' : '" << value << "'";
239    }
240  }
241
242  if (!VerifySettings(new_settings))
243    return false;
244
245  settings_.swap(new_settings);
246  digest_ = CalculateDigest(settings_);
247  return true;
248}
249
250void GServicesSettings::UpdateFromLoadResult(
251    const GCMStore::LoadResult& load_result) {
252  // No need to try to update settings when load_result is empty.
253  if (load_result.gservices_settings.empty())
254    return;
255  if (!VerifySettings(load_result.gservices_settings))
256    return;
257  std::string digest = CalculateDigest(load_result.gservices_settings);
258  if (digest != load_result.gservices_digest) {
259    DVLOG(1) << "G-services settings digest mismatch. "
260             << "Expected digest: " << load_result.gservices_digest
261             << ". Calculated digest is: " << digest;
262    return;
263  }
264
265  settings_ = load_result.gservices_settings;
266  digest_ = load_result.gservices_digest;
267}
268
269base::TimeDelta GServicesSettings::GetCheckinInterval() const {
270  int64 checkin_interval = kMinimumCheckinInterval;
271  SettingsMap::const_iterator iter = settings_.find(kCheckinIntervalKey);
272  if (iter == settings_.end() ||
273      !base::StringToInt64(iter->second, &checkin_interval)) {
274    checkin_interval = kDefaultCheckinInterval;
275  }
276
277  if (checkin_interval < kMinimumCheckinInterval)
278    checkin_interval = kMinimumCheckinInterval;
279
280  return base::TimeDelta::FromSeconds(checkin_interval);
281}
282
283GURL GServicesSettings::GetCheckinURL() const {
284  SettingsMap::const_iterator iter = settings_.find(kCheckinURLKey);
285  if (iter == settings_.end() || iter->second.empty())
286    return GURL(kDefaultCheckinURL);
287  return GURL(iter->second);
288}
289
290GURL GServicesSettings::GetMCSMainEndpoint() const {
291  // Get alternative hostname or use default.
292  std::string mcs_hostname;
293  SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey);
294  if (iter != settings_.end() && !iter->second.empty())
295    mcs_hostname = iter->second;
296  else
297    mcs_hostname = kDefaultMCSHostname;
298
299  // Get alternative secure port or use defualt.
300  int mcs_secure_port = 0;
301  iter = settings_.find(kMCSSecurePortKey);
302  if (iter == settings_.end() || iter->second.empty() ||
303      !base::StringToInt(iter->second, &mcs_secure_port)) {
304    mcs_secure_port = kDefaultMCSMainSecurePort;
305  }
306
307  // If constructed address makes sense use it.
308  GURL mcs_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port));
309  if (mcs_endpoint.is_valid())
310    return mcs_endpoint;
311
312  // Otherwise use default settings.
313  return GURL(MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSMainSecurePort));
314}
315
316GURL GServicesSettings::GetMCSFallbackEndpoint() const {
317  // Get alternative hostname or use default.
318  std::string mcs_hostname;
319  SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey);
320  if (iter != settings_.end() && !iter->second.empty())
321    mcs_hostname = iter->second;
322  else
323    mcs_hostname = kDefaultMCSHostname;
324
325  // If constructed address makes sense use it.
326  GURL mcs_endpoint(
327      MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort));
328  if (mcs_endpoint.is_valid())
329    return mcs_endpoint;
330
331  return GURL(
332      MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSFallbackSecurePort));
333}
334
335GURL GServicesSettings::GetRegistrationURL() const {
336  SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey);
337  if (iter == settings_.end() || iter->second.empty())
338    return GURL(kDefaultRegistrationURL);
339  return GURL(iter->second);
340}
341
342}  // namespace gcm
343