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.
5 #include "google_apis/gcm/engine/gservices_settings.h"
8 #include "base/command_line.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "google_apis/gcm/engine/gservices_switches.h"
16 // The expected time in seconds between periodic checkins.
17 const char kCheckinIntervalKey
[] = "checkin_interval";
18 // The override URL to the checkin server.
19 const char kCheckinURLKey
[] = "checkin_url";
20 // The MCS machine name to connect to.
21 const char kMCSHostnameKey
[] = "gcm_hostname";
22 // The MCS port to connect to.
23 const char kMCSSecurePortKey
[] = "gcm_secure_port";
24 // The URL to get MCS registration IDs.
25 const char kRegistrationURLKey
[] = "gcm_registration_url";
27 const int64 kDefaultCheckinInterval
= 2 * 24 * 60 * 60; // seconds = 2 days.
28 const int64 kMinimumCheckinInterval
= 12 * 60 * 60; // seconds = 12 hours.
29 const char kDefaultCheckinURL
[] = "https://android.clients.google.com/checkin";
30 const char kDefaultMCSHostname
[] = "mtalk.google.com";
31 const int kDefaultMCSMainSecurePort
= 5228;
32 const int kDefaultMCSFallbackSecurePort
= 443;
33 const char kDefaultRegistrationURL
[] =
34 "https://android.clients.google.com/c2dm/register3";
35 // Settings that are to be deleted are marked with this prefix in checkin
37 const char kDeleteSettingPrefix
[] = "delete_";
38 // Settings digest starts with verison number followed by '-'.
39 const char kDigestVersionPrefix
[] = "1-";
40 const char kMCSEnpointTemplate
[] = "https://%s:%d";
41 const int kMaxSecurePort
= 65535;
43 std::string
MakeMCSEndpoint(const std::string
& mcs_hostname
, int port
) {
44 return base::StringPrintf(kMCSEnpointTemplate
, mcs_hostname
.c_str(), port
);
47 // Default settings can be omitted, as GServicesSettings class provides
48 // reasonable defaults.
49 bool CanBeOmitted(const std::string
& settings_name
) {
50 return settings_name
== kCheckinIntervalKey
||
51 settings_name
== kCheckinURLKey
||
52 settings_name
== kMCSHostnameKey
||
53 settings_name
== kMCSSecurePortKey
||
54 settings_name
== kRegistrationURLKey
;
57 bool VerifyCheckinInterval(
58 const gcm::GServicesSettings::SettingsMap
& settings
) {
59 gcm::GServicesSettings::SettingsMap::const_iterator iter
=
60 settings
.find(kCheckinIntervalKey
);
61 if (iter
== settings
.end())
62 return CanBeOmitted(kCheckinIntervalKey
);
64 int64 checkin_interval
= kMinimumCheckinInterval
;
65 if (!base::StringToInt64(iter
->second
, &checkin_interval
)) {
66 DVLOG(1) << "Failed to parse checkin interval: " << iter
->second
;
69 if (checkin_interval
== std::numeric_limits
<int64
>::max()) {
70 DVLOG(1) << "Checkin interval is too big: " << checkin_interval
;
73 if (checkin_interval
< kMinimumCheckinInterval
) {
74 DVLOG(1) << "Checkin interval: " << checkin_interval
75 << " is less than allowed minimum: " << kMinimumCheckinInterval
;
81 bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap
& settings
) {
82 std::string mcs_hostname
;
83 gcm::GServicesSettings::SettingsMap::const_iterator iter
=
84 settings
.find(kMCSHostnameKey
);
85 if (iter
== settings
.end()) {
86 // Because endpoint has 2 parts (hostname and port) we are defaulting and
87 // moving on with verification.
88 if (CanBeOmitted(kMCSHostnameKey
))
89 mcs_hostname
= kDefaultMCSHostname
;
92 } else if (iter
->second
.empty()) {
93 DVLOG(1) << "Empty MCS hostname provided.";
96 mcs_hostname
= iter
->second
;
99 int mcs_secure_port
= 0;
100 iter
= settings
.find(kMCSSecurePortKey
);
101 if (iter
== settings
.end()) {
102 // Simlarly we might have to default the port, when only hostname is
104 if (CanBeOmitted(kMCSSecurePortKey
))
105 mcs_secure_port
= kDefaultMCSMainSecurePort
;
108 } else if (!base::StringToInt(iter
->second
, &mcs_secure_port
)) {
109 DVLOG(1) << "Failed to parse MCS secure port: " << iter
->second
;
113 if (mcs_secure_port
< 0 || mcs_secure_port
> kMaxSecurePort
) {
114 DVLOG(1) << "Incorrect port value: " << mcs_secure_port
;
118 GURL
mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname
, mcs_secure_port
));
119 if (!mcs_main_endpoint
.is_valid()) {
120 DVLOG(1) << "Invalid main MCS endpoint: "
121 << mcs_main_endpoint
.possibly_invalid_spec();
124 GURL
mcs_fallback_endpoint(
125 MakeMCSEndpoint(mcs_hostname
, kDefaultMCSFallbackSecurePort
));
126 if (!mcs_fallback_endpoint
.is_valid()) {
127 DVLOG(1) << "Invalid fallback MCS endpoint: "
128 << mcs_fallback_endpoint
.possibly_invalid_spec();
135 bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap
& settings
) {
136 gcm::GServicesSettings::SettingsMap::const_iterator iter
=
137 settings
.find(kCheckinURLKey
);
138 if (iter
== settings
.end())
139 return CanBeOmitted(kCheckinURLKey
);
141 GURL
checkin_url(iter
->second
);
142 if (!checkin_url
.is_valid()) {
143 DVLOG(1) << "Invalid checkin URL provided: " << iter
->second
;
150 bool VerifyRegistrationURL(
151 const gcm::GServicesSettings::SettingsMap
& settings
) {
152 gcm::GServicesSettings::SettingsMap::const_iterator iter
=
153 settings
.find(kRegistrationURLKey
);
154 if (iter
== settings
.end())
155 return CanBeOmitted(kRegistrationURLKey
);
157 GURL
registration_url(iter
->second
);
158 if (!registration_url
.is_valid()) {
159 DVLOG(1) << "Invalid registration URL provided: " << iter
->second
;
166 bool VerifySettings(const gcm::GServicesSettings::SettingsMap
& settings
) {
167 return VerifyCheckinInterval(settings
) && VerifyMCSEndpoint(settings
) &&
168 VerifyCheckinURL(settings
) && VerifyRegistrationURL(settings
);
176 const base::TimeDelta
GServicesSettings::MinimumCheckinInterval() {
177 return base::TimeDelta::FromSeconds(kMinimumCheckinInterval
);
181 const GURL
GServicesSettings::DefaultCheckinURL() {
182 return GURL(kDefaultCheckinURL
);
186 std::string
GServicesSettings::CalculateDigest(const SettingsMap
& settings
) {
187 unsigned char hash
[base::kSHA1Length
];
189 for (SettingsMap::const_iterator iter
= settings
.begin();
190 iter
!= settings
.end();
194 data
+= iter
->second
;
198 reinterpret_cast<const unsigned char*>(&data
[0]), data
.size(), hash
);
200 kDigestVersionPrefix
+ base::HexEncode(hash
, base::kSHA1Length
);
201 digest
= base::ToLowerASCII(digest
);
205 GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) {
206 digest_
= CalculateDigest(settings_
);
209 GServicesSettings::~GServicesSettings() {
212 bool GServicesSettings::UpdateFromCheckinResponse(
213 const checkin_proto::AndroidCheckinResponse
& checkin_response
) {
214 if (!checkin_response
.has_settings_diff()) {
215 DVLOG(1) << "Field settings_diff not set in response.";
219 bool settings_diff
= checkin_response
.settings_diff();
220 SettingsMap new_settings
;
221 // Only reuse the existing settings, if we are given a settings difference.
223 new_settings
= settings_map();
225 for (int i
= 0; i
< checkin_response
.setting_size(); ++i
) {
226 std::string name
= checkin_response
.setting(i
).name();
228 DVLOG(1) << "Setting name is empty";
232 if (settings_diff
&& name
.find(kDeleteSettingPrefix
) == 0) {
233 std::string setting_to_delete
=
234 name
.substr(arraysize(kDeleteSettingPrefix
) - 1);
235 new_settings
.erase(setting_to_delete
);
236 DVLOG(1) << "Setting deleted: " << setting_to_delete
;
238 std::string value
= checkin_response
.setting(i
).value();
239 new_settings
[name
] = value
;
240 DVLOG(1) << "New setting: '" << name
<< "' : '" << value
<< "'";
244 if (!VerifySettings(new_settings
))
247 settings_
.swap(new_settings
);
248 digest_
= CalculateDigest(settings_
);
252 void GServicesSettings::UpdateFromLoadResult(
253 const GCMStore::LoadResult
& load_result
) {
254 // No need to try to update settings when load_result is empty.
255 if (load_result
.gservices_settings
.empty())
257 if (!VerifySettings(load_result
.gservices_settings
))
259 std::string digest
= CalculateDigest(load_result
.gservices_settings
);
260 if (digest
!= load_result
.gservices_digest
) {
261 DVLOG(1) << "G-services settings digest mismatch. "
262 << "Expected digest: " << load_result
.gservices_digest
263 << ". Calculated digest is: " << digest
;
267 settings_
= load_result
.gservices_settings
;
268 digest_
= load_result
.gservices_digest
;
271 base::TimeDelta
GServicesSettings::GetCheckinInterval() const {
272 int64 checkin_interval
= kMinimumCheckinInterval
;
273 SettingsMap::const_iterator iter
= settings_
.find(kCheckinIntervalKey
);
274 if (iter
== settings_
.end() ||
275 !base::StringToInt64(iter
->second
, &checkin_interval
)) {
276 checkin_interval
= kDefaultCheckinInterval
;
279 if (checkin_interval
< kMinimumCheckinInterval
)
280 checkin_interval
= kMinimumCheckinInterval
;
282 return base::TimeDelta::FromSeconds(checkin_interval
);
285 GURL
GServicesSettings::GetCheckinURL() const {
286 SettingsMap::const_iterator iter
= settings_
.find(kCheckinURLKey
);
287 if (iter
== settings_
.end() || iter
->second
.empty())
288 return GURL(kDefaultCheckinURL
);
289 return GURL(iter
->second
);
292 GURL
GServicesSettings::GetMCSMainEndpoint() const {
293 // Get alternative hostname or use default.
294 std::string mcs_hostname
;
295 SettingsMap::const_iterator iter
= settings_
.find(kMCSHostnameKey
);
296 if (iter
!= settings_
.end() && !iter
->second
.empty())
297 mcs_hostname
= iter
->second
;
299 mcs_hostname
= kDefaultMCSHostname
;
301 // Get alternative secure port or use defualt.
302 int mcs_secure_port
= 0;
303 iter
= settings_
.find(kMCSSecurePortKey
);
304 if (iter
== settings_
.end() || iter
->second
.empty() ||
305 !base::StringToInt(iter
->second
, &mcs_secure_port
)) {
306 mcs_secure_port
= kDefaultMCSMainSecurePort
;
309 // If constructed address makes sense use it.
310 GURL
mcs_endpoint(MakeMCSEndpoint(mcs_hostname
, mcs_secure_port
));
311 if (mcs_endpoint
.is_valid())
314 // Otherwise use default settings.
315 return GURL(MakeMCSEndpoint(kDefaultMCSHostname
, kDefaultMCSMainSecurePort
));
318 GURL
GServicesSettings::GetMCSFallbackEndpoint() const {
319 // Get alternative hostname or use default.
320 std::string mcs_hostname
;
321 SettingsMap::const_iterator iter
= settings_
.find(kMCSHostnameKey
);
322 if (iter
!= settings_
.end() && !iter
->second
.empty())
323 mcs_hostname
= iter
->second
;
325 mcs_hostname
= kDefaultMCSHostname
;
327 // If constructed address makes sense use it.
329 MakeMCSEndpoint(mcs_hostname
, kDefaultMCSFallbackSecurePort
));
330 if (mcs_endpoint
.is_valid())
334 MakeMCSEndpoint(kDefaultMCSHostname
, kDefaultMCSFallbackSecurePort
));
337 GURL
GServicesSettings::GetRegistrationURL() const {
338 SettingsMap::const_iterator iter
= settings_
.find(kRegistrationURLKey
);
339 if (iter
== settings_
.end() || iter
->second
.empty()) {
340 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
341 if (!command_line
->HasSwitch(switches::kGCMRegistrationURL
))
342 return GURL(kDefaultRegistrationURL
);
345 command_line
->GetSwitchValueASCII(switches::kGCMRegistrationURL
));
347 return GURL(iter
->second
);