Index: google_apis/gcm/engine/gservices_settings.cc |
diff --git a/google_apis/gcm/engine/gservices_settings.cc b/google_apis/gcm/engine/gservices_settings.cc |
index 55cfa391392373f3326e0a8d34842a4095f1e3c7..4e989724b7e7333abd7e4cf53dd64e6ee841d62a 100644 |
--- a/google_apis/gcm/engine/gservices_settings.cc |
+++ b/google_apis/gcm/engine/gservices_settings.cc |
@@ -5,7 +5,9 @@ |
#include "google_apis/gcm/engine/gservices_settings.h" |
#include "base/bind.h" |
+#include "base/sha1.h" |
#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_util.h" |
#include "base/strings/stringprintf.h" |
namespace { |
@@ -28,12 +30,142 @@ const int kDefaultMCSMainSecurePort = 5228; |
const int kDefaultMCSFallbackSecurePort = 443; |
const char kDefaultRegistrationURL[] = |
"https://android.clients.google.com/c2dm/register3"; |
+// Settings that are to be deleted are marked with this prefix in checkin |
+// response. |
+const char kDeleteSettingPrefix[] = "delete_"; |
+// Settings digest starts with verison number followed by '-'. |
+const char kDigestVersionPrefix[] = "1-"; |
const char kMCSEnpointTemplate[] = "https://%s:%d"; |
+const int kMaxSecurePort = 65535; |
std::string MakeMCSEndpoint(const std::string& mcs_hostname, int port) { |
return base::StringPrintf(kMCSEnpointTemplate, mcs_hostname.c_str(), port); |
} |
+// Default settings can be omitted, as GServicesSettings class provides |
+// reasonable defaults. |
+bool CanBeOmitted(const std::string& settings_name) { |
+ return settings_name == kCheckinIntervalKey || |
+ settings_name == kCheckinURLKey || |
+ settings_name == kMCSHostnameKey || |
+ settings_name == kMCSSecurePortKey || |
+ settings_name == kRegistrationURLKey; |
+} |
+ |
+bool VerifyCheckinInterval( |
+ const gcm::GServicesSettings::SettingsMap& settings) { |
+ gcm::GServicesSettings::SettingsMap::const_iterator iter = |
+ settings.find(kCheckinIntervalKey); |
+ if (iter == settings.end()) |
+ return CanBeOmitted(kCheckinIntervalKey); |
+ |
+ int64 checkin_interval = kMinimumCheckinInterval; |
+ if (!base::StringToInt64(iter->second, &checkin_interval)) { |
+ DVLOG(1) << "Failed to parse checkin interval: " << iter->second; |
+ return false; |
+ } |
+ if (checkin_interval == std::numeric_limits<int64>::max()) { |
+ DVLOG(1) << "Checkin interval is too big: " << checkin_interval; |
+ return false; |
+ } |
+ if (checkin_interval < kMinimumCheckinInterval) { |
+ DVLOG(1) << "Checkin interval: " << checkin_interval |
+ << " is less than allowed minimum: " << kMinimumCheckinInterval; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap& settings) { |
+ std::string mcs_hostname; |
+ gcm::GServicesSettings::SettingsMap::const_iterator iter = |
+ settings.find(kMCSHostnameKey); |
+ if (iter == settings.end()) { |
+ // Because endpoint has 2 parts (hostname and port) we are defaulting and |
+ // moving on with verification. |
+ if (CanBeOmitted(kMCSHostnameKey)) |
+ mcs_hostname = kDefaultMCSHostname; |
+ else |
+ return false; |
+ } else if (iter->second.empty()) { |
+ DVLOG(1) << "Empty MCS hostname provided."; |
+ return false; |
+ } else { |
+ mcs_hostname = iter->second; |
+ } |
+ |
+ int mcs_secure_port = 0; |
+ iter = settings.find(kMCSSecurePortKey); |
+ if (iter == settings.end()) { |
+ // Simlarly we might have to default the port, when only hostname is |
+ // provided. |
+ if (CanBeOmitted(kMCSSecurePortKey)) |
+ mcs_secure_port = kDefaultMCSMainSecurePort; |
+ else |
+ return false; |
+ } else if (!base::StringToInt(iter->second, &mcs_secure_port)) { |
+ DVLOG(1) << "Failed to parse MCS secure port: " << iter->second; |
+ return false; |
+ } |
+ |
+ if (mcs_secure_port < 0 || mcs_secure_port > kMaxSecurePort) { |
+ DVLOG(1) << "Incorrect port value: " << mcs_secure_port; |
+ return false; |
+ } |
+ |
+ GURL mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port)); |
+ if (!mcs_main_endpoint.is_valid()) { |
+ DVLOG(1) << "Invalid main MCS endpoint: " |
+ << mcs_main_endpoint.possibly_invalid_spec(); |
+ return false; |
+ } |
+ GURL mcs_fallback_endpoint( |
+ MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort)); |
+ if (!mcs_fallback_endpoint.is_valid()) { |
+ DVLOG(1) << "Invalid fallback MCS endpoint: " |
+ << mcs_fallback_endpoint.possibly_invalid_spec(); |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap& settings) { |
+ gcm::GServicesSettings::SettingsMap::const_iterator iter = |
+ settings.find(kCheckinURLKey); |
+ if (iter == settings.end()) |
+ return CanBeOmitted(kCheckinURLKey); |
+ |
+ GURL checkin_url(iter->second); |
+ if (!checkin_url.is_valid()) { |
+ DVLOG(1) << "Invalid checkin URL provided: " << iter->second; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool VerifyRegistrationURL( |
+ const gcm::GServicesSettings::SettingsMap& settings) { |
+ gcm::GServicesSettings::SettingsMap::const_iterator iter = |
+ settings.find(kRegistrationURLKey); |
+ if (iter == settings.end()) |
+ return CanBeOmitted(kRegistrationURLKey); |
+ |
+ GURL registration_url(iter->second); |
+ if (!registration_url.is_valid()) { |
+ DVLOG(1) << "Invalid registration URL provided: " << iter->second; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool VerifySettings(const gcm::GServicesSettings::SettingsMap& settings) { |
+ return VerifyCheckinInterval(settings) && VerifyMCSEndpoint(settings) && |
+ VerifyCheckinURL(settings) && VerifyRegistrationURL(settings); |
+} |
+ |
} // namespace |
namespace gcm { |
@@ -48,152 +180,163 @@ const GURL GServicesSettings::DefaultCheckinURL() { |
return GURL(kDefaultCheckinURL); |
} |
-GServicesSettings::GServicesSettings() |
- : checkin_interval_(base::TimeDelta::FromSeconds(kDefaultCheckinInterval)), |
- checkin_url_(kDefaultCheckinURL), |
- mcs_main_endpoint_(MakeMCSEndpoint(kDefaultMCSHostname, |
- kDefaultMCSMainSecurePort)), |
- mcs_fallback_endpoint_(MakeMCSEndpoint(kDefaultMCSHostname, |
- kDefaultMCSFallbackSecurePort)), |
- registration_url_(kDefaultRegistrationURL), |
- weak_ptr_factory_(this) { |
+// static |
+std::string GServicesSettings::CalculateDigest(const SettingsMap& settings) { |
+ unsigned char hash[base::kSHA1Length]; |
+ std::string data; |
+ for (SettingsMap::const_iterator iter = settings.begin(); |
+ iter != settings.end(); |
+ ++iter) { |
+ data += iter->first; |
+ data += '\0'; |
+ data += iter->second; |
+ data += '\0'; |
+ } |
+ base::SHA1HashBytes( |
+ reinterpret_cast<const unsigned char*>(&data[0]), data.size(), hash); |
+ std::string digest = |
+ kDigestVersionPrefix + base::HexEncode(hash, base::kSHA1Length); |
+ digest = StringToLowerASCII(digest); |
+ return digest; |
+} |
+ |
+GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) { |
+ digest_ = CalculateDigest(settings_); |
} |
-GServicesSettings::~GServicesSettings() {} |
+GServicesSettings::~GServicesSettings() { |
+} |
bool GServicesSettings::UpdateFromCheckinResponse( |
- const checkin_proto::AndroidCheckinResponse& checkin_response) { |
- if (!checkin_response.has_digest() || |
- checkin_response.digest() == digest_) { |
- // There are no changes as digest is the same or no settings provided. |
+ const checkin_proto::AndroidCheckinResponse& checkin_response) { |
+ if (!checkin_response.has_settings_diff()) { |
+ DVLOG(1) << "Field settings_diff not set in response."; |
return false; |
} |
- std::map<std::string, std::string> settings; |
+ bool settings_diff = checkin_response.settings_diff(); |
+ SettingsMap new_settings; |
+ // Only reuse the existing settings, if we are given a settings difference. |
+ if (settings_diff) |
+ new_settings = settings_map(); |
+ |
for (int i = 0; i < checkin_response.setting_size(); ++i) { |
std::string name = checkin_response.setting(i).name(); |
- std::string value = checkin_response.setting(i).value(); |
- settings[name] = value; |
- } |
+ if (name.empty()) { |
+ DVLOG(1) << "Setting name is empty"; |
+ return false; |
+ } |
- // Only update the settings in store and digest, if the settings actually |
- // passed the verificaiton in update settings. |
- if (UpdateSettings(settings)) { |
- digest_ = checkin_response.digest(); |
- return true; |
+ if (settings_diff && name.find(kDeleteSettingPrefix) == 0) { |
+ std::string setting_to_delete = |
+ name.substr(arraysize(kDeleteSettingPrefix) - 1); |
+ new_settings.erase(setting_to_delete); |
+ DVLOG(1) << "Setting deleted: " << setting_to_delete; |
+ } else { |
+ std::string value = checkin_response.setting(i).value(); |
+ new_settings[name] = value; |
+ DVLOG(1) << "New setting: '" << name << "' : '" << value << "'"; |
+ } |
} |
- return false; |
+ if (!VerifySettings(new_settings)) |
+ return false; |
+ |
+ settings_.swap(new_settings); |
+ digest_ = CalculateDigest(settings_); |
+ return true; |
} |
void GServicesSettings::UpdateFromLoadResult( |
const GCMStore::LoadResult& load_result) { |
- if (UpdateSettings(load_result.gservices_settings)) |
- digest_ = load_result.gservices_digest; |
-} |
+ // No need to try to update settings when load_result is empty. |
+ if (load_result.gservices_settings.empty()) |
+ return; |
+ if (!VerifySettings(load_result.gservices_settings)) |
+ return; |
+ std::string digest = CalculateDigest(load_result.gservices_settings); |
+ if (digest != load_result.gservices_digest) { |
+ DVLOG(1) << "G-services settings digest mismatch. " |
+ << "Expected digest: " << load_result.gservices_digest |
+ << ". Calculated digest is: " << digest; |
+ return; |
+ } |
-std::map<std::string, std::string> GServicesSettings::GetSettingsMap() const { |
- std::map<std::string, std::string> settings; |
- settings[kCheckinIntervalKey] = |
- base::Int64ToString(checkin_interval_.InSeconds()); |
- settings[kCheckinURLKey] = checkin_url_.spec(); |
- settings[kMCSHostnameKey] = mcs_main_endpoint_.host(); |
- settings[kMCSSecurePortKey] = mcs_main_endpoint_.port(); |
- settings[kRegistrationURLKey] = registration_url_.spec(); |
- return settings; |
+ settings_ = load_result.gservices_settings; |
+ digest_ = load_result.gservices_digest; |
} |
-bool GServicesSettings::UpdateSettings( |
- const std::map<std::string, std::string>& settings) { |
- int64 new_checkin_interval = kMinimumCheckinInterval; |
- std::map<std::string, std::string>::const_iterator iter = |
- settings.find(kCheckinIntervalKey); |
- if (iter == settings.end()) { |
- LOG(ERROR) << "Setting not found: " << kCheckinIntervalKey; |
- return false; |
- } |
- if (!base::StringToInt64(iter->second, &new_checkin_interval)) { |
- LOG(ERROR) << "Failed to parse checkin interval: " << iter->second; |
- return false; |
- } |
- if (new_checkin_interval < kMinimumCheckinInterval) { |
- LOG(ERROR) << "Checkin interval: " << new_checkin_interval |
- << " is less than allowed minimum: " << kMinimumCheckinInterval; |
- new_checkin_interval = kMinimumCheckinInterval; |
- } |
- if (new_checkin_interval == std::numeric_limits<int64>::max()) { |
- LOG(ERROR) << "Checkin interval is too big: " << new_checkin_interval; |
- return false; |
+base::TimeDelta GServicesSettings::GetCheckinInterval() const { |
+ int64 checkin_interval = kMinimumCheckinInterval; |
+ SettingsMap::const_iterator iter = settings_.find(kCheckinIntervalKey); |
+ if (iter == settings_.end() || |
+ !base::StringToInt64(iter->second, &checkin_interval)) { |
+ checkin_interval = kDefaultCheckinInterval; |
} |
- std::string new_mcs_hostname; |
- iter = settings.find(kMCSHostnameKey); |
- if (iter == settings.end()) { |
- LOG(ERROR) << "Setting not found: " << kMCSHostnameKey; |
- return false; |
- } |
- new_mcs_hostname = iter->second; |
- if (new_mcs_hostname.empty()) { |
- LOG(ERROR) << "Empty MCS hostname provided."; |
- return false; |
- } |
+ if (checkin_interval < kMinimumCheckinInterval) |
+ checkin_interval = kMinimumCheckinInterval; |
- int new_mcs_secure_port = -1; |
- iter = settings.find(kMCSSecurePortKey); |
- if (iter == settings.end()) { |
- LOG(ERROR) << "Setting not found: " << kMCSSecurePortKey; |
- return false; |
- } |
- if (!base::StringToInt(iter->second, &new_mcs_secure_port)) { |
- LOG(ERROR) << "Failed to parse MCS secure port: " << iter->second; |
- return false; |
- } |
- if (new_mcs_secure_port < 0 || 65535 < new_mcs_secure_port) { |
- LOG(ERROR) << "Incorrect port value: " << new_mcs_secure_port; |
- return false; |
- } |
+ return base::TimeDelta::FromSeconds(checkin_interval); |
+} |
- GURL new_mcs_main_endpoint = |
- GURL(MakeMCSEndpoint(new_mcs_hostname, new_mcs_secure_port)); |
- GURL new_mcs_fallback_endpoint = |
- GURL(MakeMCSEndpoint(new_mcs_hostname, kDefaultMCSFallbackSecurePort)); |
- if (!new_mcs_main_endpoint.is_valid() || |
- !new_mcs_fallback_endpoint.is_valid()) |
- return false; |
+GURL GServicesSettings::GetCheckinURL() const { |
+ SettingsMap::const_iterator iter = settings_.find(kCheckinURLKey); |
+ if (iter == settings_.end() || iter->second.empty()) |
+ return GURL(kDefaultCheckinURL); |
+ return GURL(iter->second); |
+} |
- GURL new_checkin_url; |
- iter = settings.find(kCheckinURLKey); |
- if (iter == settings.end()) { |
- LOG(ERROR) << "Setting not found: " << kCheckinURLKey; |
- return false; |
- } |
- new_checkin_url = GURL(iter->second); |
- if (!new_checkin_url.is_valid()) { |
- LOG(ERROR) << "Invalid checkin URL provided: " |
- << new_checkin_url.possibly_invalid_spec(); |
- return false; |
- } |
+GURL GServicesSettings::GetMCSMainEndpoint() const { |
+ // Get alternative hostname or use default. |
+ std::string mcs_hostname; |
+ SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey); |
+ if (iter != settings_.end() && !iter->second.empty()) |
+ mcs_hostname = iter->second; |
+ else |
+ mcs_hostname = kDefaultMCSHostname; |
- GURL new_registration_url; |
- iter = settings.find(kRegistrationURLKey); |
- if (iter == settings.end()) { |
- LOG(ERROR) << "Setting not found: " << kRegistrationURLKey; |
- return false; |
- } |
- new_registration_url = GURL(iter->second); |
- if (!new_registration_url.is_valid()) { |
- LOG(ERROR) << "Invalid registration URL provided: " |
- << new_registration_url.possibly_invalid_spec(); |
- return false; |
+ // Get alternative secure port or use defualt. |
+ int mcs_secure_port = 0; |
+ iter = settings_.find(kMCSSecurePortKey); |
+ if (iter == settings_.end() || iter->second.empty() || |
+ !base::StringToInt(iter->second, &mcs_secure_port)) { |
+ mcs_secure_port = kDefaultMCSMainSecurePort; |
} |
- // We only update the settings once all of them are correct. |
- checkin_interval_ = base::TimeDelta::FromSeconds(new_checkin_interval); |
- mcs_main_endpoint_ = new_mcs_main_endpoint; |
- mcs_fallback_endpoint_ = new_mcs_fallback_endpoint; |
- checkin_url_ = new_checkin_url; |
- registration_url_ = new_registration_url; |
- return true; |
+ // If constructed address makes sense use it. |
+ GURL mcs_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port)); |
+ if (mcs_endpoint.is_valid()) |
+ return mcs_endpoint; |
+ |
+ // Otherwise use default settings. |
+ return GURL(MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSMainSecurePort)); |
+} |
+ |
+GURL GServicesSettings::GetMCSFallbackEndpoint() const { |
+ // Get alternative hostname or use default. |
+ std::string mcs_hostname; |
+ SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey); |
+ if (iter != settings_.end() && !iter->second.empty()) |
+ mcs_hostname = iter->second; |
+ else |
+ mcs_hostname = kDefaultMCSHostname; |
+ |
+ // If constructed address makes sense use it. |
+ GURL mcs_endpoint( |
+ MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort)); |
+ if (mcs_endpoint.is_valid()) |
+ return mcs_endpoint; |
+ |
+ return GURL( |
+ MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSFallbackSecurePort)); |
+} |
+ |
+GURL GServicesSettings::GetRegistrationURL() const { |
+ SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey); |
+ if (iter == settings_.end() || iter->second.empty()) |
+ return GURL(kDefaultRegistrationURL); |
+ return GURL(iter->second); |
} |
} // namespace gcm |