diff options
author | fgorski@chromium.org <fgorski@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-04 23:03:55 +0000 |
---|---|---|
committer | fgorski@chromium.org <fgorski@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-04 23:03:55 +0000 |
commit | d3ba08d9ead26a59ae5503ca7f175c5a6046f2cc (patch) | |
tree | 36df33b1f4903d8236f7f74d1e822081f02b47b9 /google_apis/gcm | |
parent | 1590d95a9dc56331266b5927e5d7f81eaa395e5a (diff) | |
download | chromium_src-d3ba08d9ead26a59ae5503ca7f175c5a6046f2cc.zip chromium_src-d3ba08d9ead26a59ae5503ca7f175c5a6046f2cc.tar.gz chromium_src-d3ba08d9ead26a59ae5503ca7f175c5a6046f2cc.tar.bz2 |
Adding periodic checkin controlled by G-services settings
* extracting CheckinRequest::RequestInfo to better manage checkin parameters
* adding G-services settings digest to RequestInfo of the checkin (will be sent with checkin request)
* extracting G-services settings from checkin response
* storing and loading of the G-services settings in GCM Store
BUG=359254
Review URL: https://codereview.chromium.org/215363007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@261912 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'google_apis/gcm')
-rw-r--r-- | google_apis/gcm/engine/checkin_request.cc | 42 | ||||
-rw-r--r-- | google_apis/gcm/engine/checkin_request.h | 45 | ||||
-rw-r--r-- | google_apis/gcm/engine/checkin_request_unittest.cc | 37 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store.h | 10 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store_impl.cc | 108 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store_impl.h | 6 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store_impl_unittest.cc | 50 | ||||
-rw-r--r-- | google_apis/gcm/gcm_client_impl.cc | 63 | ||||
-rw-r--r-- | google_apis/gcm/gcm_client_impl.h | 32 | ||||
-rw-r--r-- | google_apis/gcm/gcm_client_impl_unittest.cc | 146 | ||||
-rw-r--r-- | google_apis/gcm/tools/mcs_probe.cc | 31 |
11 files changed, 481 insertions, 89 deletions
diff --git a/google_apis/gcm/engine/checkin_request.cc b/google_apis/gcm/engine/checkin_request.cc index 94a6fc9..7addbeb 100644 --- a/google_apis/gcm/engine/checkin_request.cc +++ b/google_apis/gcm/engine/checkin_request.cc @@ -46,21 +46,30 @@ void RecordCheckinStatusToUMA(CheckinRequestStatus status) { } // namespace -CheckinRequest::CheckinRequest( - const CheckinRequestCallback& callback, - const net::BackoffEntry::Policy& backoff_policy, - const checkin_proto::ChromeBuildProto& chrome_build_proto, +CheckinRequest::RequestInfo::RequestInfo( uint64 android_id, uint64 security_token, + const std::string& settings_digest, const std::vector<std::string>& account_ids, + const checkin_proto::ChromeBuildProto& chrome_build_proto) + : android_id(android_id), + security_token(security_token), + settings_digest(settings_digest), + account_ids(account_ids), + chrome_build_proto(chrome_build_proto) { +} + +CheckinRequest::RequestInfo::~RequestInfo() {} + +CheckinRequest::CheckinRequest( + const RequestInfo& request_info, + const net::BackoffEntry::Policy& backoff_policy, + const CheckinRequestCallback& callback, net::URLRequestContextGetter* request_context_getter) : request_context_getter_(request_context_getter), callback_(callback), backoff_entry_(&backoff_policy), - chrome_build_proto_(chrome_build_proto), - android_id_(android_id), - security_token_(security_token), - account_ids_(account_ids), + request_info_(request_info), weak_ptr_factory_(this) { } @@ -70,21 +79,24 @@ void CheckinRequest::Start() { DCHECK(!url_fetcher_.get()); checkin_proto::AndroidCheckinRequest request; - request.set_id(android_id_); - request.set_security_token(security_token_); + request.set_id(request_info_.android_id); + request.set_security_token(request_info_.security_token); request.set_user_serial_number(kDefaultUserSerialNumber); request.set_version(kRequestVersionValue); + if (!request_info_.settings_digest.empty()) + request.set_digest(request_info_.settings_digest); checkin_proto::AndroidCheckinProto* checkin = request.mutable_checkin(); - checkin->mutable_chrome_build()->CopyFrom(chrome_build_proto_); + checkin->mutable_chrome_build()->CopyFrom(request_info_.chrome_build_proto); #if defined(CHROME_OS) checkin->set_type(checkin_proto::DEVICE_CHROME_OS); #else checkin->set_type(checkin_proto::DEVICE_CHROME_BROWSER); #endif - for (std::vector<std::string>::const_iterator iter = account_ids_.begin(); - iter != account_ids_.end(); + for (std::vector<std::string>::const_iterator iter = + request_info_.account_ids.begin(); + iter != request_info_.account_ids.end(); ++iter) { request.add_account_cookie("[" + *iter + "]"); } @@ -141,7 +153,7 @@ void CheckinRequest::OnURLFetchComplete(const net::URLFetcher* source) { << response_status << ". Checkin failed."; RecordCheckinStatusToUMA(response_status == net::HTTP_BAD_REQUEST ? HTTP_BAD_REQUEST : HTTP_UNAUTHORIZED); - callback_.Run(0,0); + callback_.Run(response_proto); return; } @@ -167,7 +179,7 @@ void CheckinRequest::OnURLFetchComplete(const net::URLFetcher* source) { } RecordCheckinStatusToUMA(SUCCESS); - callback_.Run(response_proto.android_id(), response_proto.security_token()); + callback_.Run(response_proto); } } // namespace gcm diff --git a/google_apis/gcm/engine/checkin_request.h b/google_apis/gcm/engine/checkin_request.h index e11089a..1d706a7 100644 --- a/google_apis/gcm/engine/checkin_request.h +++ b/google_apis/gcm/engine/checkin_request.h @@ -12,7 +12,7 @@ #include "base/callback.h" #include "base/memory/weak_ptr.h" #include "google_apis/gcm/base/gcm_export.h" -#include "google_apis/gcm/protocol/android_checkin.pb.h" +#include "google_apis/gcm/protocol/checkin.pb.h" #include "net/base/backoff_entry.h" #include "net/url_request/url_fetcher_delegate.h" @@ -28,17 +28,35 @@ namespace gcm { // check-ins. class GCM_EXPORT CheckinRequest : public net::URLFetcherDelegate { public: - // A callback function for the checkin request, accepting |android_id| and - // |security_token|. - typedef base::Callback<void(uint64 android_id, uint64 security_token)> - CheckinRequestCallback; - - CheckinRequest(const CheckinRequestCallback& callback, + // A callback function for the checkin request, accepting |checkin_response| + // protobuf. + typedef base::Callback<void(const checkin_proto::AndroidCheckinResponse& + checkin_response)> CheckinRequestCallback; + + // Checkin request details. + struct GCM_EXPORT RequestInfo { + RequestInfo(uint64 android_id, + uint64 security_token, + const std::string& settings_digest, + const std::vector<std::string>& account_ids, + const checkin_proto::ChromeBuildProto& chrome_build_proto); + ~RequestInfo(); + + // Android ID of the device. + uint64 android_id; + // Security token of the device. + uint64 security_token; + // Digest of GServices settings on the device. + std::string settings_digest; + // Account IDs of GAIA accounts related to this device. + std::vector<std::string> account_ids; + // Information of the Chrome build of this device. + checkin_proto::ChromeBuildProto chrome_build_proto; + }; + + CheckinRequest(const RequestInfo& request_info, const net::BackoffEntry::Policy& backoff_policy, - const checkin_proto::ChromeBuildProto& chrome_build_proto, - uint64 android_id, - uint64 security_token, - const std::vector<std::string>& account_ids, + const CheckinRequestCallback& callback, net::URLRequestContextGetter* request_context_getter); virtual ~CheckinRequest(); @@ -57,10 +75,7 @@ class GCM_EXPORT CheckinRequest : public net::URLFetcherDelegate { net::BackoffEntry backoff_entry_; scoped_ptr<net::URLFetcher> url_fetcher_; - const checkin_proto::ChromeBuildProto chrome_build_proto_; - const uint64 android_id_; - const uint64 security_token_; - const std::vector<std::string> account_ids_; + const RequestInfo request_info_; base::WeakPtrFactory<CheckinRequest> weak_ptr_factory_; diff --git a/google_apis/gcm/engine/checkin_request_unittest.cc b/google_apis/gcm/engine/checkin_request_unittest.cc index 0142e0c..e750df0 100644 --- a/google_apis/gcm/engine/checkin_request_unittest.cc +++ b/google_apis/gcm/engine/checkin_request_unittest.cc @@ -51,6 +51,7 @@ const uint64 kBlankAndroidId = 999999UL; const uint64 kBlankSecurityToken = 999999UL; const char kChromeVersion[] = "Version String"; const uint64 kSecurityToken = 77; +const char kSettingsDigest[] = "settings_digest"; class CheckinRequestTest : public testing::Test { public: @@ -65,7 +66,8 @@ class CheckinRequestTest : public testing::Test { CheckinRequestTest(); virtual ~CheckinRequestTest(); - void FetcherCallback(uint64 android_id, uint64 security_token); + void FetcherCallback( + const checkin_proto::AndroidCheckinResponse& response); void CreateRequest(uint64 android_id, uint64 security_token); @@ -102,11 +104,13 @@ CheckinRequestTest::CheckinRequestTest() CheckinRequestTest::~CheckinRequestTest() {} -void CheckinRequestTest::FetcherCallback(uint64 android_id, - uint64 security_token) { +void CheckinRequestTest::FetcherCallback( + const checkin_proto::AndroidCheckinResponse& checkin_response) { callback_called_ = true; - android_id_ = android_id; - security_token_ = security_token; + if (checkin_response.has_android_id()) + android_id_ = checkin_response.android_id(); + if (checkin_response.has_security_token()) + security_token_ = checkin_response.security_token(); } void CheckinRequestTest::CreateRequest(uint64 android_id, @@ -117,15 +121,19 @@ void CheckinRequestTest::CreateRequest(uint64 android_id, chrome_build_proto_.set_channel( checkin_proto::ChromeBuildProto::CHANNEL_CANARY); chrome_build_proto_.set_chrome_version(kChromeVersion); + + CheckinRequest::RequestInfo request_info( + android_id, + security_token, + kSettingsDigest, + account_ids_, + chrome_build_proto_); // Then create a request with that protobuf and specified android_id, // security_token. request_.reset(new CheckinRequest( - base::Bind(&CheckinRequestTest::FetcherCallback, base::Unretained(this)), + request_info, kDefaultBackoffPolicy, - chrome_build_proto_, - android_id, - security_token, - account_ids_, + base::Bind(&CheckinRequestTest::FetcherCallback, base::Unretained(this)), url_request_context_getter_.get())); // Setting android_id_ and security_token_ to blank value, not used elsewhere @@ -198,6 +206,7 @@ TEST_F(CheckinRequestTest, FetcherData) { request_proto.checkin().type()); #endif + EXPECT_EQ(kSettingsDigest, request_proto.digest()); EXPECT_EQ(1, request_proto.account_cookie_size()); EXPECT_EQ("[account_id]", request_proto.account_cookie(0)); } @@ -244,8 +253,8 @@ TEST_F(CheckinRequestTest, ResponseHttpStatusUnauthorized) { CompleteFetch(); EXPECT_TRUE(callback_called_); - EXPECT_EQ(0u, android_id_); - EXPECT_EQ(0u, security_token_); + EXPECT_EQ(kBlankAndroidId, android_id_); + EXPECT_EQ(kBlankSecurityToken, security_token_); } TEST_F(CheckinRequestTest, ResponseHttpStatusBadRequest) { @@ -256,8 +265,8 @@ TEST_F(CheckinRequestTest, ResponseHttpStatusBadRequest) { CompleteFetch(); EXPECT_TRUE(callback_called_); - EXPECT_EQ(0u, android_id_); - EXPECT_EQ(0u, security_token_); + EXPECT_EQ(kBlankAndroidId, android_id_); + EXPECT_EQ(kBlankSecurityToken, security_token_); } TEST_F(CheckinRequestTest, ResponseHttpStatusNotOK) { diff --git a/google_apis/gcm/engine/gcm_store.h b/google_apis/gcm/engine/gcm_store.h index ccf480b..1b15e56b 100644 --- a/google_apis/gcm/engine/gcm_store.h +++ b/google_apis/gcm/engine/gcm_store.h @@ -48,6 +48,8 @@ class GCM_EXPORT GCMStore { RegistrationInfoMap registrations; std::vector<std::string> incoming_messages; OutgoingMessageMap outgoing_messages; + std::map<std::string, std::string> gservices_settings; + std::string gservices_digest; base::Time last_checkin_time; }; @@ -107,6 +109,14 @@ class GCM_EXPORT GCMStore { virtual void SetLastCheckinTime(const base::Time& last_checkin_time, const UpdateCallback& callback) = 0; + // G-service settings handling. + // Persists |settings| and |settings_digest|. It completely replaces the + // existing data. + virtual void SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& settings_digest, + const UpdateCallback& callback) = 0; + private: DISALLOW_COPY_AND_ASSIGN(GCMStore); }; diff --git a/google_apis/gcm/engine/gcm_store_impl.cc b/google_apis/gcm/engine/gcm_store_impl.cc index 9bd562d..5393fd1 100644 --- a/google_apis/gcm/engine/gcm_store_impl.cc +++ b/google_apis/gcm/engine/gcm_store_impl.cc @@ -16,12 +16,14 @@ #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" +#include "base/time/time.h" #include "base/tracked_objects.h" #include "components/os_crypt/os_crypt.h" #include "google_apis/gcm/base/mcs_message.h" #include "google_apis/gcm/base/mcs_util.h" #include "google_apis/gcm/protocol/mcs.pb.h" #include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" namespace gcm { @@ -53,6 +55,14 @@ const char kOutgoingMsgKeyStart[] = "outgoing1-"; // Key guaranteed to be higher than all outgoing message keys. // Used for limiting iteration. const char kOutgoingMsgKeyEnd[] = "outgoing2-"; +// Lowest lexicographically ordered G-service settings key. +// Used for prefixing G-services settings. +const char kGServiceSettingKeyStart[] = "gservice1-"; +// Key guaranteed to be higher than all G-services settings keys. +// Used for limiting iteration. +const char kGServiceSettingKeyEnd[] = "gservice2-"; +// Key for digest of the last G-services settings update. +const char kGServiceSettingsDigestKey[] = "gservices_digest"; // Key used to timestamp last checkin (marked with G services settings update). const char kLastCheckinTimeKey[] = "last_checkin_time"; @@ -76,6 +86,14 @@ std::string ParseOutgoingKey(const std::string& key) { return key.substr(arraysize(kOutgoingMsgKeyStart) - 1); } +std::string MakeGServiceSettingKey(const std::string& setting_name) { + return kGServiceSettingKeyStart + setting_name; +} + +std::string ParseGServiceSettingKey(const std::string& key) { + return key.substr(arraysize(kGServiceSettingKeyStart) - 1); +} + // Note: leveldb::Slice keeps a pointer to the data in |s|, which must therefore // outlive the slice. // For example: MakeSlice(MakeOutgoingKey(x)) is invalid. @@ -121,6 +139,10 @@ class GCMStoreImpl::Backend const UpdateCallback& callback); void SetLastCheckinTime(const base::Time& last_checkin_time, const UpdateCallback& callback); + void SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& digest, + const UpdateCallback& callback); private: friend class base::RefCountedThreadSafe<Backend>; @@ -131,6 +153,8 @@ class GCMStoreImpl::Backend bool LoadIncomingMessages(std::vector<std::string>* incoming_messages); bool LoadOutgoingMessages(OutgoingMessageMap* outgoing_messages); bool LoadLastCheckinTime(base::Time* last_checkin_time); + bool LoadGServicesSettings(std::map<std::string, std::string>* settings, + std::string* digest); const base::FilePath path_; scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_; @@ -176,12 +200,16 @@ void GCMStoreImpl::Backend::Load(const LoadCallback& callback) { !LoadRegistrations(&result->registrations) || !LoadIncomingMessages(&result->incoming_messages) || !LoadOutgoingMessages(&result->outgoing_messages) || - !LoadLastCheckinTime(&result->last_checkin_time)) { + !LoadLastCheckinTime(&result->last_checkin_time) || + !LoadGServicesSettings(&result->gservices_settings, + &result->gservices_digest)) { result->device_android_id = 0; result->device_security_token = 0; result->registrations.clear(); result->incoming_messages.clear(); result->outgoing_messages.clear(); + result->gservices_settings.clear(); + result->gservices_digest.clear(); result->last_checkin_time = base::Time::FromInternalValue(0LL); foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, @@ -465,7 +493,44 @@ void GCMStoreImpl::Backend::SetLastCheckinTime( if (!s.ok()) LOG(ERROR) << "LevelDB set last checkin time failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok())); +} + +void GCMStoreImpl::Backend::SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& settings_digest, + const UpdateCallback& callback) { + leveldb::WriteBatch write_batch; + + // Remove all existing settings. + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kGServiceSettingKeyStart)); + iter->Valid() && iter->key().ToString() < kGServiceSettingKeyEnd; + iter->Next()) { + write_batch.Delete(iter->key()); + } + + // Add the new settings. + for (std::map<std::string, std::string>::const_iterator iter = + settings.begin(); + iter != settings.end(); ++iter) { + write_batch.Put(MakeSlice(MakeGServiceSettingKey(iter->first)), + MakeSlice(iter->second)); + } + + // Update the settings digest. + write_batch.Put(MakeSlice(kGServiceSettingsDigestKey), + MakeSlice(settings_digest)); + + // Write it all in a batch. + leveldb::WriteOptions write_options; + write_options.sync = true; + leveldb::Status s = db_->Write(write_options, &write_batch); + if (!s.ok()) + LOG(ERROR) << "LevelDB GService Settings update failed: " << s.ToString(); foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok())); } @@ -604,6 +669,34 @@ bool GCMStoreImpl::Backend::LoadLastCheckinTime( return true; } +bool GCMStoreImpl::Backend::LoadGServicesSettings( + std::map<std::string, std::string>* settings, + std::string* digest) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + // Load all of the GServices settings. + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kGServiceSettingKeyStart)); + iter->Valid() && iter->key().ToString() < kGServiceSettingKeyEnd; + iter->Next()) { + std::string value = iter->value().ToString(); + if (value.empty()) { + LOG(ERROR) << "Error reading GService Settings " << value; + return false; + } + std::string id = ParseGServiceSettingKey(iter->key().ToString()); + (*settings)[id] = value; + DVLOG(1) << "Found G Service setting with key: " << id + << ", and value: " << value; + } + + // Load the settings digest. It's ok if it is empty. + db_->Get(read_options, MakeSlice(kGServiceSettingsDigestKey), digest); + + return true; +} + GCMStoreImpl::GCMStoreImpl( const base::FilePath& path, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) @@ -783,6 +876,19 @@ void GCMStoreImpl::SetLastCheckinTime(const base::Time& last_checkin_time, callback)); } +void GCMStoreImpl::SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& digest, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::SetGServicesSettings, + backend_, + settings, + digest, + callback)); +} + void GCMStoreImpl::LoadContinuation(const LoadCallback& callback, scoped_ptr<LoadResult> result) { if (!result->success) { diff --git a/google_apis/gcm/engine/gcm_store_impl.h b/google_apis/gcm/engine/gcm_store_impl.h index a1eeff6..e46c503 100644 --- a/google_apis/gcm/engine/gcm_store_impl.h +++ b/google_apis/gcm/engine/gcm_store_impl.h @@ -77,6 +77,12 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore { virtual void SetLastCheckinTime(const base::Time& last_checkin_time, const UpdateCallback& callback) OVERRIDE; + // G-service settings handling. + virtual void SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& settings_digest, + const UpdateCallback& callback) OVERRIDE; + private: typedef std::map<std::string, int> AppIdToMessageCountMap; diff --git a/google_apis/gcm/engine/gcm_store_impl_unittest.cc b/google_apis/gcm/engine/gcm_store_impl_unittest.cc index 41bb427..f0f9019 100644 --- a/google_apis/gcm/engine/gcm_store_impl_unittest.cc +++ b/google_apis/gcm/engine/gcm_store_impl_unittest.cc @@ -111,6 +111,7 @@ TEST_F(GCMStoreImplTest, LoadNew) { EXPECT_EQ(0U, load_result->device_security_token); EXPECT_TRUE(load_result->incoming_messages.empty()); EXPECT_TRUE(load_result->outgoing_messages.empty()); + EXPECT_TRUE(load_result->gservices_settings.empty()); EXPECT_EQ(base::Time::FromInternalValue(0LL), load_result->last_checkin_time); } @@ -154,10 +155,57 @@ TEST_F(GCMStoreImplTest, LastCheckinTime) { gcm_store->Load(base::Bind( &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); PumpLoop(); - ASSERT_EQ(last_checkin_time, load_result->last_checkin_time); } +TEST_F(GCMStoreImplTest, GServicesSettings_ProtocolV2) { + scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); + scoped_ptr<GCMStore::LoadResult> load_result; + gcm_store->Load(base::Bind( + &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); + PumpLoop(); + + std::map<std::string, std::string> settings; + settings["checkin_interval"] = "12345"; + settings["mcs_port"] = "438"; + settings["checkin_url"] = "http://checkin.google.com"; + std::string digest = "digest1"; + + gcm_store->SetGServicesSettings( + settings, + digest, + base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); + PumpLoop(); + + gcm_store = BuildGCMStore().Pass(); + gcm_store->Load(base::Bind( + &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); + PumpLoop(); + + ASSERT_EQ(settings, load_result->gservices_settings); + ASSERT_EQ(digest, load_result->gservices_digest); + + // Remove some, and add some. + settings.clear(); + settings["checkin_interval"] = "54321"; + settings["registration_url"] = "http://registration.google.com"; + digest = "digest2"; + + gcm_store->SetGServicesSettings( + settings, + digest, + base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); + PumpLoop(); + + gcm_store = BuildGCMStore().Pass(); + gcm_store->Load(base::Bind( + &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); + PumpLoop(); + + ASSERT_EQ(settings, load_result->gservices_settings); + ASSERT_EQ(digest, load_result->gservices_digest); +} + TEST_F(GCMStoreImplTest, Registrations) { scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); scoped_ptr<GCMStore::LoadResult> load_result; diff --git a/google_apis/gcm/gcm_client_impl.cc b/google_apis/gcm/gcm_client_impl.cc index 568e740..78b7da1 100644 --- a/google_apis/gcm/gcm_client_impl.cc +++ b/google_apis/gcm/gcm_client_impl.cc @@ -11,6 +11,7 @@ #include "base/message_loop/message_loop.h" #include "base/metrics/histogram.h" #include "base/sequenced_task_runner.h" +#include "base/strings/string_number_conversions.h" #include "base/time/default_clock.h" #include "google_apis/gcm/base/mcs_message.h" #include "google_apis/gcm/base/mcs_util.h" @@ -221,6 +222,8 @@ void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) { device_checkin_info_.android_id = result->device_android_id; device_checkin_info_.secret = result->device_security_token; base::Time last_checkin_time = result->last_checkin_time; + gservices_settings_ = result->gservices_settings; + gservices_digest_ = result->gservices_digest; InitializeMCSClient(result.Pass()); if (device_checkin_info_.IsValid()) { @@ -293,32 +296,36 @@ void GCMClientImpl::ResetState() { } void GCMClientImpl::StartCheckin() { + CheckinRequest::RequestInfo request_info( + device_checkin_info_.android_id, + device_checkin_info_.secret, + gservices_digest_, + account_ids_, + chrome_build_proto_); checkin_request_.reset( - new CheckinRequest(base::Bind(&GCMClientImpl::OnCheckinCompleted, - weak_ptr_factory_.GetWeakPtr()), + new CheckinRequest(request_info, kDefaultBackoffPolicy, - chrome_build_proto_, - device_checkin_info_.android_id, - device_checkin_info_.secret, - account_ids_, + base::Bind(&GCMClientImpl::OnCheckinCompleted, + weak_ptr_factory_.GetWeakPtr()), url_request_context_getter_)); checkin_request_->Start(); } -void GCMClientImpl::OnCheckinCompleted(uint64 android_id, - uint64 security_token) { +void GCMClientImpl::OnCheckinCompleted( + const checkin_proto::AndroidCheckinResponse& checkin_response) { checkin_request_.reset(); - CheckinInfo checkin_info; - checkin_info.android_id = android_id; - checkin_info.secret = security_token; - - if (!checkin_info.IsValid()) { - // TODO(fgorski): I don't think a retry here will help, we should probalby + if (!checkin_response.has_android_id() || + !checkin_response.has_security_token()) { + // TODO(fgorski): I don't think a retry here will help, we should probably // start over. By checking in with (0, 0). return; } + CheckinInfo checkin_info; + checkin_info.android_id = checkin_response.android_id(); + checkin_info.secret = checkin_response.security_token(); + if (state_ == INITIAL_DEVICE_CHECKIN) { OnFirstTimeDeviceCheckinCompleted(checkin_info); } else { @@ -335,6 +342,7 @@ void GCMClientImpl::OnCheckinCompleted(uint64 android_id, last_checkin_time, base::Bind(&GCMClientImpl::SetLastCheckinTimeCallback, weak_ptr_factory_.GetWeakPtr())); + UpdateGServicesSettings(checkin_response); SchedulePeriodicCheckin(last_checkin_time); } } @@ -702,4 +710,31 @@ void GCMClientImpl::HandleIncomingSendError( send_error_details); } +void GCMClientImpl::UpdateGServicesSettings( + const checkin_proto::AndroidCheckinResponse& checkin_response) { + if (!checkin_response.has_digest() || + checkin_response.digest() == gservices_digest_) { + return; + } + + gservices_digest_ = checkin_response.digest(); + gservices_settings_.clear(); + + 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(); + gservices_settings_[name] = value; + } + + gcm_store_->SetGServicesSettings( + gservices_settings_, + gservices_digest_, + base::Bind(&GCMClientImpl::UpdateGServicesSettingsCallback, + weak_ptr_factory_.GetWeakPtr())); +} + +void GCMClientImpl::UpdateGServicesSettingsCallback(bool success) { + DCHECK(success); +} + } // namespace gcm diff --git a/google_apis/gcm/gcm_client_impl.h b/google_apis/gcm/gcm_client_impl.h index 51ca7be..7b28a34 100644 --- a/google_apis/gcm/gcm_client_impl.h +++ b/google_apis/gcm/gcm_client_impl.h @@ -19,7 +19,7 @@ #include "google_apis/gcm/engine/registration_request.h" #include "google_apis/gcm/engine/unregistration_request.h" #include "google_apis/gcm/gcm_client.h" -#include "google_apis/gcm/protocol/android_checkin.pb.h" +#include "google_apis/gcm/protocol/checkin.pb.h" #include "net/base/net_log.h" #include "net/url_request/url_request_context_getter.h" @@ -40,6 +40,10 @@ class CheckinRequest; class ConnectionFactory; class GCMClientImplTest; +// Map with GServices settings. +// TODO(fgorski): Extract services handling to a separate struct/class. +typedef std::map<std::string, std::string> GServicesSettingsMap; + // Helper class for building GCM internals. Allows tests to inject fake versions // as necessary. class GCM_EXPORT GCMInternalsBuilder { @@ -167,13 +171,12 @@ class GCM_EXPORT GCMClientImpl : public GCMClient { // Starts a first time device checkin. void StartCheckin(); - // Completes the device checkin request. - // |android_id| and |security_token| are expected to be non-zero or an error - // is triggered. Function also cleans up the pending checkin. - void OnCheckinCompleted(uint64 android_id, - uint64 security_token); - // Schedules next device checkin, based on |last_checkin_time| and default - // checkin interval. + // Completes the device checkin request by parsing the |checkin_response|. + // Function also cleans up the pending checkin. + void OnCheckinCompleted( + const checkin_proto::AndroidCheckinResponse& checkin_response); + // Schedules next device checkin, based on |last_checkin_time| and + // checkin_interval specified in GServices settings. void SchedulePeriodicCheckin(const base::Time& last_checkin_time); // Callback for setting last checkin time in the |gcm_store_|. void SetLastCheckinTimeCallback(bool success); @@ -212,6 +215,13 @@ class GCM_EXPORT GCMClientImpl : public GCMClient { const mcs_proto::DataMessageStanza& data_message_stanza, MessageData& message_data); + // Updates the G-services settings based on the |checkin_response|. It assumes + // base::Time::Now() is the checkin time. + void UpdateGServicesSettings( + const checkin_proto::AndroidCheckinResponse& checkin_response); + // Completes the G-services settings update request. + void UpdateGServicesSettingsCallback(bool success); + // Builder for the GCM internals (mcs client, etc.). scoped_ptr<GCMInternalsBuilder> internals_builder_; @@ -240,6 +250,12 @@ class GCM_EXPORT GCMClientImpl : public GCMClient { scoped_ptr<ConnectionFactory> connection_factory_; scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; + // Map with GServices settings, such us checkin_interval, checkin_url, etc. + GServicesSettingsMap gservices_settings_; + + // Digest of the |gservices_settings|. + std::string gservices_digest_; + // Controls receiving and sending of packets and reliable message queueing. scoped_ptr<MCSClient> mcs_client_; diff --git a/google_apis/gcm/gcm_client_impl_unittest.cc b/google_apis/gcm/gcm_client_impl_unittest.cc index 39fb3f1..2520382 100644 --- a/google_apis/gcm/gcm_client_impl_unittest.cc +++ b/google_apis/gcm/gcm_client_impl_unittest.cc @@ -7,6 +7,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" #include "base/test/simple_test_clock.h" #include "components/os_crypt/os_crypt.h" #include "google_apis/gcm/base/mcs_message.h" @@ -38,6 +39,9 @@ enum LastEvent { const uint64 kDeviceAndroidId = 54321; const uint64 kDeviceSecurityToken = 12345; +const int64 kSettingsCheckinInterval = 0; +const char kSettingsCheckinIntervalKey[] = "checkin_interval"; +const char kSettingsDefaultDigest[] = "default_digest"; const char kAppId[] = "app_id"; const char kSender[] = "project_id"; const char kSender2[] = "project_id2"; @@ -183,7 +187,11 @@ class GCMClientImplTest : public testing::Test, void BuildGCMClient(); void InitializeGCMClient(); void ReceiveMessageFromMCS(const MCSMessage& message); - void CompleteCheckin(uint64 android_id, uint64 security_token); + void CompleteCheckin( + uint64 android_id, + uint64 security_token, + const std::string& digest, + const std::map<std::string, std::string>& settings); void CompleteRegistration(const std::string& registration_id); void CompleteUnregistration(const std::string& app_id); @@ -217,6 +225,12 @@ class GCMClientImplTest : public testing::Test, ConnectionFactory* connection_factory() const { return gcm_client_->connection_factory_.get(); } + GServicesSettingsMap& services_settings() { + return gcm_client_->gservices_settings_; + } + const std::string& services_digest() { + return gcm_client_->gservices_digest_; + } void reset_last_event() { last_event_ = NONE; @@ -239,19 +253,21 @@ class GCMClientImplTest : public testing::Test, const GCMClient::SendErrorDetails& last_error_details() const { return last_error_details_; } + base::SimpleTestClock* clock() const { + return reinterpret_cast<base::SimpleTestClock*>(gcm_client_->clock_.get()); + } int64 CurrentTime(); - private: // Tooling. void PumpLoop(); void PumpLoopUntilIdle(); void QuitLoop(); + void ResetLoop(); - base::SimpleTestClock* clock() const { - return reinterpret_cast<base::SimpleTestClock*>(gcm_client_->clock_.get()); - } + bool CreateUniqueTempDir(); + private: // Variables used for verification. LastEvent last_event_; std::string last_app_id_; @@ -282,11 +298,14 @@ GCMClientImplTest::GCMClientImplTest() GCMClientImplTest::~GCMClientImplTest() {} void GCMClientImplTest::SetUp() { - ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); - run_loop_.reset(new base::RunLoop); + ASSERT_TRUE(CreateUniqueTempDir()); + ResetLoop(); BuildGCMClient(); InitializeGCMClient(); - CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken); + CompleteCheckin(kDeviceAndroidId, + kDeviceSecurityToken, + kSettingsDefaultDigest, + std::map<std::string, std::string>()); } void GCMClientImplTest::PumpLoop() { @@ -304,18 +323,38 @@ void GCMClientImplTest::QuitLoop() { run_loop_->Quit(); } +void GCMClientImplTest::ResetLoop() { + run_loop_.reset(new base::RunLoop); +} + +bool GCMClientImplTest::CreateUniqueTempDir() { + return temp_directory_.CreateUniqueTempDir(); +} + void GCMClientImplTest::BuildGCMClient() { gcm_client_.reset(new GCMClientImpl( make_scoped_ptr<GCMInternalsBuilder>(new FakeGCMInternalsBuilder()))); } -void GCMClientImplTest::CompleteCheckin(uint64 android_id, - uint64 security_token) { +void GCMClientImplTest::CompleteCheckin( + uint64 android_id, + uint64 security_token, + const std::string& digest, + const std::map<std::string, std::string>& settings) { checkin_proto::AndroidCheckinResponse response; response.set_stats_ok(true); response.set_android_id(android_id); response.set_security_token(security_token); + // For testing GServices settings. + response.set_digest(digest); + for (std::map<std::string, std::string>::const_iterator it = settings.begin(); + it != settings.end(); ++it) { + checkin_proto::GservicesSetting* setting = response.add_setting(); + setting->set_name(it->first); + setting->set_value(it->second); + } + std::string response_string; response.SerializeToString(&response_string); @@ -610,4 +649,91 @@ TEST_F(GCMClientImplTest, SendMessage) { mcs_client()->last_data_message_stanza().app_data(0).value()); } +class GCMClientImplCheckinTest : public GCMClientImplTest { + public: + GCMClientImplCheckinTest(); + virtual ~GCMClientImplCheckinTest(); + + virtual void SetUp() OVERRIDE; + + std::map<std::string, std::string> GenerateSettings(); +}; + +GCMClientImplCheckinTest::GCMClientImplCheckinTest() {} + +GCMClientImplCheckinTest::~GCMClientImplCheckinTest() {} + +void GCMClientImplCheckinTest::SetUp() { + ASSERT_TRUE(CreateUniqueTempDir()); + ResetLoop(); + BuildGCMClient(); + InitializeGCMClient(); +} + +std::map<std::string, std::string> +GCMClientImplCheckinTest::GenerateSettings() { + std::map<std::string, std::string> settings; + settings[kSettingsCheckinIntervalKey] = + base::Int64ToString(kSettingsCheckinInterval); + return settings; +} + +TEST_F(GCMClientImplCheckinTest, GServicesSettingsAfterInitialCheckin) { + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, + kSettingsDefaultDigest, GenerateSettings()); + EXPECT_EQ(base::Int64ToString(kSettingsCheckinInterval), + services_settings()[kSettingsCheckinIntervalKey]); +} + +// This test only checks that periodic checkin happens. +TEST_F(GCMClientImplCheckinTest, PeriodicCheckin) { + std::map<std::string, std::string> settings = GenerateSettings(); + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, + kSettingsDefaultDigest, settings); + PumpLoopUntilIdle(); + + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, + kSettingsDefaultDigest, settings); + } + +// This test checks that checkin reponse with the same digest will not update +// G-services settings. +TEST_F(GCMClientImplCheckinTest, GServicesSettingsSameDigest) { + std::map<std::string, std::string> settings = GenerateSettings(); + settings["checkin_url"] = "http://checkin.google.com"; + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, + kSettingsDefaultDigest, settings); + EXPECT_EQ(settings, services_settings()); + EXPECT_EQ(kSettingsDefaultDigest, services_digest()); + PumpLoopUntilIdle(); + + // Response will carry same digest and no settings. + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, + kSettingsDefaultDigest, std::map<std::string, std::string>()); + EXPECT_EQ(settings, services_settings()); + EXPECT_EQ(kSettingsDefaultDigest, services_digest()); +} + +// Test that checkin response with a different digest will also update the +// G-services settings. +TEST_F(GCMClientImplCheckinTest, GServicesSettingsDifferentDigest) { + std::map<std::string, std::string> settings = GenerateSettings(); + settings["checkin_url"] = "http://checkin.google.com"; + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, + kSettingsDefaultDigest, settings); + EXPECT_EQ(settings, services_settings()); + EXPECT_EQ(kSettingsDefaultDigest, services_digest()); + PumpLoopUntilIdle(); + + settings.clear(); + settings["some_settings"] = "on second checkin"; + settings[kSettingsCheckinIntervalKey] = "2100"; + settings["checkin_url"] = "http://checkin.google.com"; + std::string new_digest = "some_other_digest"; + + CompleteCheckin(kDeviceAndroidId, kDeviceSecurityToken, new_digest, settings); + EXPECT_EQ(settings, services_settings()); + EXPECT_EQ(new_digest, services_digest()); +} + } // namespace gcm diff --git a/google_apis/gcm/tools/mcs_probe.cc b/google_apis/gcm/tools/mcs_probe.cc index 6d9a7d5..038f3af 100644 --- a/google_apis/gcm/tools/mcs_probe.cc +++ b/google_apis/gcm/tools/mcs_probe.cc @@ -210,7 +210,8 @@ class MCSProbe { void LoadCallback(scoped_ptr<GCMStore::LoadResult> load_result); void UpdateCallback(bool success); void ErrorCallback(); - void OnCheckInCompleted(uint64 android_id, uint64 secret); + void OnCheckInCompleted( + const checkin_proto::AndroidCheckinResponse& checkin_response); void StartMCSLogin(); base::DefaultClock clock_; @@ -423,24 +424,32 @@ void MCSProbe::CheckIn() { chrome_build_proto.set_channel( checkin_proto::ChromeBuildProto::CHANNEL_CANARY); chrome_build_proto.set_chrome_version(kChromeVersion); + + CheckinRequest::RequestInfo request_info( + 0, 0, std::string(), std::vector<std::string>(), chrome_build_proto); + checkin_request_.reset(new CheckinRequest( - base::Bind(&MCSProbe::OnCheckInCompleted, base::Unretained(this)), + request_info, kDefaultBackoffPolicy, - chrome_build_proto, - 0, - 0, - std::vector<std::string>(), + base::Bind(&MCSProbe::OnCheckInCompleted, base::Unretained(this)), url_request_context_getter_.get())); checkin_request_->Start(); } -void MCSProbe::OnCheckInCompleted(uint64 android_id, uint64 secret) { +void MCSProbe::OnCheckInCompleted( + const checkin_proto::AndroidCheckinResponse& checkin_response) { + bool success = checkin_response.has_android_id() && + checkin_response.android_id() != 0UL && + checkin_response.has_security_token() && + checkin_response.security_token() != 0UL; LOG(INFO) << "Check-in request completion " - << (android_id ? "success!" : "failure!"); - if (!android_id || !secret) + << (success ? "success!" : "failure!"); + + if (!success) return; - android_id_ = android_id; - secret_ = secret; + + android_id_ = checkin_response.android_id(); + secret_ = checkin_response.security_token(); gcm_store_->SetDeviceCredentials(android_id_, secret_, |