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