Home | History | Annotate | Download | only in engine
      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 
     13 namespace {
     14 // The expected time in seconds between periodic checkins.
     15 const char kCheckinIntervalKey[] = "checkin_interval";
     16 // The override URL to the checkin server.
     17 const char kCheckinURLKey[] = "checkin_url";
     18 // The MCS machine name to connect to.
     19 const char kMCSHostnameKey[] = "gcm_hostname";
     20 // The MCS port to connect to.
     21 const char kMCSSecurePortKey[] = "gcm_secure_port";
     22 // The URL to get MCS registration IDs.
     23 const char kRegistrationURLKey[] = "gcm_registration_url";
     24 
     25 const int64 kDefaultCheckinInterval = 2 * 24 * 60 * 60;  // seconds = 2 days.
     26 const int64 kMinimumCheckinInterval = 12 * 60 * 60;      // seconds = 12 hours.
     27 const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin";
     28 const char kDefaultMCSHostname[] = "mtalk.google.com";
     29 const int kDefaultMCSMainSecurePort = 5228;
     30 const int kDefaultMCSFallbackSecurePort = 443;
     31 const 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.
     35 const char kDeleteSettingPrefix[] = "delete_";
     36 // Settings digest starts with verison number followed by '-'.
     37 const char kDigestVersionPrefix[] = "1-";
     38 const char kMCSEnpointTemplate[] = "https://%s:%d";
     39 const int kMaxSecurePort = 65535;
     40 
     41 std::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.
     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 
    169 }  // namespace
    170 
    171 namespace gcm {
    172 
    173 // static
    174 const base::TimeDelta GServicesSettings::MinimumCheckinInterval() {
    175   return base::TimeDelta::FromSeconds(kMinimumCheckinInterval);
    176 }
    177 
    178 // static
    179 const GURL GServicesSettings::DefaultCheckinURL() {
    180   return GURL(kDefaultCheckinURL);
    181 }
    182 
    183 // static
    184 std::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 = StringToLowerASCII(digest);
    200   return digest;
    201 }
    202 
    203 GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) {
    204   digest_ = CalculateDigest(settings_);
    205 }
    206 
    207 GServicesSettings::~GServicesSettings() {
    208 }
    209 
    210 bool 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 
    250 void 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 
    269 base::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 
    283 GURL 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 
    290 GURL 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 
    316 GURL 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 
    335 GURL 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